Basics
Hello, and welcome to Kronos! I'm happy to show you the ropes.
Kronos is a functional language. Instead of command sequences, you will be thinking in terms of values: computing new values based on old values.
Hello World!
Writing the "hello world" in a new programming language is a comforting ritual. As in most functional languages, the Kronos version is brief and to the point:
"Hello world!"Hello world!
By the way, that is a code box. Inside the box you can find Kronos program code, verbatim. The lines in italic show how the REPL will respond when you type in the code. If you want to try some of it yourself, please feel free to copy and paste from the code boxes. For your convenience, the example output will not be selected or copied.
Computing
The next step is to try to compute something. Let's do some arithmetic:
2 + 57
5 * 10 - 347
1 / 90.11111111
Math:Pow(2 16)65536
Please do note the spaces around the mathematical operators. Without space, Kronos will treat the entire expression as an identifier:
a = 5
b = 8
c = 3
a + b * c29
a+b*c
* E-9995: Fatal{Eval (Anon-Fn nil) Fatal{eval nil Fatal{Unbound symbol 'a+b*c'}}}
Undefined symbol 'a+b*c'.
By Unbound a+b*c
Kronos means that it thinks you want a symbol called a+b*c
and that it is not defined at the moment.
Structuring
Combining many pieces of data under one name is a useful abstraction in computer programming. We use the concept of an ordered pair as the fundamental unit of structuring. Pair algebra is used with three simple but powerful primitives: First
, Rest
and Pair
.
two-things = Pair(1 2)
two-things1 2
First(two-things)1
Rest(two-things)2
And that is basically all there is to it! Because pair algebra is so ubiquitous, there is some syntactic sugar to make life easier. Suppose we want to construct a list by chaining ordered pairs.
Pair(1 Pair(2 3))1 2 3
This can be written equivalently as
(1 2 3)1 2 3
Which is why this works, and might be a clue to why the primitives are called First
and Rest
.
First(1 2 3)1
Rest(1 2 3)2 3
First(Rest(1 2 3 4 5 6))2
Rest(Rest(1 2 3 4 5 6))3 4 5 6
Destucturing
In fact, structuring is also used when calling functions.
Add(3 5)8
Add(Pair(3 5))8
Add(two-things)3
Upon application, functions break down the structure of pairs given as the argument. That is called destructuring.
While destructuring relies on First
and Rest
internally, additional convenience comes in handy:
my-list = (1 2 3 4 5)
First(my-list)1
Rest(my-list)2 3 4 5
(x y) = my-list
x1
y2 3 4 5
In the above example, the destructuring via multiple binding ( (x y) = my-list
), is a mirrored version of the pair-building shortcut shown above. In fact, to understand multiple binding, imagine the pair structure on both left and right hand side of the definition, and have each symbol on the left bind to whatever is on the right in a structurally similar position.
Functions
Defining functions is convenient when there is a bunch of functionality that is more generally applicable. Functions receive arguments, which are symbols bound in the local scope of the function. Consider:
Square(x) {
x * x
}
Square(3)9
Square(9)81
Besides the argument, you can also define symbols that are local to the function.
Distance(x1 y1 x2 y2) {
dx = x2 - x1
dy = y2 - y1
Math:Sqrt( Square(dx) + Square(dy) )
}
Distance(0 0 3 4)5
; any symbols bound within the function do not exist outside of it
dx
* E-9995: Fatal{Eval (Anon-Fn nil) Fatal{eval nil Fatal{Unbound symbol 'dx'}}}
Undefined symbol 'dx'.
You can pass functions as arguments to other functions. This is handy with algorithms, like Map
, which can apply a function to a number of elements at once.
Algorithm:Map(Square 1 2 3 4 5 6 7 8)1 4 9 16 25 36 49 64
Anonymous Functions
Sometimes you need a lightweight, ad-hoc function with a hyper-local parameter binding. The lambda arrow is convenient for defining such.
Algorithm:Map(x => x + 100
1 2 3 4 5 6 7 8)101 102 103 104 105 106 107 108
Sections
Simple arithmetic operations are a common case for anonmyous functions. You can also use sections, that is, infix operators with missing sides. The example above can alternatively be written as
Algorithm:Map((+ 100) 1 2 3 4 5 6 7 8)101 102 103 104 105 106 107 108
Where (+ 100)
is an anonymous function that adds 100
to its argument. The handedness of the section is significant for non-commutative operators:
Algorithm:Map((/ 100) 1 2 3 4 5 6 7 8)0.0099999998 0.02 0.029999999 0.039999999 0.050000001 0.059999999 0.07 0.079999998
Algorithm:Map((100 /) 1 2 3 4 5 6 7 8)100 50 33.333332 25 20 16.666666 14.285714 12.5
By omitting both sides of an infix operator, we obtain a two-argument (binary) function. There is another algorithm, Reduce
, which combines the elements on a list with such a binary function.
; binary section
Algorithm:Reduce((+) 1 2 3 4 5 6 7 8)36
; or with a lambda
Algorithm:Reduce((a b) => (b a) 1 2 3 4 5 6 7 8)8 7 6 5 4 3 2 1