Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't export fst (and alike) from Prelude.Linear #405

Open
andreasabel opened this issue Apr 10, 2022 · 7 comments
Open

Don't export fst (and alike) from Prelude.Linear #405

andreasabel opened this issue Apr 10, 2022 · 7 comments

Comments

@andreasabel
Copy link
Contributor

It is surprising that Prelude.Linear exports the non-linear version of projection fst :: (a,b) -> a.
This tripped me up (with non-inspiring errors about multiplicity) when writing evalState:

I was naturally expecting that fst :: Consumable b => (a,b) -> a would be by default in scope if a fst is in scope. After all, we want linear programs when we import Prelude.Linear!
So, I suggest either to not export fst (and alike) or export them from Data.Tuple.Linear.

@aspiwack
Copy link
Member

There is a reason why we export these versions of fst and snd (not that we didn't have oversights of the sort (#354)).

The reason is that it's consistent with the behaviour of record types:

data Pair a b = MkPair { fst :: a, snd :: b }

We have:

MkPair :: a %1 -> b %1 -> Pair a b
fst :: Pair a b -> a
snd :: Pair a b -> b

This is the only type that we can give to fst and snd; unless we have an affine multiplicity, or GHC knows of Consumable and we give the type that you suggest. One problem with the latter approach is that it's not backward compatible with regular Haskell.

I should say, more generally, that I'm always rather suspicious of functions with Consumable constraints in their types. They sometimes make sense, but it's worth remembering that consume can sometimes be pretty costly (it usually requires traversing linearly the data it's consuming; but since this data has thunks, it may hide arbitrary computations, which you may not expect when writing fst x).

After reading this what are your thoughts?

@andreasabel
Copy link
Contributor Author

I agree that for arbitrary records, linear projects would be awkward (needing one Consumable constraint for each field that is thrown away by the projection).
For just pairs, it does not look so terrible, but I can accept your reservations about Consumable.

So, the remaining option would be to not export fst from Prelude.Linear so the user has to consciously import it from either Data.Tuple or Data.Tuple.Linear. Or, have a Prelude.NonLinear that exports non-linear fst and the like, to avoid the confusion that the non-linear fst is imported via (seemingly) linear Prelude.

I think what deepened my confusion is that I misread the index (https://hackage.haskell.org/package/linear-base-0.2.0/docs/doc-index-F.html), which lists fst as:

1 (Function) Prelude.Linear
2 (Function) Data.Tuple.Linear

I was somehow assuming that this is the same function exported twice, but I should have looked more carefully to recognize that these are different functions (nice if the index gave the type signatures...).

Not sure what to recommend. It might just be a beginner's learning experience.
Maybe do nothing for now and wait to see if more people suffer from the same confusion as me.

@aspiwack
Copy link
Member

Hey, I'm back 🙂

Looking at it with fresher eyes, I think that the mistake is using the names from base for

fst :: Consumable b => (a, b) %1 -> a

As it can be argued that it doesn't have the expected behaviour. Ironically it could be called consumeSnd, or something like this… But maybe there could be a good name, with a naming convention that we can reuse for this sort of things. Any idea?

@andreasabel
Copy link
Contributor Author

I think my expectation was that this is just fst.
Disambiguation by qualified imports wouldn't be so terrible if one had a simple way to import the linear versions of Prelude functions in one go. E.g.

import Prelude as NonLinear
-- OR: import Prelude.NonLinear as NonLinear / as Ur / as U
import qualified Prelude.Linear as Linear / as L
...
... fst ...
... NonLinear.fst ... (Ur.fst) (U.fst)
... Linear.fst ... (L.fst)

However, atm Prelude.Linear is something else than providing linear versions...

@b-mehta
Copy link
Contributor

b-mehta commented Apr 28, 2022

For whatever it's worth, I have a slight preference for consumeSnd: this function will perform something on the second argument and I think it's nice for the name to reflect that.

@aspiwack
Copy link
Member

For whatever it's worth, I have a slight preference for consumeSnd: this function will perform something on the second argument and I think it's nice for the name to reflect that.

I agree. However, my fear is that it's not really generalisable: how would we name the list-zipping functions, then? zipAndConsumeLeftover? Doesn't sound super nice.

@andreasabel
Copy link
Contributor Author

Similarly, would fst3 then be named consumeSndAndThd3?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants