PONY λ M2 Modula-2
for Elm programmers

You already know Elm.Now explore other languages.

Side-by-side, interactive cheatsheets for Elm programmers
comparing Elm to other languages. Every example runs live in your browser — no setup, no installation.

▶ Start with JavaScript Browse comparisons ↓

Choose your own path by reordering languages

JavaScript Alpha ⚡ Works Offline ⚡ Offline

The language of the web, JavaScript is unavoidable, powerful, and capable of running almost anywhere.

  • var / let / const vs. Ruby's local, instance, and global variables
  • Prototypal inheritance — a fundamentally different object model than Ruby's class-based one, with the inheritance machinery exposed and mutable at runtime
  • Closures and the this binding: a call-site-determined design that arrow functions exist to work around
  • Promises and async/await vs. Ruby's Fibers, threads, and Ractors
  • Destructuring, spread, and rest — features Ruby also has, with different syntax
F# Pre-Alpha

The 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.

  • The |> pipe and >> composition operators are not merely similar — Elm borrowed them from F#, spelling and all
  • Every function curries automatically, so partial application like add 10 works exactly as it does in Elm
  • Custom types are "discriminated unions" — type Shape = Circle of float | Rectangle of float * float — with the same exhaustive matching
  • Maybe is Option (Some/None) and Result keeps its name — but .NET libraries also throw real exceptions, which Elm deleted
  • Record update is { person with Age = 37 } — Elm's { person | age = 37 } with the pipe spelled with
  • No enforced purity and no Elm Architecture: printfn and mutable work anywhere, and the functional discipline becomes your job
OCaml Pre-Alpha

The 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.

  • Automatic currying and data-last functions mean the |> pipe (born in OCaml) works exactly like Elm's — your pipeline habits transfer unmodified
  • Custom types are variants with of payloads; records update with { person with age = 37 }, the construct Elm respelled with a pipe
  • Maybe is 'a option and Result is result — but core functions like List.find RAISE on failure, with _opt variants as the safe path
  • Float arithmetic has its own dotted operators (+., /.) and strings concatenate with ^ — inference without type classes makes the operator carry the type
  • Shadowing is legal and idiomatic, mutation is explicit (ref, :=, mutable arrays), and real for/while loops exist
  • Modules are first-class structures — inline, nested, signature-constrained, and parameterized (functors) — a whole design language Elm's one-module-per-file has no counterpart for
ReScript Pre-Alpha

OCaml semantics with JavaScript output, ReScript brings sound type inference, exhaustive pattern matching, and variant types to a syntax that feels familiar to JavaScript developers.

  • Sound type system — types are inferred automatically and never coerce; the compiler catches errors before the program runs
  • option<T> replaces null and undefined — the type system prevents null dereferences by design
  • Exhaustive pattern matching — the compiler errors if you miss any case in a switch, even when you add new variants later
  • Variants (algebraic data types) — model domain data precisely instead of stringly-typed flags or discriminated-union objects
  • Compiles to clean, readable JavaScript — the output is human-legible and maps naturally to the source
Roc Pre-Alpha

Elm'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.

  • The core you already trust: pure functions, immutable values, no null, tag unions, and exhaustive match — with type signatures that read almost exactly like Elm's
  • No Maybe 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
  • Effects are functions marked with a ! 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
  • No garbage collector — compile-time reference counting with opportunistic in-place mutation, so immutable code runs at mutable speed with no GC pauses
  • Surface adjustments: |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 right
Elixir Pre-Alpha

Immutable 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.

  • Pattern matching goes further than Elm's case: = is a match operator, and function heads carry the patterns — def factorial(0), do: 1
  • The pipe |> 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
  • Record update syntax nearly matches: Elm's { person | age = 37 } is Elixir's %{person | age: 37} on a struct
  • String interpolation #{…} — the feature Elm never adopted — plus a String module organized just like the one you know
  • The Elm Architecture is a BEAM process: receive matching a mailbox IS update matching Msg, and you can spawn millions of them
Ruby ⚡ Works Offline ⚡ Offline

The 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.

  • Elm has no side effects and no printmain is a value the runtime renders — whereas Ruby writes to stdout directly with puts and mutates freely
  • Building HTML: Elm returns a typed Html 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)
  • No Maybe/Result ceremony in Ruby — absence is nil and failure is an exception (raise/rescue), replacing Elm's tag unions and exhaustive case matching
  • Iteration over a collection is List.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 accept
  • Elm is compiled ahead of time to JavaScript with whole-program type inference; Ruby is interpreted with no type declarations, so the compiler that guarantees Elm's totality has no Ruby counterpart
Erlang Pre-Alpha

Where 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.

  • Variables bind exactly once — = is a match assertion, rebinding and shadowing are both illegal, and your Elm naming discipline is the perfect preparation
  • receive 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
  • No pipe operator at all — pipelines become named single-assignment checkpoints, and the ache is why Elixir added |> to this VM
  • No static types, no compiler safety net: pattern-match crashes are the runtime's exhaustiveness check, and supervisors restart what crashes ("let it crash")
  • Strings are literally lists of character codes (modern code uses binaries) — the data-model surprise Elm's opaque Unicode String exists to fix
Drag cards to reorder · your order is saved locally