Getting Started
This chapter will guide you through writing and running your first Fram program.
Interactive Mode (REPL)
The easiest way to start experimenting with Fram is to use the interactive mode
(REPL). If you have dbl installed (see Installation), you
can start it in the interactive mode by running the command without
any arguments. For better readline support and ease of use we suggest using
rlwrap.
rlwrap dbl
You should see a prompt where you can enter Fram phrases terminated by ;;.
The interpreter will evaluate the phrase and print the result type and value in
the next two lines. Phrases are either simple expressions or definitions and can
span multiple lines.
> 1 + 2 ;;
: Int
= 3
> "Hello," + " " + "World!" ;;
: String
= "Hello, World!"
> let x = 40
let y = 2 ;;
> x + y ;;
: Int
= 42
> let b = True in if b then 1 else 2 ;;
: Int
= 1
>
Hello World
To create a standalone program, create a file named hello.fram with the
following content.
let _ = printStrLn "Hello, World!"
In Fram, top-level expressions must be bound to a name. The wildcard pattern _
is used here to discard the result of printStrLn (which is (), the unit
value) and execute the side effect.
Run the program using the dbl interpreter by passing the path to the file
as an argument.
dbl hello.fram
You should see the output.
Hello, World!
Basic Syntax
Comments
Line comments in Fram start with the # character and extend to the end of the
line.
# This is a comment
let x = 42 # This is also a comment
Block comments start with {# and end with #}. They can span multiple lines
or be embedded within code.
{# This is a
multiline block comment #}
let _ = printStrLn {# This is an inline block comment #} "It works!"
Definitions
Values are bound to names using the let keyword. These bindings are immutable
but can be shadowed.
let answer = 42
let message = "Hello"
Functions
Functions can be defined using let as well. The arguments follow the function
name.
let add (x : Int) (y : Int) = x + y
In the example above, the + operator resolves to a method add defined on the
type of x. As the type of x is not specified and the interpreter cannot
infer which method to use, we must annotate it explicitly. The following
examples will demonstrate operators in various contexts, showing both when
annotations are required and when they are not.
This is syntactic sugar for defining a name that holds an anonymous function
(lambda). The same function can be written using the fn keyword.
let add = fn (x : Int) (y : Int) => x + y
Functions are applied by writing the function name followed by its arguments separated by spaces.
let result = add 10 32
In order for the definition to be recursive it must be bound using let rec.
For mutual recursion between multiple definitions the rec ... end block can
be used.
let rec factorial (n : Int) =
if n == 0 then 1 else n * factorial (n - 1)
rec
let even (x : Int) =
if x == 0 then True else odd (x - 1)
let odd x =
if x == 0 then False else even (x - 1)
end
Fram uses lexical scoping, meaning that functions capture their environment at definition time and as mentioned earlier, variable bindings can be shadowed.
let x = 10
let addX y = x + y
let x = 20
let result1 = addX 5
# result1 is 15 because addX captured x = 10
let addX (x : Int) = x + 10
let result2 = addX 5
# result2 is also 15 because the parameter x shadows the outer binding
Notice that in the first definition of addX, since the type of the captured
x is known, we do not need to annotate the function parameter. In the second
definition, the argument x shadows the previous definition of x. As the type
of the new x is locally unknown, we need to annotate it so the interpreter
can correctly infer which add method to use when resolving the + operator.
Local Definitions
Local values can be bound using the let ... in ... construct. The name bound
in the let part is visible only in the expression following in.
let quadruple (x : Int) =
let doubleX = x + x in
doubleX + doubleX
Multiple local definitions can be bound one after another omitting the in
part, only placing it after the last defitnition.
let x =
let y = 21
let add (x : Int) y = x + y
in
add y y
Control Structures
Fram supports conditional expressions using if ... then ... else ....
Since it is an expression it must return a value and both branches must
have the same type.
let abs (x : Int) =
if x < 0 then -x else x
The else branch can be omitted if the result of the then branch is of the Unit type.
let printHello cond =
if cond then printStrLn "Hello"
Pattern Matching
Pattern matching is a powerful feature in Fram used to check a value against a pattern. It is most commonly used with algebraic data types.
let isEmpty list =
match list with
| [] => True
| _ => False
end
The wildcard pattern _ matches any value. Pattern matching is exhaustive,
meaning all possible cases must be covered.