Lesson 2 — Tutorial
Factorial
The usual “Hello World” analog for functional languages consists of a recursive function definition for calculating factorial of a number. The following variation for MANOOL incorporates in addition some test code:
-- Factorial -- recursive version in MANOOL-ish ("cascading") notation
{ {extern "manool.org.18/std/0.6/all"} in
: let rec
{ Fact = -- compile-time constant binding
{ proc { N } as -- precondition: 0 <= N
: if N == 0 then 1 else
N * Fact[N - 1]
}
}
in
Out.WriteLine["Factorial of 10 = " Fact[10]]
}
And the following equivalent program (up to AST) is intended to make the syntactic structure of the above program a bit more apparent:
-- Factorial -- recursive version in conventional notation (equivalent to the above code, up to AST)
{ {extern "manool.org.18/std/0.6/all"} in
{ let rec
{ Fact = -- compile-time constant binding
{ proc { N } as -- precondition: 0 <= N
{ if N == 0 then 1 else
N * Fact[N - 1]
}
}
}
in
Out.WriteLine["Factorial of 10 = " Fact[10]]
}
}
(here another piece of syntactic sugar is demonstrated — any two constructs
{
a0 a1 … am-1 {
b0 b1 … bn-1}
}
and
{
a0 a1 … am-1:
b0 b1 … bn-1}
are always equivalent one to another).
Output:
Factorial of 10 = 3628800
How it works
-
The expression
{proc {N} as ...}
resembles a lambda-expression in many languages, whereN
would specify a parameter and the expression that followsas
would be the body. The whole expression evaluates to a procedure, which returns the result of evaluation of the body.1 -
During compilation of the expression
{let rec {Fact = ...} in ...}
, a binding betweenFact
and the entity specified on the right-hand side of the infix operator=
is injected into the scope that followsin
.2 Since we uselet rec
and not justlet
here, the right-hand side expression is also included in the scope ofFact
, so we can refer to it recursively. -
The construct
{if ... then ... else ...}
is a conditional expression here. During its evaluation either of the two branches is evaluated depending on whether the condition specified betweenif
andthen
holds and producing the result for the whole expression.
Iterative version
Although MANOOL has a functional core, it is a multiparadigm language, for which an iterative version of the factorial function, which uses a while
-loop (or
for
-loop), may be more appropriate:3
-- Factorial -- iterative version (in MANOOL, this is probably more appropriate for factorial)
{ {extern "manool.org.18/std/0.6/all"} in
: let
{ Fact = -- compile-time constant binding
{ proc { N } as -- precondition: 0 <= N
: var { Res = 1 } in -- variable binding
: do Res after -- return result
: while N <> 0 do -- loop while N not equals zero
Res = N * Res; N = N - 1
}
}
in
Out.WriteLine["Factorial of 10 is "; Fact[10]]
}
(the output is the same as above).
How it works
-
During compilation of the expression
{var {Res = 1} in ...}
, the body expression(s), which followin
, are considered in a binding environment with a temporary variable namedRes
injected.4 The variable is initialized to1
just before evaluating the body expression(s). -
The expression
{do Res after ...}
is equivalent to{do ...; Res}
, which is evaluated by evaluating its constituents one by one, and thus this expression evaluates toRes
. -
The expression
{while ... do ...}
is a traditionalwhile
-loop. During its evaluation, the body expression(s), which followdo
, are evaluated repetitively, one by one, while the pre-condition specified betweenwhile
anddo
holds. -
Res = N * Res
andN = N - 1
are assignment expressions. As a side effect of an evaluation of such expression, the current value of the location specified on the left-hand side of the=
operator is replaced with the value specified on the right-hand side of the=
operator.
Value Comparisons, Data Typing Issues
Let's see how comparison operations work in MANOOL and how the principle of strong data typing affects certain aspects of the MANOOL semantics.
Comparing for equality
First, any pair of values in MANOOL (even of different types) can be always compared for equality/inequality, two values of different types simply being deemed unequal (even if they “look” similar):
{ {extern "manool.org.18/std/0.6/all"} in
Out.WriteLine[2 == 2 ", " 2 <> 2 ", " "2" == "2" ", " "2" <> "2"]
Out.WriteLine[2 == "2" ", " 2 <> "2" ", " "2" == 2 ", " "2" <> 2]
}
Output:
True, False, True, False
False, True, False, True
(In MANOOL True
and False
are members of a special data type Boolean.)
Other operations
Any pair of integral values can be also compared for less-than as well as less-than-or-equal, greater-than, and greater-then-or-equal:
{ {extern "manool.org.18/std/0.6/all"} in
Out.WriteLine[2 < 3 ", " 3 < 2 ", " 2 < 2] -- less-than
Out.WriteLine[2 <= 3 ", " 2 > 3 ", " 2 >= 3] -- less-than-or-equal, greater-than, greater-then-or-equal
}
Output:
True, False, False
True, False, False
On the other hand, two values of different types cannot be compared for less-than, etc.; also, the +
operator cannot be applied to an integer and a string and
vice versa:
{{extern "manool.org.18/std/0.6/all"} in Out.WriteLine[2 < "3"]} -- run-time error
{{extern "manool.org.18/std/0.6/all"} in Out.WriteLine[2 + "3"]} -- run-time error
{{extern "manool.org.18/std/0.6/all"} in Out.WriteLine["2" + 3]} -- run-time error
Output:
Uncaught signal TypeMismatch
====== invocation backtrace ======
00 at (<anonymous>) 1:56-1:62 evaluating
======== end of backtrace ========
(The expression does not evaluate to any value here; such outcome is called in MANOOL signaling an exception.)
And in the following example, the string "2"
does not even “know” how to compare itself for less-than with another value, even with another string:
{{extern "manool.org.18/std/0.6/all"} in Out.WriteLine["2" < "3"]} -- run-time error
{{extern "manool.org.18/std/0.6/all"} in Out.WriteLine["2" < 3]} -- run-time error
Output:
Uncaught signal UnrecognizedOperation
====== invocation backtrace ======
00 at (<anonymous>) 1:56-1:62 evaluating
======== end of backtrace ========
Type predicates
For many data types in MANOOL, there exists a type predicate, a Boolean-valued procedure that determines whether its argument belongs to the underlying type:
{ {extern "manool.org.18/std/0.6/all"} in
Out.WriteLine[2.IsI48[] ", " "2".IsI48[]] -- is "2" an integer?
Out.WriteLine[2.IsS8[] ", " "2".IsS8[]] -- is "2" a string?
}
Output:
True, False
False, True
Compound Conditions
You can express complex conditions by using operators &
(conjunction for Booleans), |
(disjunction for Booleans), and ~
(negation for Booleans). The
operators &
and |
are short-circuiting (the right-hand side is unevaluated unless strictly necessary):
{{extern "manool.org.18/std/0.6/all"} in Out.WriteLine["2".IsI48[] & ("2" < 3 "), " ~"2".IsI48[] | ("2" < 3)]}
Output:
False, True
More Exceptions
The MANOOL specification is precise about which exception is signaled in each particular erroneous case. We are already familiar with two of them:
TypeMismatch
and UnrecognizedOperation
. Let's look at other basic signals:5
First, an inappropriate number of arguments is reported by signaling InvalidInvocation
as in
{{extern "manool.org.18/std/0.6/all"} in Neg[2; 3]}
Arithmetic exceptions
Overflows during arithmetic operations are reported using signals:
{{extern "manool.org.18/std/0.6/all"} in MaxI48 + 1}
Signals: Overflow
The same applies to division by zero and other operations near a pole of the argument (e.g. near zero for logarithms):
{{extern "manool.org.18/std/0.6/all"} in 1 / 0}
Signals: DivisionByZero
In other cases Undefined
may be signaled:
{{extern "manool.org.18/std/0.6/all"} in 0 / 0}
{{extern "manool.org.18/std/0.6/all"} in 1.Rem[0]}
Signals: Undefined
Quiz
Try to figure out what is going on here (you should have acquired all the clues after completing Lesson 3):
{{extern "manool.org.18/std/0.6/all"} in Out.WriteLine[(&)]}
Compilation error:
(<anonymous>) 1:42-1:62 Error: not an R-value expression (nested in this context)
And here:
{{extern "manool.org.18/std/0.6/all"} in Out.WriteLine[then]}
Compilation error:
(<anonymous>) 1:42-1:58 Error: unbound keyword (nested in this context)