Polynomix is a language that mixes array-based, procedural, and functional programming into one peculiar package. Sounds intimidating? Don't worry—you can safely ignore all the buzzwords for now and just start writing code.
`Hello, world' >.
Run this, and the console prints Hello, world!. That's it. You've written your first Polynomix program. Everything after this is just details.
Well... important details. But still.
Before we do anything fancy, let's use Polynomix as a glorified calculator:
1 + 2
The result is 3. Shocker.
Besides the usual + - * / %, Polynomix also gives you greatest common divisor & and least common multiple |. Because sometimes you just need to factor some numbers, okay?
Note: - is not a negative sign. To write a negative number, use a backslash: \10 means -10. This will feel weird at first. You'll get used to it.
Note: No decimals. But you do get arbitrary-precision fractions! So 1.5 is written as 3/2. Your inner mathematician will appreciate this.
Note: Expressions are evaluated strictly left to right. There is no operator precedence. 2 + 3 * 4 is 20, not 14, because the interpreter sees it as (2 + 3) * 4.
If this upsets you, use parentheses:
2 + (3 * 4)
Now it's 14. Order has been restored.
Use # to assign values:
10 # a
This assigns 10 to a. But if you run this in the console, you'll get a NameError. Why? Because a doesn't exist yet—you can't assign to something that doesn't exist.
To define a new variable, prefix it with \:
10 # \a
Now a exists and holds the value 10. The \ is a one-time thing: you only need it when you're creating the variable for the first time.
Before we go further, let's get some vocabulary out of the way:
a-. This is postfix style—get used to it, because Polynomix loves it.a + (b c...)—the first argument, then the operator, then the remaining arguments wrapped in parentheses.Reminder: No operator precedence. Left to right. Always.
Semicolons force an operator to behave monadically. You'll need this when a value appears right after a monadic operator.
Say you want the list [\2 3] using monadic -. You might try:
[2- 3]
But this gives [\1]. The interpreter sees 3 after - and assumes you want dyadic subtraction: 2 - 3 = \1.
To fix it, add a semicolon to tell - to stay monadic:
[2-; 3]
Now you get [\2 3]. The semicolon is like saying "stop, this operator is done, move along."
Like C, Polynomix uses {} for code blocks. A block runs its statements in order and returns the last value. Handy for grouping things together.
Use ? for conditionals:
cond ? block $$ if(cond){block}
cond ? (val1 val2) $$ if(cond){val1}else{val2}
cond ? (val1 cond2 val2 val3) $$ if(cond){val1}else if(cond2){val2}else{val3}
...
Conditions and code blocks alternate, and the last item is always a code block. Once a true branch is found, the rest are skipped.
cond @ block
The dyadic @ repeats a block while the condition holds. If you make the condition a code block too, you get a do-while loop. Sneaky, but it works.
Bool values are written as (1|) (true) and (0|) (false). Yes, the notation is unusual. Roll with it.
Boolean operators:
x & y (and)x | y (or)x ^ y (xor)x - (not)Comparison operators that return Bool:
x | — convert to Bool (truthiness check)x < y — less than (no greater-than operator—deal with it.)x = y — equal (for not-equal, write x = y -.)These work on every data type. Yes, even Lists. Yes, even functions. We'll get there.
Implement the Collatz conjecture. Here's the pseudocode:
n = 54
while n != 1
if n % 2 == 0
n = n / 2
else
n = 3 * n + 1
end if
print n
print " "
end while
Hint: monadic <. prints a value. Use `blah' for strings.
Remember how there's no greater-than operator? That's because > is reserved for something better: functions.
The syntax is simple: arguments on the left of >, body on the right. The body can contain multiple statements, just like a code block.
(x > x + 1) # \increment
Call it with !, which turns a name into an operator:
5 increment! $$ 6
Multi-argument functions work too—they behave like operators:
(x y > x + y) # \add 5 add! 6 $$ 11 (t x y > 1 - t * x + (t * y)) # \lerp 0.5 lerp!(1 2) $$ 1.5
Don't need a name? Skip the assignment and use the function inline:
5 (x>x+1)! $$ 6
You've seen @ as a dyadic while loop. Monadically, it means "return this value from the function."
(x >
x = 0 ? (1 @) $$ If x is 0, return 1.
x - 1 factorial! * x $$ Recursive call (bad style).
) # \factorial
That factorial! inside the function works, but it's fragile—what if you rename the function? Use \. instead, which is the dedicated recursion operator:
(x >
x = 0 ? (1 @)
x - 1 \. * x $$ Recursion, the proper way.
) # \factorial
\. always refers to "this function." No matter what you name it, recursion just works.
Rewrite the Collatz conjecture using recursion instead of a while loop.
Arrays—called Lists in Polynomix—hold multiple values in order. Create one with square brackets:
[1 2 3]
Monadic , wraps a single value in a List. Stack them for extra nesting:
1,,,, $$ [[[[1]]]] — it's Lists all the way down
Strings are just Lists of Chars, which means they're Lists all the way down too:
["h "e "l "l "o] $$ Same as `hello'
Lists have their own set of operators. Here are the essentials:
x < — length of x.x + y — concatenate two Lists.x * y — repeat the List y times (y must be a number).x / y — get the yth element (0-indexed).x /(y z) — return x with its yth element replaced by z.Polynomix has 8 data types and a unique notation for types:
$;$ Null: only one value, which is \;(Null literal). It represents nothing, emptiness, void—the philosophical abyss. It's also falsy, which feels about right.$+$ Expr: numbers. Can be positive, negative, zero, infinity, or NaN. Yes, you can have infinity. Whether you should is a different question.$=$ Bool: the classic true or false. Written as (1|) and (0|) respectively. We talked about these in the Boolean operators section—reunion!$"$ Char: a single character. Written with a double-quote prefix: "a, "Z, "\n(newline). Fun fact: 65 ~ gives you "A, and "A < gives you 65. It's like ASCII cosplay.$[]$ List: an ordered collection that can hold anything. We've met Lists already. They're the social butterflies of Polynomix—happy to mix Chars, Exprs, Bools, even other Lists in one big group.$,$ Pair: a 2-tuple. Use the comma operator to create one: 3, 4 gives you (3,4). Think of a Pair as a List that's been told "two is enough." Access the first element with <, swap the two with / monadically. Pairs are also how you do slicing on Lists—[1 2 3 4 5] /(1,3) gives [2 3].$/$ Type: a type is itself a value. Wild, right? Write $+$ and you get the Expr type as an object. Use & monadically to get any value's type: 42 & gives $+$. Use & dyadically to check if a value matches a type: 42 & $+$ gives (1|).$>$ Func: functions are values too. You can put them in Lists, pass them to other functions, assign them to variables—they're first-class citizens. The function type uses > because that's the function definition syntax.Beyond the 8 built-in types, you can create your own types using structs. A struct is simply a value with a label attached—no methods, no inheritance, no drama.
3,4 Point.
This creates a Point struct wrapping the pair (3,4). The period after the name is what triggers the struct construction. Use < monadically to unwrap it: 3,4 Point. < gives (3,4).
Structs are matched by name in the type system: $Point$ matches any Point struct. This lets you distinguish between, say, a Point and a Vector, even if they both wrap a Pair of numbers.
As the Polynomix wisdom goes: "A struct holds data with dignity; a class inherits only debt."
Since types are first-class values, type checking is just a normal operation:
42 & $$ Get the type: $+$ 42 & $+$ $$ Is 42 an Expr? (1|) `hello' & $["]$ $$ Is it a string (List of Char)? (1|) 42 & $[]$ $$ Is 42 a List? (0|)
The TypeString notation supports wildcards ($*$), unions (${+|[]}$ means Expr or List), and even capture groups for generic-style constraints. But that's advanced territory—save it for when you're comfortable with the basics.
Adverbs are modifiers that change how an operator behaves. You've already met one: !, which turns anything into an operator. But there are more.
\We've seen \ used as a negative sign (\10 = -10) and to define variables (\a). But when placed before an operator, it becomes the ranklift adjective—it makes the operator broadcast over a List.
[1 2 3] \+ 10 $$ [11 12 13] [1 2 3] \* 2 $$ [2 4 6] [`apple'`banana'`orange'] \< $$ [5 6 6] — each element's length
It works with functions too:
[1 2 3 4] \(x > x * x)! $$ [1 4 9 16]
!We've used ! to call named functions: 5 increment!. But it does more—it can reference any operator:
+! $$ The "+" operator as a function literal
!!Double exclamation commutes an operator—it swaps (or duplicates) the operands:
3 -!! 5 $$ 2 — same as 5 - 3 10 /!! 2 $$ 1/5 — same as 2 / 10 3 *!! $$ 9 — same as 3 * 3 (multiply 3 by 3)
This is handy when you want to use an operator "backwards" without rewriting your expression.
So far we've been writing functions the explicit way, with named arguments and >. But Polynomix has another trick: trains—a way to compose functions without naming any arguments at all. The idea comes from APL.
Write a train inside { }, using only operators or verb-noun phrases (i.e. an operator with a right operand but not a left one) - no named arguments:
[10 20 30] {~.+!/<}
This computes the average of the list: 20. Let's break down what's happening.
A train with two functions is called an atop. It takes one argument, feeds it through the right function, then passes the result to the left:
2 {+-} 5 $$ \7 - add, then negate
{f g} means: apply f first, then g. It's function composition: g(f(x)).
A train with three or more functions is called a fork. It takes two arguments (or more, if you have more branches), applies the outer functions to them independently, then combines the results with the middle function:
10 {/,%} 3 $$ 3,1 - pack quotient and remainder into a pair
{f g h} means: g(f(...), h(...)). The left and right branches process the same inputs, and the center merges their outputs.
Now back to the average: {~.+!/<}. This is a 3-function fork:
~.+! — reduce with addition (sum)/ — divide< — lengthSo the fork computes: sum(x) / length(x). That's the average, in 6 characters and zero named variables.
If you've used APL, you'll recognize this as (+/÷ρ). Polynomix extends the idea: a fork can have any number of branches, not just two. The center function receives as many arguments as there are branches.
Trains are not always the right tool—sometimes naming your arguments makes code clearer. But for short, punchy compositions, they're hard to beat.
Use : for try-catch. The dyadic form catches errors:
3 2! : ___ $$ If error throws, we catch it and return the string
Monadic : throws a value as an error:
`something went wrong' : $$ Throws an error with the message
It's like try-catch, but with fewer keywords and more symbols. You'll get used to it.
There are several ways to generate Lists without typing every element:
5 = $$ [0 1 2 3 4] — range from 0 to 4 2 =(1 10) $$ [1 3 5 7 9] — range from 1 to 10, step 2
[3 1 4 1 5] - $$ [3 1 4 5] — remove duplicates [3 1 4 1 5] + $$ [2 3 0 1 4] — sort index (rank of each element) [3 1 4 1 5] +. [2 3 0 1 4] = $$ [1 1 3 4 5] - use the index to sort the elements [1 2 3] / $$ [3 2 1] — reverse [1 [2 3] [4]] = $$ [1 2 3 4] — flatten [1 2 3 4] - 2 $$ [1 2],[3 4] - split at index 2
Use ~. to reduce a List with an operator:
[1 2 3 4] ~. +! $$ 10 — sum [1 2 3 4] ~. *! $$ 24 — product
And @. for prefix reduce (running totals):
[1 2 3 4] @. +! $$ [1 3 6 10] — running sum
(x >
x < 2 ? (x @)
x - 1 \. + (x - 2 \.)
) # \fib
10 fib! $$ 55
(x>x<<2?(x@)x-1#\(h,,t)t\(y>h#;y<h/!!(1,0))!+.!!t\\./(1,1 h,,)=) # \qsort
A sorting algorithm in 63 symbols. Not bad for a language that doesn't even have a greater-than operator.
Congratulations—you've made it through the tutorial! Here's what you can do next:
for loop.The best way to learn is to write code that breaks, stare at the error message, fix it, and repeat. You've got this.