PONYΞ»M2Modula-2
CodeCompared
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 RubyBrowse comparisons ↓Explore the language map β†—

Choose your own path by reordering languages

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 print β€” main 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
JavaScriptAlpha⚑ 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
OCamlPre-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
ReScriptPre-Alpha

The other typed ML that compiles to JavaScript β€” with the opposite attitude toward JavaScript. ReScript gives you Elm's type system family (variants, option, records, exhaustive switch, inference) but embraces the host platform: zero-cost bindings to any JS value where Elm has ports, mutable JS arrays where Elm has persistent lists, and compiled output you are meant to read.

  • Variants ARE custom types β€” type shape = Circle(float) | Rectangle(float, float) with the same exhaustive matching, in a switch
  • Maybe is option (Some/None) and result keeps Ok, spelling Err out as Error
  • No automatic currying since v11 β€” add(10) is an arity error, and partial application is an explicit lambda; the pipe -> feeds the FIRST argument to match
  • Record update is the JS spread β€” {...person, age: 37} for Elm's { person | age = 37 } β€” and records compile to plain JS objects
  • The default [1, 2, 3] array is a real, mutable JavaScript array; Elm-style immutable cons lists live behind list{…}
  • One @val external line binds any JavaScript function synchronously β€” the ports-vs-interop trade is the deepest difference between the two languages
RocPre-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
ErlangPre-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
ElixirPre-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
Drag cards to reorder Β· your order is saved locally