x = do
ints <- grabSomeIntegers
return (ints & map (*2) & filter (> 3) & map (-1))
(where x & f = f x) which the compiler desugars into something like x = fmap (map (- 1) . filter (> 3) . map (* 2)) grabSomeIntegers
Now the compiler can very well perform optimizations on the big composed function. Even then, as you say, this has its limits: polymorphic/DRY code still suffers a performance penalty because it has to work the same way with all allowed inputs.... right? Nope, that doesn't have to be the case: Haskell supports more than just your $grandparent's parameterized types!
The vector package uses something called data families to have different in-memory representations for vectors of different types, without "letting go of the DRY" at all.
https://www.stackage.org/haddock/lts-8.15/vector-0.11.0.0/sr...
Here, Vector a is a data family: how a value of type Vector a "looks" in memory depends on a. So you'll notice a bunch of lines of the form
newtype instance Vector Int16 = MV_Int16 (P.Vector Int16)
What this means is "if we have a vector of values of type Int16, use a primitive vector (essentially a raw array) to represent it in memory". (P is an alias for the Data.Vector.Primitive module.) Now I can write code that uses the beautiful user-facing API of vector:https://www.stackage.org/haddock/lts-8.16/vector-0.11.0.0/Da...
Written using vectors, code like in your example is perfectly DRY; it would look like (this is untested code!) this:
combobulate :: Num a => IO (Vector a) -> IO (Vector a)
combobulate = do
as <- grabSomeIntegersFromNetwork
return (ints & V.map (*2) & V.filter (> 3) & V.map (-1))
and under the hood, whenever I use a Vector Animal[0], it uses whatever fast general-purpose array implementation is available, but the moment I call this with a vector of Int16 or Bool or Complex Double, it switches to the fast implementations in the Primitive/Internal modules.The key is the data families: the types expose a simple, consistent API, hiding the actual guts of the memory representation from the user. The library author is free to perform whatever ugly/evil/unsafe manipulations she feels like behind the scenes on the internal representations, or not.
[0] Defining numeric operations on animals is left as an exercise to the careful reader.