Showing posts with label Rants. Show all posts
Showing posts with label Rants. Show all posts

Monday, August 22, 2011

Haskell Buzzing in my Ear

Unfortunately GHC will not have Type Directed Name Resolution (TDNR) for the foreseeable future. TDNR allows you to distinguish similarly named variables based on some portion of their types.

The reason TDNR is unlikely to appear in Haskell is that it is hard to show why you need it. This is because you never actually need it. Any time you run into a problem with naming conflicts, you can always rename them. And if you complain, someone is sure to show you how they would have named their variables differently and there really is no problem.

When you need TDNR is at that moment when you hit a name conflict, and you have to interrupt your thought process to go mangle some names. And you can't show that moment to people. It's like going to StackOverflow with "I'm having trouble writing this function because there's a fly buzzing in my ear" and someone responds "Just write this this and this what's your problem?". But the problem isn't the code -- the problem is the fly buzzing in your ear.

You don't need TDNR to make your code work, you need TDNR to make you work, to save you from being interrupted by random unrelated declarations. To keep your thoughts on the puzzle at hand. Haskell takes a lot of thinking. It's that kind of language.

I've often heard it said that Haskell doesn't need TDNR because it has typeclasses. But typeclasses say the wrong thing -- semantically, they just mean something different. A typeclass says "Here are some operations on a similar pattern that can be specialized in various different ways.". TDNR says "Here are some unrelated functions that, because we are using English, just happen to have the same name." Typeclasses are about how things are similar, TDNR is about how if you're using one you just don't care about the other.

Because that's just how language works -- names take on their meaning based on the context. You couldn't have a conversation if this were not the case. Context encompasses a lot of things -- it happens that types provide a lot of context. But types aren't the only way -- I'd be happy to see Haskell add any kind of context to name resolution. (Well it has two -- modules and local scope. But these aren't enough. Haskell functions are "tiny" and you tend to have a lot at global scope).

I know of no language that fuels name collisions like Haskell. Every other language I can think of has at least one thing Haskell doesn't that helps prevent conflicts. Even OCaml lets you "open" a module locally. In any Haskell program sufficiently large, the name conflicts start building up, and you start adding more qualifying information to your function names. From the programmer's perspective that's just as bad as adding an explicit type annotation every time you call the function. Type inference makes Haskell terser but name collisions blow it back up again.

Of course, if I really wanted to be constructive, I'd learn to hack GHC and add this feature myself. You can get so close with typeclasses that I don't think it would be too hard to add -- but I've never touched the GHC source code. Some day...

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: