Hacker News new | past | comments | ask | show | jobs | submit login

In Haskell your example would look like

  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.




I guess that's one of the benefits of a great type system. Thanks for the detailed explanation.


Yup. A good one isn't enough, though, it's the constant evolution that's awesome: type/data families are pretty new.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: