Haskell Hacking: a journal of Haskell programming

Haskell Hacking has moved => Go here.
2006-12-14

On syntax

Forget about what programming languages you know. What syntax do you wish you could write in? (Names have been changed to protect the innocent):

This?

    function generate_fibonacci_sequence( $length ) {
        for( $l = array(1,1), $i = 2, $x = 0; $i < $length; $i++ )
                $l[] = $l[$x++] + $l[$x];
    
        return $l;
    }

What about this?

    sub fibo
    {
        my ($n, $a, $b) = (shift, 0, 1);
        ($a, $b) = ($b, $a + $b) while $n-- > 0;
        $a;
    }

Getting better?

    def fib(n):
        if   n < 2: return 1
        else      : return fib(n - 1) + fib(n - 2)
Hmm, isn't this going backwards?
    (define fibo
     (lambda (x)
       (if (< x 2)
         x
         (+ (fibo (- x 1)) (fibo (- x 2))))))

Finally:

    fib 0 = 0
    fib 1 = 1
    fib n = fib (n-1) + fib (n-2)

If the programming languages of the future don't look like the last example, I think we've failed. Programming languages are notation for describing solutions to problems. If that notation is verbose, clunky or full of special cases it should be abandoned in favour of a more concise notation. Real languages should look like pseudcode!

My suspicion is that in 50 years time we'll look back on the syntax of today's programming languages and think them highly bizarre, like looking back at Frege's logic notation (pdf):

Syntax has come along way since the 1960s, but there's still room for much improvement, looking at the widely used languages employed today. The designers of many (not all) popular languages seem to forget that syntax should be optimised for writing by humans, not for parsing by machines.

/home :: /haskell :: permalink :: rss

Type declarations made mechanical

Haskell uses type inference, so explicit type declarations are rarely required. This:

    import Control.Monad.Fix
    fibs = fix ((1:) . scanl (+) 1)
    main = print (take 20 fibs)

Is just as good as:

    import Control.Monad.Fix

    fibs :: [Integer]
    fibs = fix ((1:) . scanl (+) 1)

    main :: IO ()
    main = print (take 20 fibs)

Running this:

    $ runhaskell A.hs
    [1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765]

Now, once programs reach a decent size, the advantage of type declarations appears: it functions as machine-checkable documentation. So people new to your code can more quickly work out what the code is doing.

Contrast this (real world) Haskell code from a network client:

    accessorMS decompose f = withMS $ \s writer ->
      let (t,k) = decompose s in f t (writer . k)

And with type annotations:

    accessorMS :: (s -> (t, t -> s))
               -> (t -> (t -> LB ()) -> LB a)
               -> ModuleT s LB a

    accessorMS decompose f = withMS $ \s writer ->
      let (t,k) = decompose s in f t (writer . k)

So at least you know have some idea of what that code does.

There's an intuition here: type declarations are good, but they can be mechanically inferred. Let's automate that then! Here's a quick script I use every day. It just passes your top level declaration to ghci, and asks it to infer the type. The resulting type signature is spliced back in to your code:

    #!/bin/sh

    # input is a top level .hs decls

    FILE=$*
    DECL=`cat`
    ID=`echo $DECL | sed 's/^\([^ ]*\).*/\1/'`
    echo ":t $ID" | ghci -v0 -cpp -fglasgow-exts -w $FILE
    echo $DECL

Save this as an executable shell file in your path. Now you can call this from your editor. For example, from vim, you'd use the following .vimrc:

    :map ty :.!typeOf %

Hitting :ty while the cursor is positioned on top of a top level declaration takes your code from:

    writePS who x = withPS who (\_ writer -> writer x)

to this:

    writePS :: forall p g. String -> Maybe p -> ModuleT (GlobalPrivate g p) LB ()
    writePS who x = withPS who (\_ writer -> writer x)

I can't emphasise enough how useful this is. So, steal this code and improve your productivity!

/home :: /haskell :: permalink :: rss

Type inference by hand

IPI asked this how #haskell:

How can I mentally infer the type of the composition of these functions?
    a :: [[Char]] -> [([Char],Int,Int)]
    b :: [Int] -> [(Int,Bool)]
    c :: [([Char],Int,Int)] -> ([Char],Int)
    d :: [(Int,Bool)] -> [[Char]]

That is, what is the type of:

    c . a . d . b

The first thing I'd do is rename the tricky types:

    a :: [String] -> [(String,Int,Int)]
    b :: [Int] -> [(Int,Bool)]
    c :: [(String,Int,Int)] -> (String,Int)
    d :: [(Int,Bool)] -> [String]

and then introduce some type synonyms:

    type A = [String]
    type B = [(String,Int,Int)]
    type C = [(Int,Bool)]

Now we can rename the complex types to a more symbolic form:

    a :: A -> B
    b :: [Int] -> C
    c :: B -> (String,Int)
    d :: C -> A

and now we can see how to combine the pieces. It's a little jigsaw puzzle!

    b :: [Int] -> C

so there's only one option to compose with b, namely

    b :: [Int] -> C
    d :: C -> A

composing these, and we hide the intermediate result:

    d . b :: [Int] -> A

now, we need something that takes an A:

    a :: A -> B

So the result of something composed with a, will be a value of type B:

    a . d . b :: [Int] -> B

and finally, something that takes a B:

    c :: B -> (String,Int)

leading to:

    c . a . d . b :: [Int] -> (String,Int)

and we're done!

/home :: /haskell :: permalink :: rss

Theory and practice: where Haskell is heading

Seen on the haskell cafe:

One of the most exciting aspects of Haskell is that pragmatic interest in the language has been growing steadily without academic interest in it declining in any way. As a result, we have a language that represents an interesting mixture of good and useful, although it is not entirely clear yet how long this nice balance will hold.
We have had lots of languages that were intended to be well-designed (good, beautiful, ..), but never much used in practice, and we have also had lots of languages that were intended to be pragmatic (practical, useful, ..), without much interest in theoretical beauty. But how many languages are there where the two aspects have converged, with both communities still actively interested in the result?

Its exciting working in Haskell right now, with new practical tools, libraries and jobs appearing daily, and yet at the same time the theory guys keep cranking out new abstractions for us: applicative functors, software transactional memory, stream fusion...

We have the technology!

/home :: /haskell :: permalink :: rss

About

Real World Haskell

RWH Book Cover

Archives

Recommended

Blog Roll

Syndicate