Latte is a minimalistic functional language shipped with the Orpheus functional layer. Higher level functionality is written in Latte, making it easier to define system state.
Orpheus is the kind of small, self-contained hobby programming environment I miss from my childhood. Those are what got me excited about programming in the first place. Each one is like a fantasy world made of logic.
The current implementation has several limitations:
- Base numbers can’t go below 0.
- Functions defined in let don’t have unrestricted recursion yet.
Starting Orpheus
To start Orpheus, open a graphical terminal in an Ubuntu-compatible Linux distro and run:
git clone https://github.com/thoriumrobot/orpheus
cd orpheus
sudo chmod +x ./latte
(enter your root password when prompted)
./latte
If you already have Orpheus, you can update and run it:
cd orpheus
git pull
sudo chmod +x ./latte
(enter your root password when prompted)
./latte
Interface
In the GUI, you can open a new text window by clicking the “+ Text” button in the top bar. You can execute commands by typing them in a text window and middle clicking them.
GUI systems either run into menu labyrinths or a lack of customizability. CLIs run into the problem of awkward line editing. For example, I often keep long commands saved inside a text file. I have to edit the command as necessary, copy and paste it into the terminal. Allowing the user to execute commands by middle clicking inside what is effectively a notepad window sidesteps this issue.
Basics
The base types are atoms and pairs.
Examples of numeric atoms: 0, 1, 2, 3, …
Examples of text atoms: %hello, %heart, …
Examples of pairs: [1 2], [3 0], …
As mentioned, numbers below 0 are not base types yet.
Lists are a derived type that must end in a 0. Example: [1 2 3 0]
[1 2 3] is the pair [1 [2 3]]. This is NOT the same as the list [1 2 3 0].
Another surprise is that 0 means true. ‘if’ takes the ‘then’ branch if the condition evaluates to 0.
Comments are lines that start with ‘::’.
Arithmetic
Incrementation is the operator +():
eval +(41)
:: → 42
The other operations are defined in the standard library lib/std.lat. Functions are called with the function name first inside parentheses and other arguments separated by spaces:
eval (add 2 3)
eval (mul 6 7)
eval (div 84 2)
eval (mod 17 5):: → 5 · 42 · 42 · 2
Similarly, sub is subtraction and dec is decrement. For base types, numbers below 0 are currently unsupported:
eval (sub 3 5)
:: → error: Bottom(“sub jet: underflow”)
Use ‘import num’ for signed numbers.
Types
The ‘type’ keyword shows the type of an expression:
type [1 [2 3]]
:: → [@ [@ @]]
Provably wrong operations are flagged:
type +([1 2])
:: → type error: + expects an atom but got a cell
Conditions
The equality operator == is built-in and doesn’t go first:
eval (5 == 5)
eval (5 == 6):: → 0 (yes) · 1 (no)
Similarly to arithmetic, several other operations are defined as functions:
lt: less than
gt: greater than
lte: less than or equal
gte: greater than or equal
not: negation
and: conjunction
or: disjunction
These go first in a parenthesized call:
eval (lt 2 3)
eval (gt 2 3):: → 0 (yes) · 1 (no)
Conditions are lazy in their second argument.
Pairs and lists
[1 2 3 0] is really [1 [2 [3 0]]]:
eval head [1 2]
eval tail [1 2 3 0]
eval iscell [1 2]
eval iscell 7:: → 1 · [2 [3 0]] · 0 (yes) · 1 (no)
The standard library assumes that a list is a pair terminated by 0. Write it manually:
eval (range 5)
eval (len [1 2 3 0])
eval (reverse [1 2 3 0])
eval (append [1 2 0] [3 4 0])
eval (nth [10 20 30 0] 1):: → [0 [1 [2 [3 [4 0]]]]] · 3 · [3 [2 [1 0]]] · [1 [2 [3 [4 0]]]] · 20 (0-indexed)
Text atoms
Text atoms are stored and displayed as numbers. You can see this from the CLI if you don’t use the @t type:
eval %heart
:: → 500135191912
The GUI displays the string by default.
You can write (cat %heart %beat). Concatenation is arithmetic on bytes. Don’t add text atoms.
Branching
An if block requires both branches:
eval if (lt 3 5) then 100 else 200
:: → 100
There is also a case block:
eval let x = %move in case x of %move -> 111 ; %stop -> 222 ; _ -> 0 end
:: → 111
‘_’ is a catchall expression.
As mentioned, 0 is true.
Binding
A let block binds a name to a value in an expression. It supports sequential binding:
eval let a = 10, b = (add a 5), c = (mul b 2) in c
:: → 30
Loops
Loop with the loop/again construct. ‘loop’ requires named accumulators that are updated by ‘again’:
eval loop with [a = 0, b = 1, i = 10] : if (i == 0) then a else again(b, (add a b), (dec i)) end
:: → 55
‘again’ must be in tail position, and it only exists inside a loop block.
There is no mutation. Each pass rebinds values.
Functions
Functions are written as fn [params] -> body
They can be passed to higher-order functions:
eval (map (fn [x] -> (mul x x)) [1 2 3 4 0])
eval (filter (fn [x] -> (gt x 2)) [1 2 3 4 0])
eval (foldl (fn [a b] -> (add a b)) 0 [1 2 3 4 0]):: → [1 [4 [9 [16 0]]]] · [3 [4 0]] · 10
A function is defined inside a session using the ‘def’ keyword. To run this, you have to middle click both lines in succession:
def fact [n] = loop with [i = n, acc = 1] : if (i == 0) then acc else again((dec i), (mul acc i)) end
eval (fact 5)
:: → 120
In the current alpha release, recursion is not supported for a function defined inside a let block. The function is not in scope while it’s being bound.
Recursion is supported:
- When a function is defined inside a module. This uses the keyword ‘core’. See the next section.
- When a function is defined inside a session using the ‘def’ keyword.
Modules
‘def’ defines a function for one session. You can create a new module permanently using the ‘core’ keyword. In a text window, type and middle click the line:
System.New greet
This opens a module frame. Enter:
import std
core greet
twice = fn [x] -> (add x x)
shout = fn [w] -> (cat w %!)
end
Press the ‘Compile’ button on the frame’s header. In a text window, type and middle click:
eval (twice 21)
:: → 42
Press ‘Store’ and it persists as pkg/greet.lat.
Functions defined inside a core can call themselves.
The Standard Library
In the example module, the line ‘import std’ incorporates the standard library. Here are some of the operations it contains:
Arithmetic: dec add sub mul div mod pow
Bitwise: shl shr band bor bxor popcount
Lists: len reverse append nth member range take drop last zip enumerate
Higher order functions: map filter foldl foldr scanl any all count
Aggregation: sum maximum minimum argmax
Sorting: sort, sortby
Association lists: aget aput ahas adel
Text atoms: cat catall bytelen bytes frombytes numtext
Some of this functionality will be covered in future tutorials.
eval (sort [3 1 4 1 5 9 2 6 0])
eval (sortby (fn [a b] -> (gte a b)) [3 1 4 1 5 0])
eval (sum (range 101)):: → ascending · descending · 5050 (Gauss)