Anatomy of a program
Lets examine a Halcyon program piece by piece. Here is a Fizz Buzz program:
<span class='hljs-keyword'>module</span> example =
<span class='hljs-keyword'>let</span> fizzbuzz = <span class='hljs-keyword'>fn</span> <span class='hljs-params'>number</span> <span class='hljs-params'>max</span> <span class='hljs-keyword'>=></span>
<span class='hljs-punctuation'>(</span><span class='hljs-keyword'>match</span> <span class='hljs-punctuation'>(</span>number <span class='hljs-operator'>%</span> <span class='hljs-variable constant_'>3</span><span class='hljs-punctuation'>,</span> number <span class='hljs-operator'>%</span> <span class='hljs-variable constant_'>5</span><span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>with</span>
| <span class='hljs-punctuation'>(</span><span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>,</span> <span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> <span class='hljs-string'>"FizzBuzz"</span>
| <span class='hljs-punctuation'>(</span><span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>,</span> _<span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> <span class='hljs-string'>"Fizz"</span>
| <span class='hljs-punctuation'>(</span>_<span class='hljs-punctuation'>,</span> <span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> <span class='hljs-string'>"Buzz"</span>
| <span class='hljs-punctuation'>(</span>_<span class='hljs-punctuation'>,</span> _<span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> <span class='hljs-title class_'>format</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>integer</span> number<span class='hljs-punctuation'>)</span>
<span class='hljs-operator'>|></span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>println</span><span class='hljs-punctuation'>;</span>
<span class='hljs-keyword'>if</span> number <span class='hljs-operator'><</span> max <span class='hljs-keyword'>then</span>
<span class='hljs-title function_'>fizzbuzz</span> <span class='hljs-punctuation'>(</span>number <span class='hljs-operator'>+</span> <span class='hljs-variable constant_'>1</span><span class='hljs-punctuation'>)</span> max
<span class='hljs-keyword'>else</span>
<span class='hljs-punctuation'>(</span><span class='hljs-punctuation'>)</span>
<span class='hljs-keyword'>do</span> <span class='hljs-title function_'>fizzbuzz</span> <span class='hljs-variable constant_'>1</span> <span class='hljs-variable constant_'>30</span>
<span class='hljs-keyword'>end</span>
Modules
A Halcyon source file contains one or more modules.
<span class='hljs-keyword'>module</span> example =
<span class='hljs-comment'>(* ... *)</span>
<span class='hljs-keyword'>end</span>
Modules contain four kinds of statements:
let
defines a variabledo
executes an expression (like amain
function)type
defines a new type, or type alias (described later)import
imports a function from another language, such as JavaScript or C
A module acts as a namespace.
To access a value or type from another module, the ::
symbol is used.
For example, std::println
refers to the println
function in the std
module.
Functions
Functions are a kind of expression.
They are defined using the syntax fn [parameters] => [body]
.
Here, a function is defined with two parameters number
and max
.
<span class='hljs-keyword'>let</span> fizzbuzz = <span class='hljs-keyword'>fn</span> <span class='hljs-params'>number</span> <span class='hljs-params'>max</span> <span class='hljs-keyword'>=></span>
The body of a function is a single expression after the =>
operator.
The functions return value is the value of this expression, there is no return
keyword.
The syntax for function calls is significantly different from other languages. If the argument to a function is a literal (numbers, strings, booleans, arrays, structures, etc), no parenthesis are required.
<span class='hljs-keyword'>do</span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>println</span> <span class='hljs-string'>"Hello World!"</span>
If an argument is an expression, it must be wrapped in parenthesis.
<span class='hljs-keyword'>do</span> <span class='hljs-title function_'>add_integers</span> <span class='hljs-punctuation'>(</span><span class='hljs-variable constant_'>5</span> <span class='hljs-operator'>-</span> <span class='hljs-variable constant_'>1</span><span class='hljs-punctuation'>)</span> <span class='hljs-variable constant_'>4</span>
An alternative way to call a function is with the |>
operator.
This operator calls a function on the right with an argument on the left.
<span class='hljs-keyword'>do</span> <span class='hljs-string'>"Hello World!"</span> <span class='hljs-operator'>|></span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>println</span>
It is even possible to combine both syntax.
The |>
operator has a lower precedence than a regular function call, so the argument to the left of it will come last.
<span class='hljs-keyword'>do</span> <span class='hljs-string'>"World!"</span>
<span class='hljs-operator'>|></span> <span class='hljs-title class_'>string</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>concatenate</span> <span class='hljs-string'>"Hello "</span>
<span class='hljs-comment'>(* Equivalent to:
* string::concatenate "Hello " "World!" *)</span>
<span class='hljs-operator'>|></span> <span class='hljs-title class_'>std</span><span class='hljs-punctuation'>::</span><span class='hljs-title function_'>println</span>
<span class='hljs-comment'>(* Prints "Hello World!" *)</span>
Match expressions
The match
expression is the most important control flow in Halcyon.
It is similar to, but much more powerful than switch
statements in other languages.
A match expression consists of an expression to match on (the discriminant), and a list of cases.
Here, the program is matching on the tuple (number % 3, number % 5)
:
<span class='hljs-punctuation'>(</span><span class='hljs-keyword'>match</span> <span class='hljs-punctuation'>(</span>number <span class='hljs-operator'>%</span> <span class='hljs-variable constant_'>3</span><span class='hljs-punctuation'>,</span> number <span class='hljs-operator'>%</span> <span class='hljs-variable constant_'>5</span><span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>with</span>
| <span class='hljs-punctuation'>(</span><span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>,</span> <span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> "FizzBuzz"
| <span class='hljs-punctuation'>(</span><span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>,</span> _<span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> "Fizz"
| <span class='hljs-punctuation'>(</span>_<span class='hljs-punctuation'>,</span> <span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> "Buzz"
| <span class='hljs-punctuation'>(</span>_<span class='hljs-punctuation'>,</span> _<span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> format<span class='hljs-punctuation'>::</span>integer number<span class='hljs-punctuation'>)</span>
A match case has the syntax | [pattern] => [expression]
.
The discriminant is compared with each pattern in order.
The entire expression evaluates to the case of the first pattern matched.
For example, consider the case:
| <span class='hljs-punctuation'>(</span><span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>,</span> <span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> "FizzBuzz"
The pattern (0, 0)
is matched only when the discriminant is equal to (0, 0)
.
However, this pattern:
| <span class='hljs-punctuation'>(</span><span class='hljs-variable constant_'>0</span><span class='hljs-punctuation'>,</span> _<span class='hljs-punctuation'>)</span> <span class='hljs-keyword'>=></span> "Fizz"
only requires the first value in the tuple to be 0
.
The _
symbol, or any identifier, is a 'wildcard' that matches anything.
This match expression evaluates to "FizzBuzz"
if number
is a multiple of 3, and 5,
"Fizz"
if number
is only a multiple of 3,
"Buzz"
if number
is only a multiple of 5,
and format::integer number
otherwise.
This is string is then printed.
<span class='hljs-operator'>|></span> std<span class='hljs-punctuation'>::</span>println<span class='hljs-punctuation'>;</span>
If expressions
The if
expression works exactly the same as in other lanuguages, with the exception that the else
clause is mandatory.
This is because if
is an expression, not a statement, and so it must evaluate to something in either case.
Here, the program recursively calls fizz_buzz
if number
is less than max
.
Otherwise, the function returns ()
(similar to void
or None
in other languages).
if number <span class='hljs-operator'><</span> max <span class='hljs-keyword'>then</span>
fizzbuzz <span class='hljs-punctuation'>(</span>number <span class='hljs-operator'>+</span> <span class='hljs-variable constant_'>1</span><span class='hljs-punctuation'>)</span> max
<span class='hljs-keyword'>else</span>
<span class='hljs-punctuation'>(</span><span class='hljs-punctuation'>)</span>
Comments
There are two kinds of comments, single line and block.
A single line comments begin with two dashes --
.
Block comments begin with (*
and end in *)
, and may be nested inside other block comments.
It is conventional to begin every line of a block comment with *
, but this is not required.
<span class='hljs-comment'>-- This is a line comment</span>
<span class='hljs-comment'>(*
* This is a block comment,
* it can span multiple lines<span class='hljs-comment'>
(*
* This is a nested block comment
*)</span>
*)</span>
Non-features
Mutable variables
Every variable in Halcyon is constant. Once a variable is defined, it cannot be changed.
Loops
Halcyon does not have for
or while
loops.
Instead, all looping is done through recursion.
The compiler performs tail call optimization to convert recursive functions into simple loops.
This means that your program will not overflow its stack no matter how deeply it recurses.
Tail call optimizization is only possible in certain circumstances. It is only done when the recursive call is the last operation performed in the function. The fizzbuzz example is tail recursive.
Exceptions and null
Halcyon does not have exceptions or null.
Functions that may fail instead return an opt
or result
.
These are covered more in-depth in their respective doc pages.