Side-by-side, interactive cheatsheets for Elm programmers
comparing Elm to other languages. Every example runs live in your browser — no
setup, no installation.
Choose your own path by reordering languages
The language of the web, JavaScript is unavoidable, powerful, and capable of running almost anywhere.
this binding: a call-site-determined design that arrow functions exist to work aroundThe ML dialect your pipe operator came from. F# is where Elm's |> was invented — same currying, same unions, same records-with-update, same Option/Result thinking — but with side effects allowed anywhere, real exceptions, and the entire .NET ecosystem one call away.
|> pipe and >> composition operators are not merely similar — Elm borrowed them from F#, spelling and alladd 10 works exactly as it does in Elmtype Shape = Circle of float | Rectangle of float * float — with the same exhaustive matchingMaybe is Option (Some/None) and Result keeps its name — but .NET libraries also throw real exceptions, which Elm deleted{ person with Age = 37 } — Elm's { person | age = 37 } with the pipe spelled withprintfn and mutable work anywhere, and the functional discipline becomes your jobThe family patriarch. Elm's let bindings, currying, custom types, records with update syntax, and exhaustive matching all trace back to OCaml — so most of the language will read like home with different punctuation. What OCaml adds is everything Elm removed: mutation, loops, exceptions woven into the core library, and a legendary module system.
|> pipe (born in OCaml) works exactly like Elm's — your pipeline habits transfer unmodifiedof payloads; records update with { person with age = 37 }, the construct Elm respelled with a pipeMaybe is 'a option and Result is result — but core functions like List.find RAISE on failure, with _opt variants as the safe path+., /.) and strings concatenate with ^ — inference without type classes makes the operator carry the typeref, :=, mutable arrays), and real for/while loops existOCaml semantics with JavaScript output, ReScript brings sound type inference, exhaustive pattern matching, and variant types to a syntax that feels familiar to JavaScript developers.
option<T> replaces null and undefined — the type system prevents null dereferences by designswitch, even when you add new variants laterElm's ideas, aimed beyond the browser — from an Elm core contributor. Roc was created by Richard Feldman and keeps what makes Elm feel safe (pure functions, immutability, no null, tag unions, exhaustive pattern matching) while trading the browser runtime for a swappable "platform," the garbage collector for compile-time reference counting, and — in this build — even the beloved |> pipe for method chaining.
match — with type signatures that read almost exactly like Elm'sMaybe and no Result: absence is an ad-hoc tag union you name yourself, and failure is the built-in Try(ok, err) — one type doing both jobs! suffix and supplied by a platform, replacing The Elm Architecture's Cmd/Sub runtime — the same source can target a CLI, a server, or the browser|x| lambdas instead of \x ->, brace if c { a } else { b }, and string interpolation (which Elm omits) — and this build has dropped |>, so you chain methods left to rightImmutable and functional like home — with the compiler traded for a crash-and-restart runtime. Elixir keeps your data habits (immutability, pipelines, pattern matching everywhere) but drops static types entirely: the safety net becomes tagged tuples, multi-clause functions, and supervisors that restart whatever fails.
case: = is a match operator, and function heads carry the patterns — def factorial(0), do: 1|> feeds the FIRST argument, not the last, and functions are not curried — the two biggest muscle-memory changes{:ok, value} / {:error, reason} tuples are Result as an ecosystem-wide convention, matched rather than type-checked{ person | age = 37 } is Elixir's %{person | age: 37} on a struct#{…} — the feature Elm never adopted — plus a String module organized just like the one you knowreceive matching a mailbox IS update matching Msg, and you can spawn millions of themThe opposite temperament, sharing one goal: rendering HTML from code. Elm is statically typed, purely functional, and immutable; Ruby is dynamically typed, object-oriented, and mutable. Yet both build markup as ordinary code rather than a separate template dialect — Elm with typed view functions, Ruby with ERB templates or Phlex components — which is exactly what the rendering examples here compare side by side.
print — main is a value the runtime renders — whereas Ruby writes to stdout directly with puts and mutates freelyHtml msg value, while Ruby offers two idioms shown here — ERB (a text template with <%= %> holes) and Phlex (a pure-Ruby component whose div { h2 { … } } reads much like an Elm view)Maybe/Result ceremony in Ruby — absence is nil and failure is an exception (raise/rescue), replacing Elm's tag unions and exhaustive case matchingList.map in Elm and an each block (or ERB loop) in Ruby — the same idea, but Ruby's blocks are a language-level feature every object can acceptWhere your architecture came from. The Elm Architecture's Msg/update loop is Erlang's receive loop with the runtime driving, and {ok, Value}/{error, Reason} tuples are the convention Result formalized — Erlang is immutable, single-assignment (stricter than Elm: no shadowing either), pattern-matched to its core, and completely untyped.
= is a match assertion, rebinding and shadowing are both illegal, and your Elm naming discipline is the perfect preparationreceive is a case … of over a process mailbox — The Elm Architecture as a language primitive, one mailbox per process, millions of processes{ok, Value} / {error, Reason} tagged tuples are the ancestral Result; maps:find/2 even returns an untyped Maybe|> to this VMString exists to fix