The Fridge-brilliance of Maybe

I’ve been tinkering with Elm recently. It’s a typed functional programming language that reminds me of the Haskell I wrote during my PhD. And, it’s great fun. I wrote a toy application, and recently upgraded it to 0.17. Along the way I had an insight about the Maybe type which I try and to explain in context.

My generic text input field needs to be able to respond to the Enter and Escape keys by triggering Latch and Reset messages respectively.

My onKeyDown function generates an on-keydown handler attribute that maps Int key codes to messages.

onKeyDown : (Int -> msg) -> Attribute msg
onKeyDown mapping =
    on "keydown" <| Json.map mapping keyCode

This excerpt from my view function shows how I use it; I define enter and escape as constant functions, Latch and Reset are constructors from my Msg type:

view model =
    ...
    div []
	[ input
	    [ type' "text"
	    , highlightStyle
	    , placeholder model.name
	    , value model.input
	    , autofocus True
	    , name model.name
	    , onInput UpdateInput
	    , onKeyDown
		&lt;| keyMsg
		[ ( enter, Latch )
		, ( escape, Reset )
		]
	    ]
	    []
	]

The helper-function keyMsg takes a list of pairs (in lieu of literal dict syntax) from which it builds a dict of key codes and the corresponding messages

keyMsg : List ( Int, Msg ) -> Int -> Msg
keyMsg mapping keycode =
    Dict.fromList mapping
        |> Dict.get keycode
        |> Maybe.withDefault NoOp

Now we get to the interesting part. While writing this I wondered how to return the default value NoOp if the key code isn’t in the dictionary. In python, dict.get(key [, default]) lets you specify the default, so I started looking in the dict module. But it isn’t there. It’s in Maybe.

At which point I saw the Fridge Brilliance of Maybe. Putting the default behaviour inside dict.get means it has to be duplicated in any other data structures that require default behaviour. Making it part of Maybe demonstrates why Maybe so much more than just an abstraction of pointers that might be null. It is the right place to define default-value behaviour.

Edit!

If I bring the names into scope, keyMsg function shortens nicely like this:

keyMsg mapping keycode =
    Dict.fromList mapping |> get keycode |> withDefault NoOp

Whoa! Even more brilliance with the ? operator!

keyMsg mapping keycode =
    Dict.fromList mapping |> get keycode ? NoOp