Skip to main content

Epsil for Mathematica Users

A working translation guide for anyone coming from the Wolfram Language. Every Epsil 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; the library is written in lowercase (simplify, solve, integrate, limit, series, factor, expand) and the Wolfram spelling with a capital works too (simplify, solve); D, N and the linear-algebra operators keep their names; user names are lowercase and shadow a library name by scope; {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:

  1. Function application uses parentheses: f(x), not f[x]. Square brackets are indexing (Wolfram's [[…]]).
  2. {…} is a set, not a list. An Epsil 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}.
  3. = assigns only as a whole statement; inside an expression it is Equal. -> is a key/value pair. := always assigns and == always compares (as in Wolfram), but replacement rules must be written Rule(x, 3).
  4. There is no %, no Out[], and no notebook history. % is the remainder operator.

Expressions and Evaluation​

WolframEpsil
f[x], sin[x]f(x), sin(x)
x = 5let x = 5
f[x_] := x^2f(x) = x^2
f = Function[x, x^2]f = x => x^2
#^2 &x => x^2 — no slot/& syntax
expr /. x -> 3replaceAll(expr, Rule(x, 3))
a == b, SameQ[a, b]a == b, a === b — see below
expr // Nexpr |> N (or ~>)
N[expr], N[expr, 25]N(expr), N(expr, 25)
Hold[expr]HoldValues(expr) — evaluate with assigned symbols kept symbolic
SetAttributes[f, HoldAll]; f[e_] := …hold f(e) = … — the whole definition holds its arguments; there is no per-argument HoldFirst/HoldRest (read an argument once into a let to evaluate it)
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 Epsil 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​

WolframEpsil
{1, 2, 3} (list)[1, 2, 3] — braces make a set
xs[[i]]xs[i] — 1-based, as in Wolfram
xs[[-1]], first, last, restxs[-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, flattensame 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 /@ xsmap(f, xs) — same order
fold[f, init, xs]fold(f, init, xs)
apply[f, {a, b}], f @@ tapply(f, (a, b)), or spread: f(...t)
position[xs, v]indexOf(xs, v)
append[xs, v], joinappend(xs, v), join(xs, ys)
tally, partitionsame names (tally returns a (values, counts) pair)
<|"a" -> 1|> (association){"a" -> 1}; read with d["a"] or d.a, enumerate with keys/values
union, intersectionsame 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]))
// ➔ (TypeFrom("set<integer>"), TypeFrom("vector<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(g, 1..4)))
// ➔ (10, 34)

Control Flow and Pattern Matching​

WolframEpsil
If[c, a, b]a if c else b, or 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) — Epsil 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(classify, [-2, 0, 5])
// ➔ ["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:

WolframEpsil
simplify, expand, factorsame 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, eigenvaluesdeterminant, inverse, transpose, eigenvalues
dot, cross, linearSolvesame names
pi, Infinity, I, Epi, Infinity, i, e — lowercase
PrimeQ, nextPrime, factorInteger, divisorsisPrime, 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 writeWhat actually happensWrite instead
f[x]f indexed at x — an incompatible-type error value, not a callf(x)
{1, 2, 3} for a listA set: unordered, deduplicated, not indexable by position[1, 2, 3]
E, IOrdinary undeclared symbols — they stay symbolic, silentlye, i
expr /. x -> 3-> builds a KeyValuePair, not a RulereplaceAll(expr, Rule(x, 3))
% for the last result% is the Mod operatorbind results with let
x = 4 inside solveWorks as expected — inside an expression = is Equal, so solve(x^2 = 4, x) is the equation(nothing to change)
expr; to suppress; only separates statements(nothing to suppress)
Total, Select, Cases, MemberQ, Accumulate, NestUnknown names: the call stays symbolic and inert, with a did-you-mean warning naming the Epsil operatorsum, filter, filter, contains(xs, v), scan, iterate
Ceiling, Quotient, IntegerPartInert (with a did-you-mean warning)ceil, floor(a/b), floor
StringLengthlength(s) — a string is a collection of its characters
toUpperCase, toLowerCaseSame names, same meaning(nothing to change)
stringReplace[s, t -> r]stringReplace takes positional arguments, not rulesstringReplace(s, t, r)
StringJoin["ab", "cd"]Silently different. stringJoin takes ONE collection plus an optional separator, and a string is a collection of its characters — so this reads as "join "ab"'s characters with the separator "cd"" and gives "acdb"join("ab", "cd"), or "\(a)\(b)"
StringRiffle[parts, sep]Unknown name: the call stays symbolic and inert. The collection-plus-separator form is stringJoin's second argumentstringJoin(parts, sep)
StringPosition, StringContainsQ, StringStartsQ, StringEndsQUnknown names: inert. The Epsil family is generic over indexed collections and character-wise on strings, and rangeOf answers one span (or nothing), not a list of spansrangeOf(s, t), containsSequence, startsWith, endsWith
StringTrim, StringPadLeft, StringPadRightUnknown names: inert (StringTrim gets a did-you-mean warning)trim/trimStart/trimEnd, padStart, padEnd
ToExpression["3.14"]Unknown name: inert. Parsing a numeral is its own operator, and answers an error value (never NaN) on text that is not onenumberFrom("3.14")
RandomReal[], RandomInteger[n]Inert (with a did-you-mean warning)random(), random(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!^2Diagnostic — the lexer reads !^ as one token3! ^ 2
a +bDiagnostic — an infix operator needs spaces on both sides or neithera + b or a+b

The rows about inert names deserve emphasis: an unknown name is not an error. Epsil 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.

Next​