Skip to content

Instantly share code, notes, and snippets.

@justinmimbs
Created April 16, 2017 14:40
Show Gist options
  • Save justinmimbs/818e7918d4b48ab694daffcb20987d14 to your computer and use it in GitHub Desktop.
Save justinmimbs/818e7918d4b48ab694daffcb20987d14 to your computer and use it in GitHub Desktop.

Failure in the waiting

Null

I call it my billion-dollar mistake. It was the invention of the null reference in 1965.

—Tony Hoare

Exceptions

I consider exceptions to be no better than “goto’s” ... in that they create an abrupt jump from one point of code to another. In fact they are significantly worse than goto’s:

  1. They are invisible in the source code.
  2. They create too many possible exit points for a function.

—Joel Spolsky

Partial functions

numberWord :: Int -> String
numberWord n =
    case n of
        1 -> "one"
        2 -> "two"

Haskell (compiles, throws runtime exception):

[1 of 1] Compiling Main
Ok, modules loaded: Main.
Main> numberWord 2
"two"
Main> numberWord 4
"*** Exception: Main.hs:(3,5)-(5,18): Non-exhaustive patterns in case

Elm (doesn't compile):

-- MISSING PATTERNS --------------------------------------------------- Main.elm

This `case` does not have branches for all possibilities.

You need to account for the following values:

    <values besides 1 and 2>

The problem with failing in these ways is that they are inconspicuous (both in the source code and at runtime). Any value may turn out to be null. Any function may throw an exception, or not return a value.

A disciplined approach

Alternatively, Elm prevents failing in these ways by restricting language features and enforcing constraints.

In Elm

  • values cannot be null
  • exceptions cannot be thrown
  • functions are pure and must be total

Seek freedom and become captive of your desires. Seek discipline and find your liberty.

—Frank Herbert

Data and (pure, total) functions

We can represent possibly-failed computations with data structures and transform them with functions.

Data types

Here are a few parameterized data types from the core library.

Maybe

type Maybe a
    = Just a
    | Nothing

Result

type Result x a
    = Err x
    | Ok a

Maybe and Result are transparent types, as their value constructors are exposed, and may be used to construct values or deconstruct values (via pattern matching).

Decoder

type Decoder a

decodeValue : Decoder a -> Value -> Result String a

Task

type Task err ok

attempt : (Result x a -> msg) -> Task x a -> Cmd msg

Decoder and Task are opaque types, as their constructors are not exposed.

Functions

Here are a few functions you'll find available for many parameterized data types. They each provide a way to take a value from one type to another.

map

map : (a -> b) -> T a -> T b

(Maybe, Result, Html, Cmd, List)

mapN

map2 : (a -> b -> c) -> T a -> T b -> T c

map3 : (a -> b -> c -> d) -> T a -> T b -> T c -> T d

map4 : (a -> b -> c -> d -> e) -> T a -> T b -> T c -> T d -> T e

(Result, Decoder, List)

mapN generalizes to:

apply: T (a -> b) -> T a -> T b

(Decoder)

andThen

andThen : (a -> T b) -> T a -> T b

(Maybe, Task, List)


Data structures that provide these functions give us a uniform interface for working with failure, collections, and side-effects.

map     :   (a ->   b) -> T a -> T b
apply   : T (a ->   b) -> T a -> T b
andThen :   (a -> T b) -> T a -> T b

Consider the transformation you want to make in terms of its type signature; look for a function having that signature; use that function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment