Wednesday, 11 February 2015

FTP dangers

I am concerned about the Foldable Traverable Proposal (FTP) (https://ghc.haskell.org/trac/ghc/wiki/Prelude710) :

My first and biggest concern is simply that it's harder to read code which uses highly polymorphic functions unnecessarily.

You can see that even by considering plain-old fmap vs map: it's harder to read "fmap f . fmap g" than "map f . map g" because with the former you're having to manually search for more information about what Functor is being used in each case.

My second concern is that the more you overload, the more you risk having something unexpected happen:

Say we have two variables:
*Borders.Base.Utils> let a' = ["alice", "bob"]
*Borders.Base.Utils> let a  = (True, a')

we want to count the characters so we type:
*Borders.Base.Utils> length $ concat a

..but we've accidentally forgotten the prime character...

... right now we get:
:6:17:
    Couldn't match expected type ‘[[a0]]’
                with actual type ‘(Bool, [[Char]])’
    In the first argument of ‘concat’, namely ‘a’
    In the second argument of ‘($)’, namely ‘concat a’

so we fix it and get our desired result:

*Borders.Base.Utils> length $ concat a'
8

...but under the FTP proposals (where concat would become Data.Foldable.concat) we get:

*Borders.Base.Utils> length $ Data.Foldable.concat a
2

(because pairs are Foldable in their second argument).

This cannot be a good thing.

I believe that the more generalised functions of FTP should be opt-in (i.e. you should - as at present - need to import them explicitly).

9 comments:

shum said...

Hah, I just asked in #haskell-ftp what is wrong with specialized functions. Here is my preferred example of overgeneralizing: http://lpaste.net/120323

Erik said...

So do we add specialized functions for all types then? Or is it ok to confuse pairs with Eithers and Maybes, but not with lists?

shum said...

Erik, so we remove specialized functions because we can't add them for all types?

(And yes, we should add specialized functions for Either and Maybe IMO)

Erik said...

Yes. You can always specialize generalized functions if you want. I'd hate to see specialized functions for all types. The naming alone seems horrible. Also, why Either and Maybe, but not pairs? Functions? IO? It's very confusing when I accidentally fmap over IO instead of the list returned from that action.

Realistically, this is either going to be very limited and ad-hoc, or cause an explosion of functions. Just look at the amount of functions with Functor, Applicative, Monad, Foldable or Traversable constraints in the base package, and multiply that by the number of base types like lists, Maybe, Either, pairs, triples, functions, IO, ST etc.

shum said...

And pairs, and functions, and IO. And the functions can have the same name. If qualified import is too heavy, them lets improve module system.

We have a lot of specialized functions for Map, List, Seq etc, and that works well. If you want to abstract over specific container, then use generalized function explicitly. Using generalized functions in monomorphic context may lead to errors.

Erik said...

I don't agree that the qualified imports work well. To me, for these generic functions, they feel like Java or .NET where everything has to be typed explicitly. But that's a matter of personal taste, I guess.

I haven't run into any errors related to polymorphic functions in a monomorphic context, even though we use a polymorphic prelude (even (.) and id are generalized). I do agree that it can take longer to read the code occasionally. IDEs/editors could help here, by showing the type of subexpressions.

Tony Morris said...

My favourite part was the "concern that the code is more unreadable" lists code that is objectively more readable by the virtues of parametricity.

These objections are becoming somewhat hilarious, ignoring the tragedy.

Jonathan Merritt said...

Couldn't you make an identical argument regarding any abstraction over types? If I have a list of String and a list of Double, might I not get confused because the length function works on both of them?

Ben Moseley said...

hackage has prelude-compat : https://hackage.haskell.org/package/prelude-compat