PONY λ M2 Modula-2
for Haskell programmers

You already know Haskell.Now explore other languages.

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

▶ Start with Kotlin Browse comparisons ↓

Choose your own path by reordering languages

Kotlin Pre-Alpha

A completely different, pragmatic lineage that still lands on familiar ideas. Kotlin descends from Java, not the ML family, yet sealed classes with exhaustive when mirror algebraic data types and nullable types bake "value or absence" into the type system the way Maybe does — while staying strict, OOP-friendly, and mutation-comfortable throughout.

  • Sealed classes with exhaustive when mirror algebraic data types — same closed, tagged hierarchy, same compiler-checked completeness, for hierarchies (not arbitrary types) only
  • Nullable types (String?) rename Maybe but bake it into the type system rather than a wrapper — closer to Swift's Optional than to Haskell's explicit Just/Nothing
  • No type classes at all, and — unlike Swift's protocol extensions or Scala's implicits — no strong workaround either: extension functions only add methods to one named type, with no type-directed dispatch
  • Strict by default, unlike lazy-by-default Haskell — infinite sequences need the explicit Sequence type, and deferred computation needs the explicit by lazy { } delegate
  • Currying needs an explicit function-returning-a-function, not Haskell's automatic partial application
  • Kotlin freely blends OOP and mutation with FP — genuinely mutable collections and var sit right alongside immutable values, a discipline Haskell enforces structurally that Kotlin only encourages by convention
Rust Pre-Alpha

The single biggest new concept for a Haskeller: Rust has no garbage collector at all. Maybe/Either map almost exactly onto Option/Result, and type classes map closely onto traits — but Rust's ownership and borrow checker replace Haskell's tracing GC entirely, introducing move semantics and lifetimes, concepts Haskell structurally has no equivalent for.

  • No garbage collector at all — Haskell's GC-backed sharing model gives way to ownership, borrowing, and (when genuinely needed) explicit Rc/Arc reference counting
  • Option/Result are close renames of Maybe/Either — one of the strongest structural parallels on the whole site
  • Traits map closely onto type classes — impl Trait for Type mirrors instance Class Type where, default methods included
  • Currying is automatic in Haskell but not in Rust — partial application needs an explicit closure capturing the fixed argument
  • Rust is strict, unlike lazy-by-default Haskell — infinite sequences need the lazy Iterator type rather than an ordinary list
  • The ? operator on Result/Option is Rust's closest brush with monadic chaining, but it is special-cased at the language level rather than a generalized, user-extensible Monad the way Haskell's do-notation is
Swift Pre-Alpha

A completely different lineage that converges on surprisingly similar ideas. Swift is not part of the ML/Haskell family at all, yet enums with associated values mirror algebraic data types, exhaustive switch mirrors pattern matching, and protocol extensions give a real, working type-class analogue — while staying strict, OOP-friendly, and ARC-managed underneath.

  • Enums with associated values are Swift's algebraic data types — same closed, tagged, payload-carrying variants, same compiler-checked exhaustive switch
  • Protocols plus protocol extensions give real, automatic, type-directed dispatch — a genuine type-class analogue, unlike F#'s interfaces alone or Roc/ReScript's weaker workarounds
  • Optional<T> renames Maybe, but is integrated into the language syntax far more deeply — if let, guard let, and ?? have no single Haskell equivalent construct
  • Strict by default, unlike lazy-by-default Haskell — every binding is computed immediately, and deferred computation needs the explicit lazy keyword
  • ARC gives deterministic, immediate deallocation the moment a reference count hits zero — a real capability Haskell's tracing garbage collector, with its unpredictable pause timing, does not offer
  • Currying needs an explicit function-returning-a-function, not Haskell's automatic partial application — Swift removed its own earlier curried-function-declaration sugar in Swift 3
Clojure Pre-Alpha ⚡ Works Offline ⚡ Offline

