PONY λ M2 Modula-2
for R programmers

You already know R.Now explore other languages.

Side-by-side, interactive cheatsheets for R programmers
comparing R 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

What R structurally cannot offer: a single static binary, real concurrency, and a compiler that catches mistakes before a long job discovers them at 2am. The landing is a deliberate culture shock — no REPL-first workflow, no vectorization at all (not even a library restores it), static types everywhere, and NA's whole job reassigned to explicit (value, bool) pairs — traded for goroutines, channels, and `go build` producing one file that just runs, anywhere.

  • 🚨 No vectorization AT ALL: doses * 2 does not compile, and no library restores it — a for/range loop is the ONLY way to touch every element (loops are fast here, unlike R's)
  • Static types everywhere: a variable's type is fixed forever at declaration, implicit coercion is gone, and 1 + true is a compile error instead of a silent 1 + TRUE
  • Errors are ordinary (value, error) return values checked with if err != nil at every call site — tryCatch's job spread across the whole function, not wrapped once
  • NA splits into two: a missing map key is the zero value (not NULL) unless you ask with the "comma ok" idiom (value, exists := m[key]) — the same shape errors use
  • Real concurrency built into the language: go launches a lightweight goroutine and channels hand off results explicitly, where R's parallel package forks whole OS processes
  • S3 generics become methods with an explicit receiver — no inheritance, no dispatch chain, each type simply owns what is declared against its name
  • The payoff: go build produces ONE static binary — no R installation, no renv lockfile, no Docker image needed to reproduce the environment on another machine
Python Beta ⚡ Works Offline ⚡ Offline

The other data-science language — similar on the surface, inverted underneath. Python looks like R with cleaner syntax, but the first week is a minefield: indexing starts at zero, x[-1] selects instead of drops, [1, 2, 3] * 2 repeats the list, and your data is no longer copied on assignment. The reward is the production ecosystem R never grew.

  • 🚨 Negative indexing INVERTS: R's x[-1] drops the first element; Python's x[-1] selects the last — same syntax, opposite meaning
  • Vectors are not the default: [1, 2, 3] * 2 repeats the list, and elementwise math lives in numpy, where R's instincts finally work again
  • Ranges and slices are half-open — 1:5 has five elements, range(1, 5) has four, and 2:4 becomes [1:4]
  • Copy-on-modify is gone: assignment aliases, functions can mutate their arguments, and .copy() is how you buy R's safety back
  • data.frame → pandas: df$age becomes df["age"], aggregate becomes groupby, and pipes become method chains
  • One NA splits into None and nan — and pandas aggregations skip missing values by default, the opposite of R
  • The apply family becomes list comprehensions: [dose * 2 for dose in doses if dose > 15] is sapply plus subsetting in one line
Fortran Pre-Alpha

A rare, genuine kinship: the two languages most native to arrays. Fortran, like R, vectorizes whole-array arithmetic with no explicit loop, and BOTH count from one — the single most disorienting habit most migrations force never happens here. What changes is the substrate: strict shape conformance instead of silent recycling, static declared types, a compile step, and NA replaced by a hand-checked sentinel value.

  • The comfort zone is real: 1-based indexing survives (heights(1) means what it always meant), and whole-array arithmetic (doses * 2.0, sqrt(doses)) vectorizes with no loop, exactly like R
  • 🚨 Recycling becomes a hard error: mismatched array shapes must conform EXACTLY — R's silent (or warned) shorter-vector repeating has no equivalent, catching a whole class of silent bugs before the program runs
  • Every variable has one fixed, DECLARED type (implicit none makes an undeclared name a compile error) — no more discovering a type mismatch mid-analysis
  • There is no NA at all: missingness needs an agreed sentinel value (often -999) checked by hand everywhere — the discipline na.rm = TRUE automates away in R
  • R's free vectorization becomes explicit: mark a function elemental to let it accept a scalar OR any array shape — requested, not assumed
  • Named lists become declared derived types (type :: patient_type, accessed with %) — fields fixed and typed, so a misspelled field is a compile error, not a silent NULL
  • The honest payoff: R already depends on Fortran one layer down — LAPACK/BLAS underneath R's own matrix operations are Fortran, and it is the well-worn path for rewriting a slow inner loop once profiling finds R itself is the bottleneck
Scala Pre-Alpha

Where sparklyr's training wheels come off. Spark is written in Scala, and the native API is what your dplyr verbs were being translated into all along. The shape of the work survives — chains that read like pipelines, if-as-expression, named arguments, data that never mutates underneath you — while the substrate changes to static types, zero-based parentheses, and missingness moved out of the data (NA) into the type system (Option).

  • dplyr verbs map straight onto collection methods — filter/sortBy/map/groupBy chain with dots, reading exactly like a magrittr pipeline (no pipe operator needed)
  • 🚨 x[1] becomes x(0) — zero-based AND parenthesized; negative-index dropping becomes named methods (tail, init, drop)
  • Nothing vectorizes: doses * 2 does not compile — map(_ * 2) is the universal spelling, and there is no built-in mean
  • No NA: absence lives in the type as OptionSeq[Option[Double]] will not sum until you flatten (na.rm) or getOrElse (replace), decided at compile time
  • Copy-on-modify formalized: default collections are immutable, every "modification" returns a new one via structural sharing — the R safety guarantee, by construction
  • Named lists grow into case classes: typed fields, .copy(age = 37) for non-destructive update, and a misspelled field is a compile error instead of a silent NULL
  • switch grows into match: destructuring patterns over sealed hierarchies, with the compiler warning when a case is missing — S3 dispatch with coverage checking
  • Comforts that survive: if as an expression, default + named arguments, inclusive 1 to 5 ranges, and the last expression as the return value
Julia Pre-Alpha

The promise on the label: R's interactive, vector-first feel WITH compiled speed. Indexing is still 1-based, ranges are still inclusive, missing is a real NA with three-valued logic, and S3-style generic functions grow up into multiple dispatch — while the JIT makes plain loops as fast as C, ending the vectorize-or-suffer culture for good.

  • The comfort zone is real: 1-based indexing, inclusive 1:5 ranges, logical-mask subsetting, functions (not methods), and the last expression is still the return value
  • Vectorization becomes EXPLICIT: sqrt.(values), doses .* 2 — the dot broadcasts ANY function elementwise, replacing R's invisible (and sometimes absent) vectorization
  • 🚨 x[-1] is a BoundsError, not a drop — exclusion is spelled x[2:end], and recycling is gone entirely (mismatched lengths error instead of silently wrapping)
  • 🚨 * and %*% swap jobs on matrices: bare * IS matrix multiplication, elementwise needs .* — port a formula untranslated and it computes the wrong thing silently
  • Copy-on-modify is gone, but every mutating function says so in its name: sort copies like R's, sort! mutates — the ! convention is the language-wide warning label
  • missing propagates with NA's exact three-valued logic, skipmissing plays na.rm = TRUE, and nothing keeps NULL's separate job
  • Loops are fast (JIT-compiled to machine code), so the two-language problem — prototype in R, rewrite in C++ — disappears
Drag cards to reorder · your order is saved locally