Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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