PONY λ M2 Modula-2
for Erlang programmers

You already know Erlang.Now explore other languages.

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

▶ Start with Go Browse comparisons ↓

Choose your own path by reordering languages

Go Pre-Alpha

Concurrency, revisited with shared memory. Goroutines and channels are Go's answer to the same problem Erlang solved first with processes and message passing — but goroutines are NOT isolated the way Erlang processes are, which is the biggest thing to unlearn.

  • go func() is a direct descendant of spawn/1 — but goroutines share memory directly, where Erlang processes share nothing at all
  • Channels are explicit and typed, unlike Erlang's implicit per-process mailbox with pattern-matched receive
  • value, err := f() is {ok, Value}/{error, Reason} spread across two return values instead of one tagged tuple
  • No pattern matching anywhere — no case, no function clauses, no destructuring; everything goes through if/switch and manual field access
  • panic/recover echoes "let it crash," but scoped to one goroutine with a manual defer — there is no OTP-style supervision tree built in
  • Full static typing replaces Erlang's "no types at all," though Go's inference is far more limited than Haskell's or Scala's
C Pre-Alpha

The most extreme contrast on the whole site. The BEAM that runs Erlang is itself written in C, yet the two languages sit at opposite ends of nearly every design axis: automatic per-process GC vs. none at all, isolated share-nothing processes vs. no concurrency primitive built into the language, and a clean catchable crash vs. undefined behavior with no guaranteed outcome at all.

  • No garbage collector, ever — Erlang's per-process GC is invisible and automatic; C asks you to pair every malloc with a free by hand, forever
  • No pattern matching or tagging at all — a tagged union needs a hand-built enum + union, with none of the automatic safety Erlang's pattern match provides
  • Undefined behavior replaces Erlang's clean, catchable crashes — a bug might crash immediately, corrupt unrelated memory, or do nothing detectable at all
  • A crash takes down the WHOLE process — there is no per-task isolation boundary the way an Erlang process provides, which is what makes "let it crash" viable in the first place
  • No concurrency primitive built into the language — pthreads are an OS-level library bolted on, sharing memory directly with no compiler-enforced protection
  • Textual-substitution macros (#define) are a genuine direct parallel to Erlang's own -define — a rare point of agreement between two otherwise opposite languages
Rust Pre-Alpha

Reliability from the opposite direction. Erlang trusts a pattern match at runtime and lets the process crash, relying on isolation and supervision to recover; Rust's ownership and borrow checker reject entire categories of bugs — data races, use-after-free — before the program ever runs. The single biggest new concept: Rust has no garbage collector at all.

  • No garbage collector, anywhere — Erlang's per-process GC is invisible and automatic; Rust tracks ownership of every value at compile time instead
  • Option/Result formalize {ok, Value}/{error, Reason} almost verbatim, and the ? operator propagates failure automatically where Erlang re-pattern-matches by hand at every step
  • Enums formalize the tagged-tuple "one of several shapes" idiom as a closed sum type the compiler checks for exhaustiveness
  • match reads close to Erlang function clauses and guards, with compiler-enforced exhaustiveness Erlang cannot offer
  • Threads share memory directly (unlike share-nothing Erlang processes), but the borrow checker's Send/Sync traits enforce safe sharing at compile time instead of via isolation
  • panic! echoes "let it crash," but scoped narrowly — idiomatic Rust reaches for Result far more often than idiomatic Erlang avoids a crash
Clojure Pre-Alpha ⚡ Works Offline ⚡ Offline

A rare shared trait, then a hard syntax turn. Clojure is one of the only other dynamically-typed languages in this whole comparison — the type-system contrast that dominates every other pairing simply does not apply here. What replaces it: Lisp's parenthesized prefix notation, and a concurrency model built on shared atoms/refs instead of Erlang's isolated, message-passing processes.

  • Dynamic typing, shared with Erlang — one of the only pairings on the site where the anchor and target agree on this from the start
  • Every operation moves the operator or function name to the front: (+ 2 (* 3 4)) instead of 2 + 3 * 4 — no precedence table to memorize, since nesting parens IS the precedence
  • Keywords (:ok) play the role Erlang atoms play, and are themselves callable as one-argument map lookups — something no Erlang atom can do
  • No built-in shape-based pattern matching — cond replaces Erlang's case/function clauses; matching Erlang's built-in case requires reaching for the separate core.match library
  • Atoms/refs/agents are shared, mutable, coordinated references on one heap — the opposite of Erlang's isolated, share-nothing processes
  • Homoiconicity — code is literally data ('(+ 2 3) is both syntax and an inspectable list) — something Erlang has no equivalent for
Haskell Pre-Alpha

Two roads to reliability, from a common functional starting point. Both languages favor immutability, pattern matching, and recursion — but Erlang trusts a pattern match at runtime and lets the process crash, while Haskell's Hindley-Milner type system rejects entire categories of bugs before the program ever runs.

  • List comprehensions are nearly identical syntax — Erlang's own [X || Generator] was directly inspired by Haskell's [x | generator]
  • Function clauses/equations and when/| guards read almost like a mechanical translation of each other
  • Erlang's {ok, Value}/{error, Reason} convention is exactly what Maybe/Either formalize — except the compiler now requires handling both cases
  • Static types with full inference replace Erlang's "no types at all" — -spec/Dialyzer is optional and separate, Haskell's checker is mandatory and built in
  • Lazy evaluation by default is the biggest new behavior — an unused argument in Haskell is never even computed, unlike Erlang's eager evaluation
  • Every function is curried automatically, where Erlang funs have a fixed arity and need an explicit wrapper for partial application
F# Pre-Alpha

The closest syntactic relative to Erlang on the whole site. Both languages run top-level script code with no boilerplate main function, both lean on pattern matching as the primary control-flow tool, and F#'s discriminated unions formalize Erlang's tagged-tuple idiom as real, compiler-checked sum types.

  • No wrapping module or main function needed — F# scripts run top-to-bottom exactly like an Erlang shell session
  • match reads almost like a direct translation of Erlang function clauses, guards and all
  • Discriminated unions plus exhaustiveness checking give the tagged-tuple "one of several shapes" idiom a closed type the compiler verifies for you
  • Option/Result formalize {ok, Value}/{error, Reason} — F# even reuses the bare word Ok
  • MailboxProcessor ("F# Agents") is explicitly documented as Erlang-style actors for .NET — mailboxes, Post, and Receive echo spawn/!/receive directly
  • Full static typing with strong inference replaces Erlang's "no types at all," and every function curries automatically, just like Haskell
Pony Pre-Alpha

The strongest actor-model parallel on the whole site. Both languages put actors at the absolute center of the language, not layered on top as a library — but where Erlang guarantees isolation by always copying data across a process boundary, Pony proves sharing is safe at compile time through reference capabilities, letting it pass data between actors with zero copying at all.

  • actor and be map directly onto Erlang processes and ! — both process exactly one message at a time, in order, with no locks needed for internal state
  • Both give every concurrent unit its own private, independently garbage-collected heap — neither language has a global "stop the world" GC pause
  • Reference capabilities (iso, val, ref, box, tag, trn) have no Erlang equivalent — the compiler proves a reference is safe to share instead of Erlang's always-copy guarantee
  • Partial functions (fun f(): T ?) replace {error, Reason}, but Pony's error carries no payload — no built-in idiom is as convenient as Erlang's tagged tuple
  • Full static typing, including compile-time-checked reference safety, replaces Erlang's "no types at all"
  • No OTP-equivalent supervision tree — an actor that errors simply stops; automatic recovery is something you build yourself, the same gap Go has relative to Erlang
Prolog Pre-Alpha ⚡ Works Offline ⚡ Offline

The paradigm shift hiding behind familiar syntax. Prolog looks almost like home — atoms, [Head|Tail] lists, and = as real unification are all things Erlang already does. But Prolog goals can have many solutions where Erlang functions have exactly one, and that single difference reorganizes everything else.

  • Atoms and the uppercase/lowercase variable convention transfer completely unchanged — one of the few languages where this Erlang habit needs zero relearning
  • [Head|Tail] list syntax and cons-list semantics are identical, character for character
  • = really is unification in both languages — but Prolog goals can backtrack into multiple solutions where an Erlang function call has exactly one outcome
  • Facts and rules replace the lists-of-tuples-plus-list-comprehensions style Erlang uses to model relationships, and Prolog answers the reverse query for free
  • assertz/retract offer a genuinely mutable database — the one place Prolog quietly breaks its own purity, the way Erlang processes are a deliberate workaround for its own
  • A failed match crashes in Erlang ({badmatch, ...}); a failed unification is just a quiet false in Prolog, with backtracking ready to try something else
Scala Pre-Alpha

The actor model, formalized on the JVM. Akka — the JVM's most widely used actor framework — was explicitly modeled on Erlang/OTP: mailboxes, message passing, and supervision strategies are direct analogues of the processes, receive, and supervisors an Erlang developer already knows.

  • Akka's SupervisorStrategy (restart, resume, stop, escalate) is a near-literal port of OTP's supervisor restart strategies — the vocabulary transfers, not just the concept
  • Option/Either formalize the {ok, Value}/{error, Reason} convention Erlang programmers already use by hand — except the compiler now requires handling both cases
  • Sealed traits plus case classes give Erlang's tagged-tuple "one of several shapes" idiom a closed, compiler-checked sum type
  • Full static typing with local inference replaces Erlang's "no types at all" — Dialyzer/-spec is optional and separate, Scala's checker is mandatory and built in
  • List comprehensions become for/yield — same generator-and-filter idea, different keyword placement
  • val mirrors Erlang's single-assignment binding closely, but Scala also allows real mutation via var — an escape hatch Erlang structurally does not have
Elixir Pre-Alpha

The same BEAM, a friendlier surface. Elixir compiles to the very bytecode your Erlang runs on and shares its processes, supervision, and pattern matching — but wraps them in a Ruby-inspired syntax, a real macro system, protocols, structs, and a build tool (Mix) that a modern team actually enjoys.

  • Same runtime, same primitives — processes, send/receive, links, monitors, and OTP behaviours are the ones you already know, callable from either language
  • The pipe |> and with replace deeply nested calls and case ladders — the readability win Erlang never got
  • Real metaprogramming — defmacro and the quoted AST, instead of parse transforms
  • Protocols and structs give principled polymorphism and named data, where Erlang has records and behaviours
  • One toolchain — Mix + Hex for builds, deps, tests, and releases, instead of rebar3 plus assorted scripts
  • Doctests, ExUnit, and first-class docs are built in — testing and documentation are part of the language culture
Drag cards to reorder · your order is saved locally