:id: lft :code :then: lft rgt :code :test: lft rgt rgt :code :void: lft rgt rgt rgt :code :pair: lft rgt rgt rgt rgt :code :fst: lft rgt rgt rgt rgt rgt :code :snd: lft rgt rgt rgt rgt rgt rgt :code :cond: lft rgt rgt rgt rgt rgt rgt rgt :code :lft: lft rgt rgt rgt rgt rgt rgt rgt rgt :code :rgt: lft rgt rgt rgt rgt rgt rgt rgt rgt rgt :code :builtin: lft rgt rgt rgt rgt rgt rgt rgt rgt rgt rgt :code print: {":id",[fst,snd,":then"],":test",":void",[fst,snd,":pair"],":fst",":snd",[fst,snd,":cond"],":lft",":rgt",[,":builtin"]}
Fine is a programming language—a programming language for manipulating structured data. What Fine means by structured data is a little weird, though. Every piece of data in Fine can have a left and a right. Structured data which has both a left and a right is called “complex”, and data which has neither is called “primitive”. In other programming languages, a complex value would be called a pair, while left-only and right-only values would be the constructors of an “Either” type.
Every program in Fine represents a function which takes in a single input and produces a single output. To write these functions, you write a sequence of commands that are evaluated left to right. Commands are usually a single word.
Let's start with what structured data looks like. Since in Fine we write programs, not data, we cannot write down data directly. Instead, we will write programs that ignore their input and produce some fixed data as output.
An upper-case word like X
is a command that ignores its input and returns that word as output:
X
Since in Fine we don't really write down data, you should see the command X
, then an arrow ⇒
, and then that command's output X
. (If you don't see that, please enable JavaScript!) Right now this is silly, but will be useful once we start writing more complicated programs.
The lft
(and rgt
) command creates a new value whose left (or right) is the input:
X lft
X rgt
It's worth digesting the X lft
program briefly. It is a program with two commands. The first, X
, ignores the input and produces the output X
, as we saw above. The second, lft
, takes that X
and makes it the left of its output.
The final result is a value with a left, X
, but no right. This output happens to be printed X lft
. This is usually convenient, though later we will see that the same data could be printed in different ways.
Finally, you can create a value with both a left and a right using brackets:
[X,Y]
Those brackets are called “pair”. The brackets combine two commands. Both are run on the input, and their results are made the left and right of the output.
You can extract the left (or right) of an input using fst
(snd
):
[A,B] fst
[A,B] snd
You can also do different things depending on whether a value has a left or a right:
lft {A,B}
rgt {A,B}
These braces are called “cond”. The combine two commands: if the value has a left, the first is run, while if it has a right, the second is run.
test
is handy primitive:
[A lft,B] test
[A rgt,B] test
test
lets you write if-then patterned code:
[1,2] [equal,] test
In this program, the first pair of brackets replaces the input with the pair [1,2]
. The second pair of brackets produces a pair whose first element tells us whether 1 and 2 are equal, and whose second element is the pair [1,2]
. Finally, the test
switches that, producing a left-only value if the two are equal, and a right-only value if they are different. See if you can trace why the final value is [2,[1, 2]] rgt
. You may need to play around with the equal
function.
To help you write big programs, you can define helper functions. You do this by naming code, which means prefixing it with a name followed by a colon:
foo: lft rgt
The check mark means the definition succeeded. The code is not run, since it only defines a helper function. Once defined, the function can be used.
X foo
Numbers are a built-in primitive (as are strings):
5
[5,5] +
5 [,] +
double: [,] +
5 double
More interesting than working with numbers is working with code, another built-in. Unlike numbers and strings, though, code is structured. You can create the fst
instruction using the command :fst
, and analogous for the other instructions:
:fst
[[:id,:id] :pair,0 :builtin] :then
:id {"is left","is right"}
:test {lft,rgt}
The last line shows that test
is represented by the data lft rgt rgt
. Fine prints code differently (in quotes), but that's just to help you read it. It's really structured data all the way down.
Another way to get code objects is to use :lookup
:
"double" :lookup
When you have some code, you run it by using the parentheses (pronounced “quote”). The code on the right is run and replaced by its result:
5 (,"double" :lookup)
A useful primitive is quote
, which turns code into code that evaluates to that code. Woah! (The instruction :code
just marks data as code without changing its structure, allowing Fine to print it nicely.)
5 quote
5 lft quote
5 lft lft quote
Note the quotes: we are creating the code 5 lft lft
, not the object 5 lft lft
. Code objects are interpreted and printed with quotes.
:id quote
Quotes make quotation even easier. The code on the left is passed to the code on the right:
([,] +,quote)
(double,quote)
How does this work?
5 (3,double)
5 (3,(double,quote))
Often you use quote
and quotations to help create code:
if: [[,:id] :pair,(test {snd lft,snd rgt},quote)] :then
This definition is a bit confusing. But try it out:
A if
[5,0] (equal,if) {[fst,"=",snd],[fst,"≠",snd]}
Another, even more confusing definition is this, which passes code to itself:
self: [[quote,:id] :pair,] :then