Anatomy of Maybe — Part I — Introduction: Understanding Functional Programming Thru Optionality
Yes Maybe!

Functional programming (FP) is totally vogue right now (and has been for a couple years)—as is functional reactive programming (FRP). I’m hearing from even heavy object-oriented (OO) teams holding the old guard embracing functional patterns in their code—like having less state, referential transparency, managed IO. Code reviews are talking about using higher-order functions and libraries, like Microsoft™’s LINQ and Java 8 streams. And the Swift community seems to really be behind Optionals.

This blog series is going to be talking about novice FP topics thru the lens (no relation) of optionality (and sometimes errorability). The example I’m going to show will be in Elm (if you need a guide on syntax) & ECMAScript2015—with & without Ramda (a utility library similar to Lodash, but curried and with the collection as the last argument like it is in most FP languages), but always with Ramda Fantasy.

The prerequisite ideas to know about are function composition, currying, and the basics on how to read Hindley–Milner type system annotation as seen in Haskell, PureScript, Elm, etc. I’ve already given a talk whose slides you can check out on these ideas in the past. Totally do research them if you’re not familiar.

Optionality embodies the solution the “billion dollar mistake”, the fact that a null reference can get into your code base and wreak havoc. From a FP standpoint, it also covers algebraic data types (ADTs) and some fun type class instances like Monoid, Ord, Functor, Applicative, Monad, Alternative, and Traversable. …It’s also one of the simplest examples of each.

Optionality goes by a lot of names like:

  • Maybe (Agda, Elm, Haskell, Idris, PureScript)

  • Option (Coq, F#, Java 8, OCaml, Rust, Scala, Standard ML)

  • Nullable (Julia)

  • T? (C++17, Kotlin, Swift)

But what does it mean? Let’s look at the definition in Elm:

type Maybe a
    = Just a
    | Nothing

A Maybe is a context, like a hand

Maybe is a context, like a fist
I seriously freehanded this

And like a hand, it can be holding something or nothing. That something be anything—but in this case it is a sphere, its material is up to your own imagination.

Just a thing or Nothing at all
I like to think it’s stress ball to tolerate your OO codebase at work

I guess technically things can be holding the hand instead of the other way around :3

Holding hands :3
Have a friend? I’m available…

But it’s always within the context of the hand

Rise Up Comrades

Maybe Nothing ≠ null,title=

Nothing ≠ null

It may seem like Nothing and null are the same, but with Nothing we’re required to deal with due to that Maybe context (the hand holding or not holding our value). In a typed language, we can also see by the type signature that we’re expecting a missing, or optional, value. Until recently, most of these OO languages when you said function int foo(int x, int y), x and y can be an int but they can also be null, and foo can return an int but could also be null; this problem propogates thru the whole app. This is not a guarantee—this is a language type system mistake. Unlike Maybe, Int | null is without context, it has no helper methods, and where you usually see that sort of context-less behavior requires explicit null checks that can easily be forgotten about (and are often adding noise visual noise).

In a production application runtime errors are, to borrow the basic phrase, literally the worst. They can be seen as a Java NullPointer Exception on a kiosk or a web app going completely unresponsive—often times accruing a lot of wasted developer time of confusion (what does undefined is not a function even mean?), or worse, an end user ditching your site or application and making loud, negatively-sentimented public social media posts since it “doesn’t work”.

Broadly, the point of the optional value is to then pass it around your app and map, apply, chain, and use other higher-order functions to continually transform the value inside of its container/box that is the Maybe. Only at IO or display time do you let a value fall out or handle an error. In later parts, we will talk more on the kinds of transformations we can do.

Maybe is an ADT between Just a and Nothing. a is a generic—it can be literally any value that you define it to be. How do we make a Maybe?

m : Maybe Int
m = Just 1

n : Maybe Int
n = Nothing

o : Maybe (List Char)
o = Just [ 'w', 't', 'f' ]

p : Maybe (List Char)
p = Nothing
import Maybe from "ramda-fantasy"

const m = Maybe.Just(1)

const n = Maybe.Nothing()

const o = Maybe.Just(['w', 't', 'f'])

const p = Maybe.Nothing()

const q = Maybe.of(['w', 't', 'f'])  // Maybe.prototype.of = Maybe.Just

With Ramda Fantasy, we can turn things into a Maybe using the Maybe constructor:

const q = Maybe("The Two of Us")
console.log(q, q.toString())
//=> Object { value: "The Two of Us" } Maybe.Just("The Two of Us")

const s = Maybe(null)
console.log(s, s.toString())
//=> Object {  } Maybe.Nothing()

That ability gives us safety. Let’s look at the function head which gets the head of a collection. In Elm’s core List: head : List a → Maybe a. Sometimes this is called “safe head” since it returns a Maybe rather than a | null | undefined.

List.head [1, 2, 3]
--=> Just 1

List.head []
--=> Nothing

So to implement this in JavaScript for arrays/strings

import { Maybe } from "ramda-fantasy"

//=> undefined

//=> Array [0]
// Weird, actually didn’t know this
// Edit: so maybe it’s working as intended:

// `appendable` is a ‘special’ thing in Elm which is essentially a semigroup
//∷ List appendable -> Maybe appendable
const safeHead = (xs) =>
	Maybe(typeof xs.length === "number" && xs.length > 0 ? xs[0] : null)

safeHead([1, 2, 3])
//=> Object { value: 1 }

//=> Object {  }

//=> Object { value: 'S' }

safeHead({foo: true})
//=> Object {  }

We can see that our new head function will return us Nothing if it’s empty. In JavaScript we don’t have types really, so we’ll go off of the length property and attempt to access the first. Anything else randomly thrown thru the function will also result in Nothing.

But I know my lists aren’t empty in my case! I don’t want to have to handle this Maybe from calling head.

— A guy that doesn’t want to have to handle a Maybe from calling head

Do you tho? I mean by definition lists can be empty. Maybe you should model your data better for that reason. :) An easy option: a non-empty list (you can use mgold’s List.Nonempty in Elm). Here is a super simplified demonstration:

