Zenhack.net

Papercuts: Haskell

06 Mar 2016

I’ve been playing around with Haskell a lot lately. It’s a language I’ve dipped my toes in before. I haven’t used it on large projects, but I’ve written a good handful of smaller tools in it, including some that I actively use. My backup system is written in Haskell. I still consider myself a beginner, but I’m not totally new to the language. I think I understand monads reasonably well. Monad transformers are another story.

This post is about some little things I find obnoxious about the language. They are little things; none of these are show-stoppers, and I’m more than willing to put up with them, but they’re irritating. I’ve seen languages and tools that have enough of these to make them not worth it. I’m talking about papercuts, except that unfortunately some of these can’t be fixed, for reasons of compatibility. Hopefully the article is at least interesting to folks who may go on to design languages (or other text-based formats).

Papercut 1: Unqualified Imports

Here’s a quick summary of Haskell’s import syntax. I also include Python equivalents where they exist, as they may be useful form some readers. It doesn’t cover every bit of syntax, just a few key things.

  1. import Foo.Bar makes all names exported by Foo.Bar available in the current module, unqualified. In Python, this is from foo.bar import *
  2. import qualified Foo.Bar makes all names exported by Foo.Bar visible by fully qualifying the name. Python: import foo.bar.
  3. import qualified Foo.Bar as B makes names exported by Foo.Bar available via e.g. B.baz instead of Foo.Bar.baz. Python: import foo.bar as b
  4. import Foo.Bar (baz, quux) makes the names baz and quux from the module Foo.Bar, unqualified. Python: from foo.bar import baz, quux

That’s enough for the time being. Here’s the gripe: (1) shouldn’t even exist, let alone be the default. Doing this makes the code substantially harder to read. Which of these five modules did this function come from? I avoid using it (in both languages). Unfortunately, this amplifies the pain of Papercut 2.

Papercut 2: Too Much Capitalization.

Whether the first character of an identifier is a capital letter is syntactically meaningful in haskell. The names of types, modules, and data constructors are capitalized. Variables—for both types and values —are not. That’s a lot of capitalization. Why is this a problem? It means heavy use of the shift key. My pinky is very angry with Simon Peyton Jones right now.

It also interacts badly with the qualified imports thing, because now if you want to use qualified imports, you don’t even get lower case letters with local variables. foo.bar everwhere is fine, and in my opinion a great tradeoff in terms of readability. Foo.bar is a potential medical hazard.

Papercut 3: Stupid Names

The mistakes fall into a few categories:

How many variable names can we come up with that don’t involve words?

Most languages have at least a standard set of operators: +, -, \*, /, ==, <, >= and so forth. You usually can’t define more, but haskell lets you. “Operators” are just functions, and this property is abused to no end: <*, <*>, *>, <$, $>, <$>, $, >>=, >> (no, neither of those are shifts, and no, >>= is not in any way like += and friends in other languages), ***, &&&, <||>, <|>, ||, .|., <//>, >->, :, ^., .., ., &&, /=, ++, <> … and that’s just a sample of the ones I know off the top of my head, and can actually remember what they do.

Most lisp dialects also let you include most of those symbols in identifiers, and they are typically used much more responsibly. Names like list? set!, and string->number are actually somewhat sensible. Just because a language lets you shoot yourself in the foot doesn’t mean you should.

If we have to use words, can they at least be ones nobody knows?

Monad, Monoid, Functor, Applicative, Arrow, Comonad, Semigroup (but no Group, as far as I can tell…)

Functor means something unrelated in OCaml, and something else unrelated in General Abstract Nonsense. Mappable is the correct name. Don’t get me wrong, math is freaking awesome and I love it. This doesn’t mean the literature is a good source of names.

Comonad might actually be the most sensible of these, but only once you’ve made peace with Monad.

I know we already have a name for that, but can’t we have more?

I’ll point out that if you really want an infix for fmap you could do `fmap`, and if they just called it map like they should have you could do `map`, which doesn’t even require pressing shift once. At least with NAMESINALLCAPS I could use caps lock; <$> doesn’t even give me that.

I am aware of the historical reasons for the pure/return split, and why it’s called fmap. It’s still dumb.

Papercut 4: {-# LANGUAGE StandardsWhatAreThose #-}

I find seeing the following at the top of a source file distressing:

{-# LANGUAGE CPP #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

Apparently the standard language isn’t complex enough; there are a large body of programs out there that use more than TEN different extensions to the language. TEN. For a language that prides itself on its powers of abstraction, haskell seems to be in need of core language changes fairly often. A bit of a code smell, to say the least.

I like haskell, but it has warts. All languages do. Hopefully, talking about our mistakes will help us make different ones in the future.