Naming scheme for total counterparts of head, tail, etc

There is a proposal to extend API of vector with total counterparts for partial functions, which are well-defined only for non-empty vectors. Compare existing

head :: Vector a -> a 
last :: Vector a -> a
maximumBy :: (a -> a -> Ordering) -> Vector a -> a
...

with hypothetical

head' :: Vector a -> Maybe a 
last' :: Vector a -> Maybe a
maximumBy' :: (a -> a -> Ordering) -> Vector a -> Maybe a
...

The maintainers are in agreement that their preference is to use [mM]aybe to distinguish partial functions from their total counterparts. Which naming scheme would you prefer?

  • headMaybe, lastMaybe, maximumByMaybe
  • maybeHead, maybeLast, maybeMaximumBy

0 voters

I’m consciously not linking vector's issue tracker so that participants here are less primed before they vote.

To be clear: the poll is not officially endorsed by the maintainers, there is no offer or promise to honor vox populi. The only purpose at the moment is to satisfy my curiosity.

I would like to ask Discourse moderators to enforce a strict no offtopic policy in this thread. Please refrain from discussions which are not directly related to the poll question.

7 Likes

One thing I asked myself was: how are existing corresponding and established [a] -> Maybe a functions named.

5 Likes

I think it’s standard for unsafe to be a prefix rather than a suffix. It would be kinda weird than to make safe/maybe a suffix.

headMaybe also looks as if it was head for Maybe. On the other hand maybeHead looks as if it was more about maybe than head.

So I’d prefer safeHead. It’s ugly, but consistency > beauty.

4 Likes

For me I think of readMaybe and readEither as counterparts, and it’s an adjustment to head/last, so having a suffix feels more apt in my head.

2 Likes

True, but there’s also a third kind of function: lookup with a default, which is both safe and allows the user to throw an error of their choice instead of the darn Prelude.error. Since such functions are always heavily inlined, I don’t see a performance issue with this approach, the only inconvenience is the user experience.

An unsafe function is then one that never errors out and instead produces an undefined behavior when used incorrectly.

I’m in favor of figuring out the correct approach to structuring a datatype API, both in the naming and the function shapes, and then adjusting the entire library to it in one major bump. If you’re going to break backwards compatiblity in the future for renaming, might as well coalesce all the changes into one big blob.

Also I have a similar complaint about laziness annotations, though it does not apply much to array libraries: splitting APIs for spine-strict data structures into a .Lazy module where functions do not force evaluations of new elements and a .Strict one where they do is terrible design. It’s confusing to users, a lot of the functions are the same in both modules, and it screws up naming for proper lazy data structures, like the actual lazy IntMap, which containers doesn’t even have.

In those libraries I would much rather prefer just one export module where each function is clear about which arguments are evaluated by the library and which are for the user to decide. Some function would need strict counterparts and that is already consistently covered by appending a ' to the name.

headMay, lastMay, maximumByMay is the pattern with the most preexisting uses. That isn’t an option in the poll, so I suppose I would go with headMaybe etc. if forced to choose, but consistency with the existing functions used in Protolude, Safe, Data.MonoTraversable, and others would be best IMO.

7 Likes

To be honest, I never liked the “May” suffix. “be” is only 2 extra characters and is just as good and more explicit. Why abbreviate half of a word?
And if the meaning is from the verb, i.e. “it may return something”, then why not “could”, or “might”?

I know I’m being nitpicky, but I’ve heard the same from other Haskellers around me about the “May” suffix.
I dunno, maybe it’s because we’re not native English speakers? :man_shrugging:

5 Likes

To augment the list of precedents, there is the module from the rio package: RIO.List. Their own choices were influenced by base's readMaybe :: String -> Maybe a and bitSizeMaybe :: a -> Maybe Int. There is also ghc's lastMaybe :: [a] -> Maybe a.

3 Likes

The headMay pattern is also used in safe, which I long considered the “standard” library comprehensively attempting to provide such functions: safe: Library of safe (exception free) functions

4 Likes

It’s not perfect, but using *Maybe as a suffix collides with names like mapMaybe which aren’t in the same family. *May has the advantage of being fairly distinctive—if a function name ends in *May, you can expect it’s a safe, Maybe-returning variant of a partial function, and not something else that happens to involve a Maybe.

If we were doing everything all over again, there might be better choices to make. But I hold that the value of a common naming convention is roughly quadratic in the number of libraries that use it, and I think that the value gained by the community if vector adopts the most common naming convention (and thus gets the greatest benefit from quadraticness) is greater than the value difference between *Maybe and *May (if that difference is even positive, given that both of them could be misleading in different ways).

5 Likes

I agree with this. On seeing this poll I immediately wanted to vote for headMay etc., only to be surprised by their absence.

2 Likes

A quantitative analysis using data from hackage-search.serokell.io, and then I’ll stop flogging this horse:

head last maximumBy
*May raw 618 uses in 190 packages 236 uses in 79 packages 30 uses in 11 packages
normalized
(by column)
66% of uses in 76% of packages 52% of uses in 67% of packages 75% of uses in 79% of packages
*Maybe raw 205 uses in 32 packages 164 uses in 27 packages 6 uses in 1 package
normalized 22% of uses in 13% of packages 36% of uses in 23% of packages 15% of uses in 7% of packages
maybe* raw 120 uses in 27 packages 54 uses in 12 packages 4 uses in 2 packages
normalized 13% of uses in 11% of packages 12% of uses in 10% of packages 10% of uses in 14% of packages

(The searches include occurrences of the relevant string in literals and documentation, and can include identifiers with these names that are not the Maybe-returning variants of a corresponding partial function—I saw a use of maybeHead as a variable holding Maybe the head of something. But as a general indicator of how popular the three naming conventions are at present across all of Hackage, I think this is usefully illustrative.)

7 Likes