type Nonempty a =
    Nonempty a (List a)

-- flipping Nonempty contstructor and applying the empty
-- list, [], first so we can use currying to our advantage
-- to be point-free
singleton : a -> Nonempty a
singleton =
    flip Nonempty []

-- Match out only the head and wildcarding the list so the
-- compiler and our team know the list isn’t being used
head : Nonempty a -> a
head (Nonempty x _) =

-- Just `cons` the head and tail into a List
toList : Nonempty a -> List a
toList (Nonempty x xs) =
    x :: xs

-- We can’t be guaranteed to get a Nonempty from a List
fromList : List a -> Maybe (Nonempty a)
fromList xs =
    case xs of
        -- See right here it can fail
        [] ->
        -- Pattern match out the head & tail of the list
        head :: tail ->
            Just <| Nonempty head tail

-- Just to demonstrate that this API can be built out
map : (a -> b) -> Nonempty a -> Nonempty b
map f (Nonempty x xs) =
    Nonempty (f x) <| f xs

-- -- --

-- First we’ll look how Nonempty lists look as Lists
toList <| singleton 1
--=> [ 1 ]

toList <| Nonempty 1 [ 2, 3 ]
--=> [ 1, 2, 3 ]

-- Then look at how at how head always returns a value
head <| singleton 1
--=> 1

head <| Nonempty 1 [ 2, 3 ]
--=> 1

-- Lastly look at how lists can can fail to create a Nonempty
fromList <| [ 1, 2 ]
--=> Just (Nonempty 1 [ 2 ])

fromList <| []
--=> Nothing

With this model, we now made it impossible to make an empty list since our ADT demands a head with the first a in Nonempty a (List a). The second parameter can be empty since regular lists can be empty by definition. Also of note in that we can’t guarantee that we can construct a Nonempty from a list since… I mean how do you make a non-empty list out of an empty list? This is literally the perfect time to return a maybe as you can see how it return a Maybe (Nonempty a) which is Nothing if the list passed in is [].

