- Exceptions
- Delimited continuations, because continuations are cool.
- Macros (maybe)
- Attribute lookup – lenses?
- Keyword parameters
- GHC implicit parameters are interesting
(Parentheses)
, like other languages, delimit sub-expressions.
[Square brackets]
create arrays (see below).
{Braces}
create pipes and functions (see below).
Source files are UTF-8. No exceptions.
Semicolons are used to delimit elements in arrays and statements in blocks. However, they are annoying to type and easy to forget.
So this language has a simple insertion rule:
If a line ends on a symbol OTHER THAN closing brackets
}])
, merge it with the next line.Otherwise, insert a semicolon.
Example:
ducks =
sum [
x - y -
z
cheese
]
waffles
is interpreted as:
ducks = sum [ x - y - z; cheese; ]; waffles;
42
-42
0x2A
0b101010
1_048_576
Infinite precision integer type.
0x
and 0b
prefixes specify hexadecimal and binary, respectively. Octal is omitted because no-one uses it and it's a common source of newbie confusion.
Underscores are syntactic sugar. They make large numbers easier to read.
c'x'
char 'x' # `char` is a built-in function that converts a 1-char string to an actual char
Unicode code point. 4 bytes per character.
"Hello, world!"
"String with a
newline in it"
"Whitepace within \
\two backslashes is ignored, just like Haskell"
Opaque text type. Implemented as UTF-16 internally – this means indexing is O(n).
Fixed length array. These can be homogeneous (like Python lists), but are faster when all elements are of the same type.
An array literal is a sequence of elements, surrounded by '[]' and delimited by ';'. They are subject to semicolon insertion (see above).
-
Bytestring = array of bytes
b'Hello, world!' encode 'utf-8' 'Hello, world!' bytes [0x48; 0x65]
-
Matrix = array of arrays
Functions are declared as follows:
distance-sq = { x y -> add (mul x x) (mul y y) }
Notice that the identifier contains a hyphen. With few exceptions, tokens must be separated by spaces – so there is no ambiguity as to what distance-sq
means.
A function with multiple arguments is simply a function that returns another function, i.e. they are curried.
distance-sq = { x -> { y -> add (mul x x) (mul y y) } }
They can then be applied using juxtaposition, a la ML:
result = distance-sq 3 4
Functions can be composed left-to-right using the -
operator:
f = { x -> add x 1 }
g = { x -> mul x 2 }
say ((f - g) 1) # 4
say ((g - f) 1) # 3
In general, f - g
is equivalent to { x -> g (f x) }
.
Similar to conduits in Haskell or pipes in sh. A pipe awaits multiple inputs and yields multiple outputs. When it finishes, it returns a final value (called the result).
Pipes are applied using the |
operator:
read-file "story.txt" | decode "utf-8" | split "\n" | filter (contains "e") | write
|
pipes the output on the left to the input on the right. It discards the left result and returns the right one.
You may have noticed that the -
and |
operators look quite similar. This is intentional.
|
, or "horizontal" composition, runs both arguments simultaneously.-
, or "vertical" composition, runs the first argument to completion before executing the second.
Creating a pipe is easy as well:
identity = {
forever {
value = await
yield value
}
}
Using the composition operators described in the previous section, we can shorten this to:
identity = {
forever {
await - yield
}
}
forever
runs its argument in an infinite loop. await
waits for an upstream value. yield
pushes a value downstream. So the identity
pipe continually reads values, then writes them unchanged.
In fact, the whole program can be thought of as a pipe with both ends closed. Pipes are everywhere!
One important concept is the difference between the output and result values. In Python (and Aqua), the former is specified by yield
and the latter with return
. In sh, the former is standard output and the latter is the exit code.
So:
await
takes the argument and turns it into a resultyield
takes the argument and turns it into an output
Often we would need to pass a single value into a pipe. In that case, we can use yield
:
yield 15 | sum
But what do we do when we need to stream in more than one value? Surprisingly, this doesn't work:
[1; 2; 3; 4; 5] | sum # Returns 0
Why? When a non-pipe is used in a context that expects a pipe, it acts as a pipe that returns that value. Note returns, not outputs – the sum
pipe never receives anything and the array is discarded.
We can solve this by using the stream
function:
stream [1; 2; 3; 4; 5] | sum # Returns 15
This is equivalent to:
each yield [1; 2; 3; 4; 5] | sum