Haskell

Aus Franky
Wechseln zu: Navigation, Suche

Reading Coders at Work inspired me to play with the programming language Haskell. I write here about my first impressions. In case you do not want to know about my squabble with function composition, jump to Subpages to see later experiments.

A general thing about new languages

First of all, it is a pure functional language and thus different to everything I have done before. So When actually experimenting, I found out that I have to start small. This is different from my first steps with C# - I got paid for my first C#-program and programming C# is really easy because I am fluent in Java. With Haskell, big things don't work.

It is a little bit like the Italian language for me: I can 'read' an Italian newspaper even though I never learned Italian. I had Latin in school, and that gives enough vocabulary to understand what is happening. But that does not enable me top speak or write Italian.

Reading some Haskell Code is just like that: I get the big picture, but I don't know the details.

Function composition

Most of my Java or C# code is Maps, Lists and Iterations over them. I often find myself commenting: this is just a functional map because Java does not support it.[1] Eager to use function composition, I stumbled over a problem: I needed a function with signature

a -> [a] -> a

and should just prepend the first argument to the list and then run a sum over it. In other words, I wanted

f a b = sum (a:b)

as an anonymous function. However, this is just a function composition of sum and (:). But

f = sum . (:)

does not work. While

sum ((:) 1 [2])

returns the expected result,

(sum . (:)) 1 [2]

gives one of those hard-to-understand error messages. Even more mysteriously,

sum $ (:) 1 [2]

works while

(sum $ (:)) 1 [2]

does not. I tried numerous other combinations, to no success.

The terms haskell and arity finally got me this result [1], from which I deduced the correct form is

f = (sum .) . (:)

Tough to understand why the shorter version does not work.

With my limited knowledge, that's why I think sum . (:) is wrong: Some type signatures:

(.) :: (b -> c) -> (a -> b) -> a -> c
(:) :: d -> [d] -> [d]
sum :: (Num e) => [e] -> e

Now when I compose sum and (:) I see that the type system will encounter some inconsistency, in other words, what is the type of

sum . (:)

?

b   should match   [e]
c   should match    e
a   must now be   d -> [d]

which, oh my head is exploding, makes the entire composition something like

([e] -> e) -> ((d -> [d]) -> [e]) -> ((d -> [d]) -> e)

but

 a -> (b -> c) = a -> b -> c

is fundamentally different from

(a -> b) -> c

Starker Tobak, I'd say in German. translation Too late to further grok this.

Later revisit

The following two are equivalent:

f = (sum .) . (:)
f = (.) sum . (:)

To understand the latter, one has to work out the precedences and grouping. The last term is equivalent to

f = ((.) sum) . (:)

Still, this makes my head explode. I understand it, but only while concentrating hard. As soon as I relieve my concentration, it is gone.

Subpages

  1. Of course I can write functional (with higher order functions) in Java, but this produces lots of boilerplate code. Many one-method-interfaces and even more anonymous instances of those. Each anonymous instance takes 5 lines minimum and visually separates what should be short and concise. I can program OO on a Turing machine...