Purity of gobba code
From Wikipedia:
In computer programming, a pure function is a function that has the following properties: Its return value is the same for the same arguments (no variation with local static variables, non-local variables, mutable reference arguments or input streams from I/O devices). Its evaluation has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or I/O streams).
The gobba interpreter statically infers whether the expressions you run in your
programs are numerical, pure or impure. An impure expression is a
computation that calls primitives that have side effects, such as direct memory
access or I/O access. Pure expressions are those expressions that do not imply
side effects. Numerical expressions are the purest computations that operate
only on numerical values. By default, the interpreter is in a uncertain
state,
it means that it will allow evaluation of pure expressions, and will allow
impure code to be executed only if inside an impure
statement.
To be impure
, an expression must contain an explicit impure
statement or a
call to an impure language primitive such as IO:print
. You can enforce purity
explicitely, inside an expression by wrapping it into the pure
and impure
statements, followed by an expression.
It is good practice to reduce the use of the pure/impure
keywords as much as
possible, and to avoid using it inside of function bodies. This means keeping
your code as purely functional as you can.
Here is an example:
let bad_function = fun x ->
impure (let mystring =
"I am a bad impure function! Also: " ++ x in
IO:print_endline mystring );
let good_function = fun x ->
IO:print_endline ("I am a good function! Also: " ++ x) ;
The following function call is causing side effects and will error
bad_function "hello!" ;
The above will error, because it is trying to execute an impure computation in a pure environment
good_function "hello! I should error" ;
Here's a good way of calling it
impure $ good_function "hello!" ;
You can specify that you DO NOT want to compute impure expressions by using the pure statement We want the following to error because it contains an impure computation.
pure $ good_function "henlo world! I should error" ;
The above will error because a pure contest does not allow nesting an impure contest inside
pure $ bad_function "ciao mondo! I should error" ;
A good way of structuring your code is keeping pure/impure
statements as
external from expressions as you can (towards the top level).
Sequencing (>>) operator
Keep in mind that every expression is either numerical, pure or impure.
You may like, as in imperative languages, to do operations sequentially one after another.
Newcomers may be confused by the ;
symbol at the end of expressions. This special symbol signals gobba that the current command is over, and everything after ;
is another command.
A command is either a global declaration, a directive call or an expression to be evaluated.
You may want to sequence operations inside of expressions. To do so, you can use the >>
operator.
The >>
operator is called the sequencing operator or the then operator and what it does is straightforward:
Evaluate the expression on the left, discard the result and then evaluate the expression on the right.
Here's a simple example: since 12.4 is greater than 3, this snippet of code will
print "Good maths!" on the standard output, and then (>>
) the variable c
will be assigned to a * c
, precisely 37.2
.
let a = 12.4 and b = 3 ;
let c =
if a < b then
impure $ IO:print_endline "Bad maths!" >>
a + b
else
impure $ IO:print_endline "Good maths!" >>
a * b ;
Function pipes (reverse composition) and composition
You can redirect the result of a function to the first argument of another
function using the >=>
operator. The <=<
operator does the same thing but in reverse.
The following example will output 6, because 2 + 3 is piped into z + 1
let sum_and_add_one = (fun x y -> x + y) >=> (fun z -> z + 1) ;
sum_and_add_one 2 3
The composition operators yield the same result as normal function composition:
let my_sum = (fun x y -> x + y) ;
let add_one = (fun z -> z + 1) ;
(add_one <=< my_sum) 2 3 = add_one (my_sum 2 3) ;
The operator <=<
means compose, the following example evaluates to true:
(add_one <=< my_sum) = (my_sum >=> add_one) ;