Monday, July 18, 2011

Haskell is that guy

I've been programming a lot of Haskell recently, and I've noticed something.

Haskell is that guy. You know the one who's like "pshhh I don't need side-effects" and then obsesses over side-effects. And I mean obsesses.

I've been using Template Haskell, which involves traversing and creating syntax trees, so, a very tree-like sort of task, you know that kind that functional languages are just perfectly suited for. Except for one little thing.

The Quotation Monad.

It's really a terribly minor detail. It does one tiny tiny hardly significant thing: it keeps a counter to make unique names. It's really not at all what Template Haskell or my project are about. It would be better not to think about it.

The problem is that quotation, being a monad, changes the type of expressions, and you then have to pull that type up the syntax tree using <*>so<$>many=<<annotations>>=, or use a do block and make everything flat and linear, losing all the benefit of a functional language.

Here's an example:

-- Main.hs --

1 matchLambda kw (Match pat body whrs) = 2 let 3 names = patNames pat 4 parts = bodyParts body 5 qLams = [buildLambda (names ++ bNames) <$> (expToSimpExp kw x) 6 | (bNames, x) <- parts 7 ] 8 in do 9 lams <- sequence qLams 10 whrs' <- fmap (id =<<) $ sequence $ (decToSimpDecs kw) <$> whrs 11 tup <- buildTuple lams 12 return $ letify whrs' tup

Granted it's still shorter than it would have been in Java, but it could be so much better if not for that one little counter. If you look at my code it looks like I wrote 500 lines for manipulating the Q monad and maybe a little bit here and there that deals with syntax trees -- especially since the parts that don't use the Q monad are short and clean.

Worse, the monad operators are the most visually familiar parts of the code, so someone who had no idea what the code does but knows Haskell would look at it and say "well, I don't know what this does, but I can see you're using a monad".

The problem is that the inconvenience of using a monad grows with the depth with which it is nested in the syntax tree. So

-- Main.hs --

1 f <$> a

is no trouble but

-- Main.hs --

1 f <$> (g <$> (h <$> a) <*> (pure 2)) <*> (pure 3)

is a pain, and has none of the beauty of

-- Main.hs --

1 f (g a 2) 3

Is there any design reason (other than syntactic) why monads should be harder to use when they are deeper? I do not know -- it's possible there is and I haven't seen it yet.

It turns out it's actually fairly simple to insert the <$>applicative<*> operators in the right place automatically (simple in terms of the idea -- it took a lot of code to get right). I don't have the energy to describe it right now but it's kind of obvious if you think about it -- it's probably been done before but I don't know how to search for it. Just trust that the following code works:

-- Demo.hs --

1 {-# LANGUAGE TemplateHaskell #-} 2 3 import Language.Haskell.TH.MetaLang 4 import Language.Haskell.TH.MetaLang.ApplicativeLang 5 6 main = do 7 print $ $(apl [| 8 let a = (nd [0,1]) 9 in 10 (a, a, a) 11 |])

[(0,0,0),(0,0,1),(0,1,0),(0,1,1),(1,0,0),(1,0,1),(1,1,0),(1,1,1)]

I mean no disrespect to Haskell -- it's a great language, this one problem has just been irritating me. But I may or may not have solved it now.

(It's also interesting that the Q monad shouldn't, I think, have the usual problem of the interaction between lazy evaluation and side-effects, because you don't care what order the counter gets incremented in, just that it does).

Other random things:

No comments:

Post a Comment