A rare, genuine kinship despite no shared type system at all. Clojure and Haskell both treat immutability as a core value rather than a syntactic default, and — uniquely among this site's Haskell targets — Clojure's core sequence functions are lazy, the closest parallel to Haskell's own laziness of any target covered. What Clojure lacks entirely is a static type system: not even a typeclass-style workaround exists, only protocols and multimethods for open, runtime dispatch.

  • Lazy sequences by default — (take 5 (range)) mirrors Haskell's take 5 [1..] almost exactly, a rarity among this site's Haskell targets, all of which are otherwise strict
  • Immutability as a core value, not just a default — persistent, structurally-shared data structures echo Haskell's own commitment, despite Clojure having no type system enforcing it
  • Protocols and multimethods give real, runtime type-directed dispatch — closer to type classes than any other dynamically-typed language on the site offers
  • No static types at all — not even Kotlin's weak extension-function workaround, since Clojure has no static dispatch to begin with
  • Threading macros (->/->>) echo Haskell's own point-free composition style, just read left-to-right instead of right-to-left
  • Homoiconicity — code is literally data, quotable and evaluable — a capability with no Haskell parallel at all outside rarely-used Template Haskell
F# Pre-Alpha

The closest functional language on the .NET runtime — strict where Haskell is lazy. F# shares Haskell's Hindley-Milner inference, automatic currying, and algebraic-data-type-driven design, but trades laziness for eager evaluation and type classes for object-oriented interfaces.

  • Every function curries automatically in both languages — a genuine structural match rarely found elsewhere on this site
  • Discriminated unions are F#'s name for Haskell's algebraic data types — same closed, tagged-variant sum types, same compiler-checked exhaustive matching
  • Option and Result are direct renames of Maybe and Either, right down to Some/None and Ok/Error
  • F# is strict by default, unlike lazy-by-default Haskell — infinite lists need the explicit seq type, and memoization needs an explicit cache rather than falling out of laziness for free
  • No type classes: ad-hoc polymorphism goes through object-oriented interfaces or Statically Resolved Type Parameters (SRTP) instead of automatic, type-directed instance resolution
  • The |> pipe operator is F#'s idiomatic default, where Haskell code more often composes with . or nests calls instead of using its own & operator
OCaml Pre-Alpha

Haskell's closest living cousin. OCaml shares the same ML-family Hindley-Milner inference, algebraic data types, and exhaustive pattern matching — but it is strict by default where Haskell is lazy, and that one difference explains almost everything else: ordinary mutation, real loops, and exceptions-as-the-default all follow directly from OCaml's eager evaluation.

  • Strict (eager) evaluation is the default, not lazy — arguments are always evaluated before a function runs, so infinite lists need the explicit Seq module instead of falling out for free
  • ref, mutable record fields, and genuinely mutable arrays make mutation ordinary and unrestricted, with no IORef/IO wrapper required
  • option's Some/None map directly onto Maybe's Just/Nothing, and result's Ok/Error onto Either's Right/Left — nearly one-to-one
  • No type classes — ad-hoc polymorphism instead goes through an explicit module system, with functors as modules parameterized by other modules
  • Recursion needs the explicit rec keyword; a plain let cannot refer to itself the way every Haskell binding automatically can
  • The |> pipe operator was born in OCaml — Haskell has no built-in equivalent, only the low-precedence right-to-left $ and the less common &
Prolog Pre-Alpha ⚡ Works Offline ⚡ Offline

One answer becomes many. Haskell pattern matching and algebraic data types already look a lot like Prolog facts and compound terms, but Prolog's unification runs in any direction and a single query can backtrack through every solution at once, where a Haskell function always returns exactly one value.

  • Pattern matching transfers directly into unification, except Prolog's = can bind either side and is not limited to destructuring an already-known value
  • A failed Haskell pattern match throws a runtime exception; a failed Prolog unification is a quiet false that backtracking can route around
  • findall/3 turns a search into a list — the closest thing to a Haskell list comprehension, except the list did not exist until the backtracking search that produced it finished
  • Nullary data constructors like Ok already feel like Prolog atoms, but atoms carry no type at all — nothing like the compiler stands between an atom and a typo
  • assertz/retract add and remove facts from a live, mutable global database — untracked mutation with no analogue in pure Haskell at all, not even IORef
  • The classic grandparent/2-style rule runs in any direction from one definition, where a Haskell version commits to one direction of query as soon as it is written
