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

Parameter order of linear collections #147

Closed
utdemir opened this issue Sep 2, 2020 · 5 comments · Fixed by #242
Closed

Parameter order of linear collections #147

utdemir opened this issue Sep 2, 2020 · 5 comments · Fixed by #242

Comments

@utdemir
Copy link
Contributor

utdemir commented Sep 2, 2020

Currently, most of our linear collections (Array, Vector.Mutable and HashMap) take the collection as a first argument. Here are some examples from linear base:

Data.Array.Mutable.Linear.write :: HasCallStack => Array a #-> Int -> a -> Array a
Data.Vector.Mutable.Linear.write :: HasCallStack => Vector a #-> Int -> a -> Vector a

This is also what vector library does.

But there are also places where we do the opposite:

Data.Array.Mutable.Linear.resize :: HasCallStack => Int -> Array a #-> Array a
Data.Array.Polarized.Pull.foldr :: (a #-> b #-> b) -> b #-> Array a #-> b
Data.Array.Destination.split :: Int -> DArray a #-> (DArray a, DArray a)

(Also, as expected, the functions we re-implemented from base also tend to take the collection as the last argument)


I think, taking the collection as the last parameter has some benefits:

  • I believe most libraries are using the collection-last argument order.
  • It makes point-free usage nicer (vec & write 0 'a' & write 1 'c')
  • Usually our functions are linear on the collection, and not on the rest. So, a type signature like:
write :: Int -> a -> Array a #-> Array a

Makes it clear that "given an Int and an a that we're linearly transforming an Array to another". The alternative:

write :: Array a #-> Int -> a -> Array a

In my opinion does not read as well, the unrestricted arrow before the result gives me the wrong impression.

The disadvantages I can think of:

  • Differing from the vector library.
  • We have to change some amount of code.

No matter which order we choose, it would be good to stick to it across the codebase.

This issue is broken up from the original PR: #146

@aspiwack
Copy link
Member

aspiwack commented Sep 2, 2020

This is also what vector library does.

And the IORef library.

Generally speaking, both orders make a lot of sense, depending on how you are going to use the arguments.

Also, in the case of snoc, in vector (and, I assume, many places), the arguments are in an order which suggests their ordering (a bit like a specialised append)

cons :: a -> Vector a -> Vector a
snoc :: Vector a -> a -> Vector a

Personnally, I have one order, which I use whenever I write a library. It's obviously the right one. The problem is that it's not always the same… But, you know, obvious.

More seriously, argument ordering really don't have a good unique solution. Maybe we could make an argument that, since linear types favour chaining quite a bit, having an argument order which makes chaining easy is a reasonable choice.

Quite frankly, I've got a hard time making a decision on this one. I don't really have a feel for how the Haskell community, at large, feels about argument order (nor any community to be honest, there are reasons why people I work with tend to immediately recognise code that I wrote…). @mboes do you have some opinion on the subject?

@utdemir utdemir added this to the Initial release milestone Sep 4, 2020
@utdemir
Copy link
Contributor Author

utdemir commented Sep 11, 2020

As with issues #164 and #165; we're extending the collections interface, and it makes the inconsistency quite a bit obvious. I'd prefer if we make a decision sooner rather than later.

I'm happy with either orders (with a slight preference towards collection-last order), both will be much better than status-quo.

cc @aspiwack @mboes

@mboes
Copy link
Member

mboes commented Sep 29, 2020

I think it's fine to break with (relatively hard to characterize consistently) precedent if it's to establish a convention that makes chaining more convenient. If such a convention is, "if you're in the fortunate case where there is exactly one linear argument, make it the last one", then let's establish that and document it in the design file.

The precedent seems to be that imperative interfaces write the target first whereas pure interfaces usually have it last. But linear types gives us a pure interface for doing imperative things (like writing to an array cell). But that begs the question, what do we do with I/O? We're still in a monad in that case.

@aspiwack
Copy link
Member

The precedent seems to be that imperative interfaces write the target first whereas pure interfaces usually have it last. But linear types gives us a pure interface for doing imperative things (like writing to an array cell). But that begs the question, what do we do with I/O? We're still in a monad in that case.

I've been thinking about this in the past few days, actually. I think that what we should do is: get/set/insert/lookup functions (the names are not particularly consistent here, the array library even only has (!)) should have the same convention as Map, whereas read, write functions should have the same convention as MVar. And, in mutable collections, we would want to have both styles. (that is, a get (or lookup) function, as well as a read = flip get function).

At least we want

  • a set of collection-last primitives for all the pure interfaces
    • optionally, a set of collection first with names like read, write. These suggest mutation, hence are better suited having the same convention as IORef.
  • mutable-stuff first for IO, because they are often used with $ and a big-ish right-hand side.

@Divesh-Otwani
Copy link
Contributor

Late to the discussion, but I agree with @aspiwack. get/set/insert/lookup with the collection last for completely pure things and both for mutable things. It seems like a clean convention.

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

Successfully merging a pull request may close this issue.

4 participants