Skip to content

Instantly share code, notes, and snippets.

@josephscottstevens
Forked from yang-wei/destructuring.md
Last active February 7, 2018 21:07
Show Gist options
  • Save josephscottstevens/edc10b40bfa57ef46c2ba621d1b80814 to your computer and use it in GitHub Desktop.
Save josephscottstevens/edc10b40bfa57ef46c2ba621d1b80814 to your computer and use it in GitHub Desktop.
Elm Destructuring (or Pattern Matching) cheatsheet

Should be work with 0.18

Destructuring(or pattern matching) is a way used to extract data from a data structure(tuple, list, record) that mirrors the construction. Compare to other languages, Elm comparatively supports less destructuring than other functional languages, but lets see what Elm does have!

Tuple

myTuple = ("A", "B", "C")
myNestedTuple = ("A", "B", "C", ("X", "Y", "Z"))

let
  (a,b,c) = myTuple
in 
  a ++ b ++ c
-- "ABC" : String

let
  (a,b,c,(x,y,z)) = myNestedTuple
in
  a ++ b ++ c ++ x ++ y ++ z
-- "ABCXYZ" : String

Make sure to match every tuple(no more no less) or you will get an error like:

let
  (a,b) = myTuple
in
  a ++ b
-- TYPE MISMATCH :(

In Elm community, the underscore _ is commonly used to bind to unused element.

let
  (a,b,_) = myTuple
in 
  a ++ b
-- "AB" : String

It's also more elegant to declare some constant of your app using destructuring.

-- with no destructuring
width = 200
height = 100

-- with destructuring
(width, height) = (200, 100)

Thanks to @robertjlooby, I learned that we can match exact value of comparable. This is useful when you want to explicitly renaming the variable in your branches of case .. of.

isOrdered : (String, String, String) -> String
isOrdered tuple =
 case tuple of
  ("A","B","C") as orderedTuple ->
    toString orderedTuple ++ " is an ordered tuple."
    
  (_,_,_) as unorderedTuple ->
    toString unorderedTuple ++ " is an unordered tuple."


isOrdered myTuple
-- "(\"A\",\"B\",\"C\") is an ordered tuple."

isOrdered ("B", "C", "A")
-- "(\"B\",\"C\",\"A\") is an unordered tuple."

Exact values of comparables can be used to match when destructuring (also works with String, Char, etc. and any Tuple/List/union type built up of them) - @robertjlooby

List

Comparing Lists to Tuples, Lists barely support destructuring. But Lists can be used to find the first element in a list, by utilizing the cons operator, ie ::w

myList = ["a", "b", "c"]

first list =
  case list of
    f::_ -> Just f
    [] -> Nothing

first myList
-- Just "a"

This is much more cleaner than using List.head but at the same time increases the codebase's complexity. By stacking up the :: operator, we can also use it to match the first, second or remaining values.

listDescription : List String -> String
listDescription list =
 case list of
    [] -> "Nothing here !"
    [_] -> "This list has one element"
    [a,b] -> "Wow we have 2 elements: " ++ a ++ " and " ++ b
    a::b::_ -> "A huge list !, The first 2 are: " ++ a ++ " and " ++ b

Record

myRecord = { x = 3, y = 4 }

sum record =
  let
    {x,y} = record
  in
    x + y

sum myRecord
-- 7

Or more cleaner:

sum {x,y} =
  x + y

Notice that the variable declared on the left side must match the key of record:

sum {a,b} =
  a + b

sum myRecord
-- The argument to function `sum` is causing a mismatch.

As long as our variable matches one of the keys in the record, we can ignore the other.

onlyX {x} =
  x

onlyX myRecord
-- 3 : number

I don't think Elm support destructuring in nested record (I tried) because Elm encourages sparse record

When destructuring a record, you do not have to declare all fields within the braces. Also, you can alias the whole record while destructuring it as a parameter. This is useful if you need shorthand access to some fields but also to the record as a whole.

  • nmk
myRecord = { x = 1, y = 2, z = 3}

computeSomething ({x, y} as wholeRecord) =
    -- x and y refer to the x and y fields of the passed in record
    -- wholeRecord is the complete record
    -- i.e. x and wholeRecord.x refer to the same field
    -- but z is only accessible as wholeRecord.z

Union Type

Again, thanks to @robertjlooby, we can even deconstruct the arguments of a union type.

type MyThing
  = AString String
  | AnInt Int
  | ATuple (String, Int)

unionFn : MyThing -> String
unionFn thing =
  case thing of
    AString s -> "It was a string: " ++ s
    AnInt i -> "It was an int: " ++ toString i
    ATuple (s, i) -> "It was a string and an int: " ++ s ++ " and " ++ toString i
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment