Cortex for Mathematica Users
A working translation guide for anyone coming from the Wolfram Language. Every
Cortex example on this page is executed by the documentation test suite and
its // ➔ output verified.
What carries over. Almost all of the mental model. Values are symbolic
expressions; evaluation is exact unless you ask for a number; capitalized
names are the library and lowercase names are yours; Simplify, Solve, D,
Integrate, Limit, Series, Factor, Expand, N and the linear-algebra
operators all keep their names; {k, 1, n} iterator triples work in Sum,
Product, Integrate, D and Table; Range(5) starts at 1; indexing is
1-based and -1 is the last element; arithmetic threads over lists the way a
Listable function does.
What to unlearn. Four things:
- Function application uses parentheses:
f(x), notf[x]. Square brackets are indexing (Wolfram's[[…]]). {…}is a set, not a list. A Cortex list is[1, 2, 3]. The braces survive in iterator triples, where they read positionally, but a bare{1, 2, 2}is the set{1, 2}.=is assignment and->is a key/value pair. Equations use==(as in Wolfram), but replacement rules must be writtenRule(x, 3).- There is no
%, noOut[], and no notebook history.%is the remainder operator.
Expressions and Evaluation
| Wolfram | Cortex |
|---|---|
f[x], Sin[x] | f(x), Sin(x) |
x = 5 | let x = 5 |
f[x_] := x^2 | f(x) = x^2 |
f = Function[x, x^2] | f = x |-> x^2 |
#^2 & | x |-> x^2 — no slot/& syntax |
expr /. x -> 3 | ReplaceAll(expr, Rule(x, 3)) |
a == b, SameQ[a, b] | a == b, a === b — see below |
expr // N | expr |> N (or ~>) |
N[expr], N[expr, 25] | N(expr), N(expr, 25) |
Hold[expr] | HoldValues(expr) — evaluate with assigned symbols kept symbolic |
Print[x] | (no printing) — the program's value is its last statement |
%, Out[3] | (no history) — bind with let |
(* comment *) | // comment or /* comment */ |
expr; to suppress output | ; is a statement separator, nothing is suppressed |
f(x) = x^2 + 1
(f(3), D(f(x), x), Integrate(f(x), {x, 0, 1}))
// ➔ (10, 2x, 4/3)
Only the value of the last statement is returned; an earlier statement that evaluates to an error value also raises a diagnostic, so nothing vanishes silently.
== vs === (Wolfram's SameQ)
== is the semantic comparison: it evaluates, compares within tolerance, and
may stay an unresolved condition (x == y is what you hand to Solve).
=== is SameQ: structural identity, no tolerance, and total — it always
answers True or False.
(Sqrt(2) == 1.4142135623730951, Sqrt(2) === 1.4142135623730951, x === y, 1 === 1.0)
// ➔ (True, False, False, True)
One caveat for Wolfram users: SameQ[1, 1.] is False there, because 1 and
1. are different kinds of number. In Cortex 1 === 1.0 is True — the
lexer folds 1.0 to the integer literal 1, and === compares number leaves
by exact value, so 0.5 === 1/2 is True too.
Lists and Parts
| Wolfram | Cortex |
|---|---|
{1, 2, 3} (list) | [1, 2, 3] — braces make a set |
xs[[i]] | xs[i] — 1-based, as in Wolfram |
xs[[-1]], First, Last, Rest | xs[-1], First(xs), Last(xs), Rest(xs) |
xs[[2 ;; 4]] | xs[2..4] |
m[[i, j]] | m[i, j] (or m[i][j]) |
Range[5], Range[2, 10, 2] | Range(5) or 1..5; Range(2, 10, 2) |
Length, Sort, Reverse, Flatten | same names |
Total[xs] | Sum(xs) |
Select[xs, f] | Filter(xs, f) |
Count[xs, v], Count[xs, f] | Count(xs, v), Count(xs, f) — Count(xs) is the length |
Map[f, xs], f /@ xs | Map(xs, f) — collection first |
Fold[f, init, xs] | Fold(f, init, xs) |
Apply[f, {a, b}], f @@ t | Apply(f, (a, b)), or spread: f(...t) |
Position[xs, v] | IndexOf(xs, v) |
Append[xs, v], Join | Append(xs, v), Join(xs, ys) |
Tally, Partition | same names (Tally returns a (values, counts) pair) |
<|"a" -> 1|> (association) | {"a" -> 1}; read with d["a"], enumerate with Keys/Values |
Union, Intersection | same names, returning a set |
let xs = [3, 1, 4, 1, 5]
(xs[1], xs[-1], xs[2..4], Length(xs), Sort(xs))
// ➔ (3, 5, [1,4,1], 5, [1,1,3,4,5])
Count covers all three Wolfram spellings — the plain length, a value to
match, and a predicate:
let xs = [3, 1, 4, 1, 5, 1]
(Count(xs), Count(xs, 1), Count(xs, k |-> k > 2))
// ➔ (6, 3, 3)
Lists and sets are genuinely different types, so the brace/bracket distinction is not cosmetic:
(Type({1, 2, 3}), Type([1, 2, 3]))
// ➔ ("set<finite_integer>", "vector<finite_integer^3>")
Threading over lists
Arithmetic and the elementary functions thread over lists, so a Listable
habit transfers directly. Matrices multiply as matrices:
([1, 2, 3] + 1, [1, 2, 3] * [4, 5, 6], Sin([0, Pi]))
// ➔ ([2,3,4], [4,10,18], [0,0])
let A = [[2, 1], [1, 3]]
(Determinant(A), Inverse(A), A * [1, 1])
// ➔ (5, [[3/5,-1/5],[-1/5,2/5]], [3,4])
Iterators and Table
Iterator triples in braces work exactly as in Wolfram — Sum, Product,
Integrate, D and Table all read {var, lo, hi} (and {var, lo, hi, step}) positionally:
let squares = Table(k^2, {k, 1, 5})
(Sum(squares), Sum(1/k^2, {k, 1, Infinity}), Product(k, {k, 1, 5}))
// ➔ (55, 1/6 * pi^2, 120)
Sum, Product, Integrate and Table all accept the tuple spelling
(k, 1, 5) as well. D(expr, {x, 2}) takes a second derivative.
Sum(Table(k^2, (k, 1, 5)))
// ➔ 55
Table is a lazy generator, so the value above is materialized by Sum. When
you want an ordinary list, index it, aggregate it, or build it with Map:
let g = x |-> x^2 + 1
(g(3), Sum(Map(1..4, g)))
// ➔ (10, 34)
Control Flow and Pattern Matching
| Wolfram | Cortex |
|---|---|
If[c, a, b] | if c { a } else { b } — an expression |
Which[c1, a, c2, b, True, z] | if c1 { a } else if c2 { b } else { z } |
Switch[x, 0, "zero", _, "other"] | match x { 0 => "zero"; _ => "other" } |
Cases[xs, patt] | Filter with a predicate, or Map over a match |
Do[body, {k, 1, n}] | for k in 1..n { body } |
While[c, body] | while c { body } |
Module[{t}, body] | do { let t = …; body }, or a function block |
With[{t = v}, body] | do { const t = v; body } |
Block[{x}, body] | (no dynamic scoping) — Cortex is lexically scoped |
match replaces the whole Switch/Which/Cases family. It is structural
and total: it always selects a case, and a bare identifier in pattern position
binds rather than compares. Guards use if, and == expr pins a value.
classify(z) = match z {
0 => "zero"
n if n > 0 => "positive"
_ => "negative"
}
Map([-2, 0, 5], classify)
// ➔ ["negative", "zero", "positive"]
Because a pattern is parsed as an ordinary expression, matching on operator
structure comes for free — a case pattern a + b destructures an Add and
captures its operands, the Wolfram Plus[a_, b_] idiom. Blank patterns are
spelled differently: _ is the wildcard, name is a named capture (Wolfram's
name_), name: type adds a type guard (name_Integer), and ...rest
captures the remainder of a list (___). See
Control Flow for the full pattern grammar.
Scoping constructs are blocks:
function area(r) {
let c = Pi
c * r^2
}
(area(2), area(3))
// ➔ (4pi, 9pi)
Symbolic Mathematics
This is the part that needs the least translation:
| Wolfram | Cortex |
|---|---|
Simplify, Expand, Factor | same names |
Solve[x^2 == 4, x] | Solve(x^2 == 4, x) |
Solve[{e1, e2}, {x, y}] | Solve([e1, e2], [x, y]) — lists in brackets |
D[f, x], D[f, {x, 2}] | D(f, x), D(f, {x, 2}) |
Integrate[f, x], Integrate[f, {x, a, b}] | same, with parentheses |
Limit[f, x -> 0] | Limit(f, x, 0) |
Series[f, {x, 0, n}] | Series(f, x, 0) — the tail is a BigO term |
Det, Inverse, Transpose, Eigenvalues | Determinant, Inverse, Transpose, Eigenvalues |
Dot, Cross, LinearSolve | same names |
Pi, Infinity, I, E | Pi, Infinity, i, e — lowercase |
PrimeQ, NextPrime, FactorInteger, Divisors | IsPrime, NextPrime, FactorInteger, Divisors |
Binomial, GCD, LCM, n! | same |
(Solve(x^2 - 5x + 6 == 0, x), Simplify((x^2 - 1)/(x - 1)), Factor(x^2 - 4))
// ➔ ([3,2], x + 1, (x - 2) * (x + 2))
(Limit((1 + 1/n)^n, n, Infinity), Series(Cos(x), x, 0))
// ➔ (e, 1 - 1/2 * x^2 + 1/24 * x^4 + BigO(x^6))
N takes an optional precision, and the engine works to arbitrary precision:
N(Pi, 25)
// ➔ 3.141592653589793238462643
Traps
Surface forms that look like Wolfram but behave differently.
| You write | What actually happens | Write instead |
|---|---|---|
f[x] | f indexed at x — an incompatible-type error value, not a call | f(x) |
{1, 2, 3} for a list | A set: unordered, deduplicated, not indexable by position | [1, 2, 3] |
E, I | Ordinary undeclared symbols — they stay symbolic, silently | e, i |
expr /. x -> 3 | -> builds a KeyValuePair, not a Rule | ReplaceAll(expr, Rule(x, 3)) |
% for the last result | % is the Mod operator | bind results with let |
x = 4 inside Solve | = is assignment: Solve(x^2 = 4, x) is silently [] | Solve(x^2 == 4, x) |
expr; to suppress | ; only separates statements | (nothing to suppress) |
Total, Select, Cases, MemberQ, Accumulate, Nest | Unknown names: the call stays symbolic and inert, with a did-you-mean warning naming the Cortex operator | Sum, Filter, Filter, Contains(xs, v), Scan, Iterate |
Ceiling, Quotient, IntegerPart | Inert (with a did-you-mean warning) | Ceil, Floor(a/b), Floor |
StringLength, ToUpperCase | Inert — the string library is small | Length(Characters(s)); decompose and rebuild |
RandomReal[], RandomInteger[n] | Inert (with a did-you-mean warning) | Random(), Random(Range(1, n)) |
SameQ[1, 1.] | 1 === 1.0 is True — the lexer folds 1.0 to 1 | (nothing — but don't read === as type-aware) |
3!^2 | Diagnostic — the lexer reads !^ as one token | 3! ^ 2 |
a +b | Diagnostic — an infix operator needs spaces on both sides or neither | a + b or a+b |
The rows about inert names deserve emphasis: an unknown capitalized name is
not an error. Cortex leaves the call symbolic (with a did-you-mean warning
when a close library name exists), exactly the way Wolfram leaves Foo[1]
unevaluated. A program that calls Total(xs) therefore returns the unevaluated
Total([…]) rather than a number — when a result looks unfinished, check for
an inert head.
The most-reached-for Wolfram names are curated into that warning, so
Total(xs) reports did you mean Sum and Select(xs, f) reports
did you mean Filter. The suggestion is only a pointer to the right
neighborhood — it is not an alias, and the call shape may differ
(Accumulate[xs] becomes Scan(xs, Add), with an explicit combining
function). MemberQ[xs, v] maps directly to Contains(xs, v), same
argument order.
Also worth knowing: lazy collection operators (Range, Map, Filter,
Take, Table) enumerate only when materialized, and a tuple does not
materialize its operands — (Table(k, {k, 1, 3}), 5) keeps the unevaluated
Tabulate(…). Aggregate or index where you stand.