Trying to model this Nonempty in JavaScript is outside the scope of this article, but hopefully you can start to see how creating and using ADTs like Maybe, Nonempty, and Either is very powerful—and trivial in a language language Elm.

But How Do We Get a Value Out?

What good is this structure if we cannot use the value inside?

Maybe.Extra as Maybe exposing ((?))

Maybe.withDefault 66 <| Just 5
--=> 5

Maybe.withDefault 66 <| Nothing
--=> 66

Just 5 ? 88
--=> 5

Nothing ? 88
--=> 88

What these functions are doing is providing a default value in the case where the Maybe is a Nothing (withDefault : a → Maybe a → a). The ? infix can really help clean up some crufty code. There is also composed solutions like Maybe.Extra.unwrap and Maybe.Extra.unpack.

import { Maybe } from "ramda-fantasy"

//=> 5

//=> 66

// Could store invoker(1, "getOrElse") as a variable
R.invoker(1, "getOrElse")(77)(Maybe.Just(5))
//=> 5

R.invoker(1, "getOrElse")(77)(Maybe.Nothing())
//=> 77

Errors go Left, Successes go Right
Neither road is less traveled

Error Handling with Either

Similar to a Maybe is the sum type Either type (in Elm there is even a specific type for this called Result), which has a lot of same properties. This is what those both look like:

type Either a b
    = Left a
    | Right b

type Result e a
    = Err e
    | Ok a

This looks a bit like the Maybe, but instead of one type, a in Just a, we can hold two generics, Left a and Right b. So in the case of head on a List, it didn’t fail per se; however some things do have errors. An Either lets us diverge our code into a failure and success path—sometimes called Railway-Oriented Programming. As we attempt failable things, we can move the value’s path to failure.

What’s something that could fail? Let’s look at the signature of String.toInt which parses an string into an integer.

toInt : String -> Result String Int

So in Elm, to parse we’d do:

String.toInt "99"
--=> Ok 99

String.toInt "9.9"
--=> Ok 9

String.toInt "Gary Busey"
--=> Err "could not convert string 'Gary Busey' to an Int"

String.toInt "NaN"
--=> Err "could not convert string 'NaN' to an Int"

So can we build this in JS with Either (where Ok == Right and Err == Left)?

import { Either } from "ramda-fantasy"

//∷ String → Either String ℤ
const toInt = (x) => {
	// y ∷ ℤ | NaN
	const y = parseInt(x, 10)

	// if it’s NaN, then it’s not a number, and it’ll
	// be tagged with a Left
	// NOTE: in JS, `typeof NaN === "number` is true
	return isNaN(y)
		? Either.Left(`could not convert “${x}” to an Int`)
		: Either.Right(y)

So to test:

//=> "Either.Right(99)"

//=> "Either.Right(9)"

toInt("Gary Busey").toString()
//=> "Either.Left("could not convert “Gary Busey” to an Int")"

//=> "Either.Left("could not convert “NaN” to an Int")"

//=> "Either.Left("could not convert “NaN” to an Int")"

toInt({foo: 1}).toString()
//=> "Either.Left("could not convert “[object Object]” to an Int")"

So now we have a failure message that we can hold onto and use later. And we can now map both the the Left and Right separately and continue along the pipeline. You can image this being great for like the return of a non-20x fetch call. At the view layer, you could pass all Left failures to a failure modal, whereas the Right would be displayed as normal. There is a lot more you can do with this, but it is pretty ubquitous to see something Either-like to handle errors in typed functional programming languages. Shameless plug: I wrote Elm’s Either library.

If you are looking for a fun place to try Elm or Ramda + Ramda Fantasy (+ Sactuary (which has a safe head)), go to Ellie and/or Ramtuary REPL.

In Part II, we’ll be looking to explore the Functor type class and how to map data… particular inside our Maybe to turn a Just 1 into a Just 2 so that we can hold onto that optional value for as long as possible.