This book will teach you Orion from 0 to 1 by presenting all the Orion concepts with detailed examples.
To install Orion, you will need Git, a "make" program (e.g. GNU Make), the Rust toolchain v1.51 or later and a POSIX shell (e.g. DaSH) linked to /bin/sh
.
First, clone the Orion repository with git clone https://github.com/orion-lang/orion.git
(add the --branch dev
option if you would would like to have the nightly toolchain.).
Then, enter the produced directory (cd orion/
) and make the configure
script executable (chmod +x configure
).
Finally, produce a Makefile by running ./configure
, compile the program make
and then install it with make install PREFIX=/wherever/you/want
(Please note that root access may be necessary depending of the location.).
In this part, you are going to learn the very basics needed to learn how to program in Orion.
Create a file named main.orn
. Orion files always end with the .orn extension.
Then open it in your favourite text editor and put this content in it:
(def 'impure main
(\ () (putStrLn "Hello, World !")))
Save the file, go back to your shell and run the following command:
$ orion main.orn
Hello, World !
should appear on screen.
Let's break the code down:
(def main
declares the main variable.(\ ()
declares a closure with 0 arguments.(putStrLn "Hello, World !")
displaysHello, World !
and a newline on the standard output.
Orion is a purely functional programming language, that means that it has almost no side effects, and the remaining side effects are controlled. Therefore, mutation is not possible in Orion. Now for I/O, Orion has a special system to control side effects. Haskell uses the IO monad, Pony uses an Env, and Orion uses the impure
tag. There are 2 main rules about impurity: the top-level is pure, so you cannot call an impure
-tagged function at the top-level, and you cannot use an impure
-tagged function in a non-impure variable.
It means that the following codes would fail:
(putStrLn "Hello, World !")
(def foo (putStrLn "Hello, World !"))
Try typing this in a file and running it: you should get a compilation error.
To fix the first code, you should put this line in a main
function. The main
function is the entry point of every Orion program, and it allows more control on side effects.
To fix the second, you must tag foo
as impure
.
The REPL, for Read Evaluate Print Loop is an interactive environment to try Orion code ; unlike the files', the REPL's top-level is impure.
To enter the REPL, run orion
in a shell, without passing it any arguments, you should see an interactive prompt. Then type (putStrLn "Hello, World !") 4
, and hit the return key.
You should see
Hello, World !
=> 4
Hello, World !
is the output string, and 4
is the returned value. The returned value is identified by a =>
sign before it.
There are 3 basic datatypes: Integer, Single and String.
Type | Description | Example |
---|---|---|
Integer | A 32 bits relative number | 42 |
Single | A 32 bits real number | 3.1415926535897932 |
String | A character string. | "Wafelack" |
A variable is defined using the def
keyword, followed by an identifier and an expression.
Example:
(def a 44)
(def b 3.1415)
(def c "Hello, World !")
(def b "foo") ;; Shadowing the variable.
A closure is defined with the λ
(or \
keyword), followed by zero or more arguments enclosed in parentheses and an expression.
Syntax: (λ (<ident>*) expr)
Example:
(def square ((λ (x) (* x x)))
To call a closure, you just write the closure followed by the arguments.
Syntax: (<closure> <args>*)
.
Example:
(def square (λ (n) (* n n)))
(square 13) ;; 169
Tuples are ordered, fixed size collections of data. They are made using the ,
function with zero or more values in arguments.
Syntax: (, <expr>*)
.
Example:
(def x (, 4 5 6 7))
An enumeration is a data type containing different variants, and each of these variants can contain values.
They are declared using the enum
keyword.
Syntax: (enum { '(' <ident> <ident>* ')' }*)
.
Example:
(enum List
(Cons x next)
Nil)
Enum constructors are initialized with the enum variant name and the values corresponding to the variant's data.
Syntax:(<ident> <expr>*)
.
Example:
(Cons 4 (Cons 6 Nil))
Pattern matching is the central feature of Orion. It allows to check if a value matches a pattern, and if it matches, it executes the associated expression.
Patterns can either be:
- A variable, which is bound to the matched value.
- A constructor, containing patterns.
- A tuple, containing patterns.
- An Integer, a Single or a String.
- An "Any" pattern, which matches any value.
Example:
(match (, 4 5)
((, 4 6) (do_someth)) ;; Does not match
((, x y) (nice x y)) ;; Matches, x is boud to 4 and y is bound to 5
(_ (foo))) ;; Would match if the previous pattern hasn't been matched.
Macros are like functions, but they don't evaluate the arguments. They only replace the variables in the body with the given parameter.
For example, let's take the if
macro. As a macro, it is written like this:
(macro if (cond then else)
(match cond
(True then)
(False else)))
If you run (if (= 3 3) (putStrLn "All good !") (putStrLn "What is going on ?"))
, only All good !
is displayed, but if if
was built in a standard function, like this:
(def if (λ (cond then else)
(match cond
(True then)
(False else))))
, both All good !
and What is going on ?
would be displayed.
TL;DR: Macros allows you to manipulate the AST nodes instead of the interpreter Values.
- Variables names should be in snake_case
- Enumerations and variants names should be in PascalCase
- Macros names should be in kebab-case
- Builtins names are in camelCase.
- The indentation should be made with 4 spaces.
- If the function is impure, the closure creation should be on the next line and indented.
(def 'impure main (λ () (putStrLn "Hello, World !")))
- Every expression should be indented one level more than the parent expression.
(match a (5 10)) { (putStrLn "Hello, World !") 5}
()
expands to(,)
{x}
expands to(begin x)
'x
expands to(\ () x)
[x y z]
expands to(Cons x (Cons y (Cons z Nil)))