Scala Pre-Alpha

The richest functional-programming comparison on the site. Scala's for-comprehensions genuinely generalize across Option, List, and Either the way Haskell's Monad typeclass does, and implicit parameters give real, working ad-hoc polymorphism — but Scala is strict, blends OOP freely with FP, and currying is opt-in rather than automatic.

  • For-comprehensions desugar to flatMap/map/withFilter and work uniformly across Option, List, Either, and Future — the closest any language on this site gets to Haskell's generalized Monad abstraction
  • Implicit parameters give real, automatic, type-directed dispatch — a genuine type-class analogue, unlike F#'s interfaces, Roc's structural constraints, or ReScript's near-total absence of ad-hoc polymorphism
  • Either[L, R] matches Haskell's Either almost exactly, reusing the names Left/Right with the same failure/success convention
  • Strict by default, unlike lazy-by-default Haskell — infinite sequences need the explicit LazyList type, and deferred computation needs the explicit lazy keyword
  • Currying is opt-in via multiple parameter lists (def add(x: Int)(y: Int)), not automatic the way every Haskell function is curried by default
  • Scala freely blends OOP and mutation with FP — genuinely mutable collections and var sit right alongside immutable values, a discipline Haskell enforces structurally that Scala only encourages by convention
Scheme Pre-Alpha ⚡ Works Offline ⚡ Offline

The sharpest contrast a Haskeller will find on this site. Every other Haskell target is still statically typed — Scheme has no type system at all. In exchange it offers two genuinely new capabilities: unhygienic macros that write code at read time, and first-class continuations via call/cc, which Haskell can only model purely through the Cont monad rather than offer as a raw primitive.

  • No type system whatsoever — not even Kotlin's weak extension-function workaround exists, because there is no static dispatch to speak of in the first place
  • define-macro lets ordinary code write new syntax that decides whether and how its own arguments are evaluated — a capability Haskell only approximates with rarely-used Template Haskell
  • call/cc captures the rest of a computation as a callable value — a raw control-flow primitive Haskell can only model purely, through the Cont monad, never offer directly
  • Pairs and lists are close cons-cell cousins of Haskell's own list representation — one of the closest structural matches on the whole site
  • Both languages formally guarantee proper tail-call optimization for accumulator-passing recursion
  • Three different equality predicates (eq?/eqv?/equal?) replace Haskell's single, typeclass-dispatched == — picking the wrong one is a real, common Scheme bug with no Haskell equivalent
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

Purely functional, but strict, and with no type classes. Roc shares Haskell's commitment to immutability and effect-tracked purity, but trades laziness for eager evaluation, compile-time reference counting for a generational garbage collector, and Haskell's nominal type classes for structural where-clause constraints.

  • Strict by default, unlike lazy-by-default Haskell — there is no infinite-list idiom, and every top-level binding is computed at compile time rather than deferred to first use
  • No type classes at all — ad-hoc polymorphism goes through a structural where [a.method : a -> b] constraint instead of Haskell's nominal, automatic instance resolution
  • Tag unions are algebraic data types without the data/deriving ceremony — structural, anonymous, and inferred, closable or left open with ..
  • Try(ok, err) renames Either, but Roc has no built-in Maybe at all — absence is modeled with an ad-hoc tag union instead
  • Effects are tracked with a ! suffix and => arrow rather than the Monad typeclass and do-notation — visible in every signature, but with no single generalized abstraction unifying them
  • Real while loops exist alongside recursion — a genuine surprise for a language this committed to purity and immutability elsewhere
Drag cards to reorder · your order is saved locally