Appendix A: Syntax Cheat Sheet
A one-stop reference for the syntax used throughout the book. Everything here is real Mar.
Values and functions
answer = 42 -- a definition (not a variable; it never changes)
double : Int -> Int -- type annotation (optional, recommended on top level)
double n = n * 2
add : Int -> Int -> Int -- two arguments
add a b = a + b
half = \n -> n // 2 -- lambda (anonymous function)
result = users |> List.filter isActive |> List.map score -- pipeline: x |> f == f x
Strings concatenate with ++. Comparison: ==, /=, <, >, <=, >=. Logic: &&, ||.
Numbers
Two number types, both exact: Int (whole) and Decimal (base-10, written 19.99). + - * work on either (never mixed; convert with Decimal.fromInt).
n = 7 // 2 -- integer division, truncates (3); n // 0 == 0
-- no modulo operator; idiom: n - (n // m) * m
third = 1.0 / 3.0 |> Decimal.rounded Decimal.HalfEven 4 -- 0.3333
exact = Decimal.exact (1.0 / 4.0) -- Just 0.25 (Nothing if it repeats)
split = Decimal.withRemainder 2 (100.00 / Decimal.fromInt 3)
-- { quotient = 33.33, remainder = 0.01 }
/ works only on Decimals and returns a Decimal.Division (the unresolved exact quotient); the three resolvers above are the only exits, so every rounding names its mode (Decimal.HalfEven, HalfUp, Down, Up, Floor, Ceiling) and scale at the call site. Handy conversions: Decimal.toString, Decimal.fromString, Decimal.toScale, Decimal.round / floor / ceiling / truncate (to Int), Decimal.fromCents / toCents.
Records
type alias User =
{ id : Int
, name : String
}
u = { id = 1, name = "Ana" } -- build
u.name -- read
{ u | name = "Bia" } -- "update": a new record differing in one field
Unions and pattern matching
type Status
= Open
| Assigned User
| Closed
describe : Status -> String
describe status =
case status of
Open -> "open"
Assigned user -> "assigned to " ++ user.name
Closed -> "closed"
case must cover every constructor. _ is the wildcard pattern. Records destructure in patterns too.
Maybe and Result
case Repo.findById users id of
Just user -> greet user
Nothing -> showNotFound
case outcome of
Ok value -> use value
Err why -> explain why
Conditionals and locals
size = if n > 100 then "big" else "small" -- if is an expression; else is mandatory
area =
let
w = x2 - x1
h = y2 - y1
in
w * h
Lists
xs = [ 1, 2, 3 ]
ys = 0 :: xs -- prepend
names = List.map (\u -> u.name) users
adults = List.filter (\u -> u.age >= 18) users
total = List.foldl (\acc n -> acc + n) 0 xs -- NOTE: the accumulator comes FIRST
Mar’s List.foldl passes the accumulator as the lambda’s first argument (\acc item -> ...). If you arrive from Elm, this is flipped from what your fingers expect.
Modules
module Frontend.Home exposing (page)
import UI exposing (list, section, title, button)
import Frontend.Routes
File path mirrors module name (Frontend/Home.mar is module Frontend.Home). Only what a module exposings is visible. No import cycles.
The MVU page skeleton
type alias Model = { count : Int }
type Msg
= Increment
init : (Model, Cmd Msg)
init = ( { count = 0 }, Cmd.none )
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Increment -> ( { model | count = model.count + 1 }, Cmd.none )
view : Model -> View Msg
view model = ...
page : Page
page =
Page.create
{ path = "/", title = "Counter"
, init = init, update = update, view = view
, subscriptions = always Sub.none
}
Effects, both sides
-- Frontend: commands
Service.call Shared.addTask { name = draft } Added
Cmd.perform GotNow Time.now
Cmd.batch [ cmdA, cmdB ]
Nav.pushTo Frontend.Routes.home
-- Frontend: subscriptions
Time.every (Time.millis 16) Tick
Keyboard.onKeyDown KeyDown
-- Backend: tasks
let
row <- Repo.findById tasks id -- bind: run, name the result, continue
in
...
Task.succeed value
Task.fail "broken invariant"
Repo.all tasks |> Task.map List.length
The fullstack triangle
-- Shared.mar: declare once
addTask : Service { name : String } AddTaskOutcome
addTask = Service.declare POST "/tasks"
-- Backend: implement
services = [ Auth.protect Shared.addTask addTaskImpl ]
-- Frontend: call
Service.call Shared.addTask { name = model.draft } Added