Want to see the full-length video right now for free?
Compilers are powerful helpers but they can only do so much with primitives. Joël and Stephanie fix a bug by introducing domain-specific types. Learn about how these encode real-world context, what are the downsides, and how some functional programming concepts can make those downsides go away.
Previously discussed, don't depend too heavily on low-level constructs.
Two Ints can bean different things and easily be confused for each other
type alias User =
{ age : Int
, salary : Int
, balance : Int
}
payday : User -> User
payday user =
{ user | balance = pay user.age user.salary }
pay : Int -> Int -> Int
pay salary age =
salary + (age * 10)
type Dollar = Dollar Int
type alias User =
{ age : Int
, salary : Dollar
, balance : Dollar
}
payday : User -> User
payday user =
{ user | balance = pay user.age user.salary }
pay : Dollar -> Int -> Int
pay (Dollar salary) age =
salary + (age * 10)
gives error
The 1st argument to function pay is causing a mismatch.
Function pay is expecting the 1st argument to be:
Dollar
But it is:
Int
Wrapping, doing thing, and re-wrapping usually is the sign of a map
function
module Dollar exposing(Dollar, fromInt, map2)
type Dollar = Dollar Int
fromInt : Int -> Dollar
fromInt =
Dollar
map2 : (Int -> Int -> Int) -> Dollar -> Dollar -> Dollar
map2 f (Dollar d1) (Dollar d2) =
Dollar <| f d1 d2
payday : User -> User
payday user =
{ user | balance = Dollar.map2 (+) user.balance (pay user.age user.salary) }
With map
and map2
, we can implement most custom operations
plus : Dollar -> Dollar -> Dollar
plus =
map2 (+)
minus : Dollar -> Dollar -> Dollar
minus =
map2 (-)
times : Int -> Dollar -> Dollar
times n =
map ((*) n)
divideBy : Int -> Dollar -> Dollar
divideBy n =
map (\d -> d / n)
square : Dollar -> Dollar
square =
map (\d -> d ^ 2)
Some articles: * Avoiding primitives in Elm * Algebraic blindness
Ideas described here implemented as a package.