Some thoughts of Haskell newbie going from Scala

I don’t earn by writing Scala, but I did quite a fair of Scala when working on my Minecraft mod and my master’s thesis. I am very interested in functional approaches, so for a long time I wanted to learn Haskell. I bought a book “Learn You a Haskell for Great Good” and started reading and testing examples. In this short article I point out some things I found to be odd or not that good as in other languages I encountered. Don’t read this as a bashing of Haskell, I love the language (the type inference is so strong), but no language (not even my favourite Scala) is without faults and imperfections.

Unfamiliar operators

I already knew some basics from school. But I find some things a bit strange - things like function composition . - why it has a reversed flow of an execution? Why aren’t more used packages like Flow with its right functional composition .> which clearly states direction and is in my opinion more intuitive (same flow direction as in reading text and guiding character >) and instead one should use . or >>> from Control.Arrow when writing The Only Right Haskell™.

I love pipe operator (forward function application) from ScalaZ - |>. I was mildly disappointed that Haskell needs an import before one can start using it and also the fact that & isn’t as readable as |>. I know Haskellers hate the Flow package, but I love its simplicity and consistency. If you are not a Haskeller, just look at the table (if you are, you probably already dislike the Flow library):

Flow Base
x |> f x & f
f <| x f $ x
apply x f f x
f .> g f >>> g
g <. f g . f
compose f g x g (f x)
x !> f -
f <! x f $! x
apply’ x f seq x (f x)

I just really like how the pipes (applications) and compositions look. If you can’t picture it have a few examples.

Composition
1
2
3
4
5
6
7
8
9
10
11
12
13
Haskell> -- Composition and the inverted flow <---
Haskell> sum . filter (> 25) . map (* 5) $ [1..10]
200

Haskell> -- Composition and more natural flow ---> in "Right" Haskell
Haskell> import Control.Arrow ((>>>))
Haskell> map (* 5) >>> filter (> 25) >>> sum $ [1..10]
200

Haskell> -- Composition, natural flow ---> and nice symbol using "bad" Haskell library
Haskell> import Flow ((.>))
Haskell> map (* 5) .> filter (> 25) .> sum $ [1..10]
200

 

Application
1
2
3
4
5
6
7
8
9
10
11
12
13
Haskell> -- Piping in unnatural direction <---
Haskell> sum $ filter (> 25) $ map (* 5) $ [1..10]
200

Haskell> -- More natural flow --->, but not so intuitive operator
Haskell> import Control.Lens.Operators ((&))
Haskell> [1..10] & map (* 5) & filter (> 25) & sum
200

Haskell> -- Natural flow + intuitive operator
Haskell> import Flow ((|>))
Haskell> [1..10] |> map (* 5) |> filter (> 25) |> sum
200

What happened to flatten?

Until I tried Python, started developing in JavaScript and toyed with Haskell I really didn’t know how great the Scala’s collection API actually is.

Scala utilizes so called fluent interface in its collections API. I very like how concise it is.

Scala collections
1
2
scala> (1 to 10).map(_ * 5).filter(_ > 25).map(x => List(x, x + 3)).flatten.sorted
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(30, 33, 35, 38, 40, 43, 45, 48, 50, 53)

Now the shock - Haskell doesn’t have flatten function? The flatten takes a list of lists and makes it one level more flat - e.g. from [1, [2, 3], [[4]]] it creates [1, 2, 3, [4]]. I found some intricate solutions on StackOverflow, but it turns out the function I was looking for is named concat. If you ask me it is not a best name. Let us see the Scala example turned into idiomatic Haskell.

1
2
3
Haskell> import Data.List
Haskell> sort . concat . map (\x -> [x, x + 3]) . filter (> 25) . map (* 5) $ [1..10]
[30,33,35,38,40,43,45,48,50,53]

I still dislike the inverted flow of execution/reading, but to my best knowledge this is the recommended way. Also note that I am required to import a package to actually do the sorting, a bit unexpected (Scala runs fine without any import whatsoever).

Those with some FP experience probably already spotted a possible improvement - using Scala one can replace map + flatten with flatMap. Let’s do it.

Scala collections
1
2
scala> (1 to 10).map(_ * 5).filter(_ > 25).flatMap(x => List(x, x + 3)).sorted
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(30, 33, 35, 38, 40, 43, 45, 48, 50, 53)

I will show how solutions in JavaScript and Python looks like. JavaScript version uses Lodash library. I am actually not sure if JavaScript contains all functions necessary for this example if one would desire to rewrite it to Vanilla JavaScript (are there functions like range or flatMap?).

1
2
3
> _ = require('lodash');
> _(_.range(1, 10)).map(x => x * 5).filter(x => x > 25).flatMap(x => [x, x + 3]).sort().value()
[ 30, 33, 35, 38, 40, 43, 45, 48 ]

I have to note that FP version of Lodash might look a bit better, but I don’t think it would be much more concise - I don’t see a way how to shorten those lambdas.

Alright, JavaScript version is not amazing - has some repetition and is not as concise as Scala or Haskell, but overall quite good.

Let’s do the Python version. Oh, Python entirely lacks flatMap, you have to define it on your own. Just from this fact one can quite safely deduce that Python is very weak in the FP land. I am no Python guru, just occasional (usually involuntary) user.

1
2
3
4
5
6
>>> from itertools import chain
>>> def flatmap(f, items):
... return chain.from_iterable(map(f, items))
...
>>> list(sorted(flatmap(lambda x: [x, x + 3], filter(lambda x: x > 25, map(lambda x: x * 5, range(1,11))))))
[30, 33, 35, 38, 40, 43, 45, 48, 50, 53]

What a mess… Even if there is some wonderful Python FP library the loong lambda notation is just awful.

What about Haskell? Well, it took me some time to stumble upon operator >>=. Is it just me, or names in Haskell are not that great? By the way it is called bind and according to StackOverflow it is a bit weaker than Scala’s flatMap.

1
2
Haskell> sort . (>>= (\x -> [x, x + 3])) . filter (> 25) . map (* 5) $ [1..10]
[30,33,35,38,40,43,45,48,50,53]

For comparison I will rewrite it to be more Scalish and Flowish.

1
2
3
4
Haskell> let flatMap = flip (>>=)
Haskell> import Flow
Haskell> [1..10] |> map (* 5) |> filter (> 25) |> flatMap (\x -> [x, x + 3]) |> sort
[30,33,35,38,40,43,45,48,50,53]

It is a bit longer than the idiomatic version, but I like it way more - it just has a nice flow.