Table of Contents
Last update: 29.01.2025.
Wafl is a strongly typed functional programming language, with implicit type inference. It was originally designed as an experimental application of FP languages in web development. A few years later, however, Wafl became a simple and very fast scripting language, with a simple and efficient interface to databases.
The Wafl Tutorial introduces the language constructions, the use of the command line interpreter, elements of the core library and many examples.
Welcome to Wafl!
Saša Malkov
This introductory chapter covers only the basic elements of Wafl syntax and elements of using the command line interpreter. The aim is to provide some basic information that will enable us to progress further in the following chapters.
For most of this tutorial, we assume that you are using the Wafl
command line interpreter. The name of the command line interpreter may
vary depending on the version of the installation package. It can be
clWafl
, clwafl
or wafl
. In this
tutorial we assume that it is called clwafl
.
To check if the interpreter is ready, open the OS command shell and type:
clwafl
If everything is set up correctly, the output should look like this:
*** Program filename missing!
*** Wafl Command Line Interpreter usage syntax:
clwafl [<options>] <program file name> [<arguments>]
clwafl [<options>] -code <source code> [-args <arguments>]
clwafl -help
We assume that each program is written and saved in a file and then
evaluated with the command line interpreter. To evaluate a program
stored in example.wafl
, enter and execute:
clwafl example.wafl
Command line interpreter options are discussed in details in Command Line Reference.
To download Wafl, visit Wafl Downloads. The current version is available as a Linux deb package and as a Windows installation program. Each installation package contains an offline version of this tutorial.
The easiest way to calculate the character string
"Hello World!"
is to write a program that consists only of
this character string:
"Hello World!"
Hello World!
If you write this program and save it as a new file with the name
“hello.wafl
”, you can evaluate it with the Wafl command
line interpreter:
clwafl hello.wafl
If everything is set up correctly, the output should look like this:
Hello World!
Wafl is a strongly typed and eager functional programming language with implicit type inference.
Let us discuss the first sentence in detail.
Wafl is a functional programming language. A Wafl program has the syntactic form of an expression. In imperative programming we speak of the execution of a program, but here we speak instead of the calculation or evaluation of a program. The order of program execution is not explicitly determined by the program source file, but by implicitly defined rules for the evaluation of expressions.
Wafl is not a pure functional language. There are some language features that violate the strict rules of the functional paradigm, including the command line interaction subsystem, the use of files and databases, and so on. However, the core of the language is defined in such a way that we can speak of a large pure subset. Most of the examples in this tutorial use only the pure subset of the language. Impure elements are generally limited to some elements of the core library.
There are (almost) no variables, assignments, iterations, procedures and many other elements that are common in imperative programming languages. Even if there are some types of these concepts, they are not present in the usual imperative form.
The order of evaluation is not important in most cases, but where it is important, it can be assumed that all arguments of functions and operators are evaluated from left to right.
Wafl is an eager language. This means, in short, that for most functions (and operators) all arguments are fully evaluated before the function (or operator) is evaluated. There are some common exceptions, such as logical operators and conditional expressions.
However, there are also some lazy elements. The most important lazy subsystem is the processing of database queries. The query results are not retrieved completely before the query results are processed. Instead, the query result is treated as a lazy list of rows. Individual rows of the query result are retrieved as needed.
Wafl is a strongly typed programming language. Each program is completely analyzed and type-checked before evaluation. No type-related errors can occur during program evaluation. However, the Wafl syntax does not contain an explicit type specification. All types are implicitly inferred. Normally, the derived types are not displayed during the evaluation, but if a type error is detected during type checking, the corresponding derived types are reported to support problem localization.
To understand the notation of function types, we need to familiarize ourselves with the notation of types in general. This requires at least a small introduction to the Wafl type system. For exact types the notation is relatively simple, but for polymorphic types it can be more complex.
The types are never written in Wafl programs. The notation of types is only important because it is used in the interpreter’s type-checking error messages and in the Wafl documentation (including the library reference and this tutorial).
Wafl supports implicit polymorphism. This means that each function
has a strictly defined type, but is as general as possible so that it
works for all types to which its definition can apply. For example, we
can define a function add3
:
= x + y + 3; add3(x,y)
This is a definition of the function add3
. It is easy to
verify that add3
can only work for integer arguments
(because no implicit conversions are allowed and we have an explicitly
specified integer argument: 3
). We can write the type of
this function as follows:
(Int * Int -> Int)
Function types are always enclosed in parenthesis. This type
indicates that the function has two arguments of type Int
and the result of the same type Int
.
Let us now define a function add
:
= x + y; add(x,y)
The function add
can work for all types for which its
definition expression can be evaluated correctly. This includes all
types for which the binary operator ‘+
’ is defined. In
Wafl, these are the primitive types: Integer
,
Bool
and String
. The function add
therefore has a polymorphic type that includes the exact types
(Int * Int -> Int)
,
(Float * Float -> Float)
and
(String * String -> String)
. To simplify the notation,
we use the parameterized notation:
(Value['1] * Value['1] -> Value['1])
The Value
represents a particular set of types
(Int
, Float
, String
) and
Value['1]
represents an instance of the set, where
'1
is a type parameter (or type
variable). Type parameters take the form of a quotation mark
followed by an integer. A type parameter can represent any type that
fits the context (i.e. any type from the given set), but the same type
in the entire type notation. In this example, '1
is one of
the types in the set Value
- Int
,
Float
or String
, but the same in all three
occurrences.
So, if we replace '1
with specific types, one by one,
then our type (Value['1] * Value['1] -> Value['1])
virtually becomes a set of types that includes:
(Int * Int -> Int)
,
(Float * Float -> Float)
and
(String * String -> String)
.
Now, we agree that it was almost more complicated to explain this
than to use the set of types. But there are sets that are too large to
be written without a special polymorphic notation. For example, if we
have a polymorphic list of elements, then its type is denoted as
List['1]
, where '1
denotes the element type
and can be any type that Wafl supports. Thus,
List['1]
represents an infinite set of types.
List['1]
is an infinite set, at least theoretically. If
there is a List[A]
for a type A
in this set,
then there is also a type List[List[A]]
in this set, and so
on… For every natural number N
there is at least one
N
-deep list, so our set of list types is at least as large
as the set of all natural numbers, which is infinite.
Let us write a function that checks if a list is longer than a given number:
= size( list ) > n; isLongerThan( list, n )
First, we know that size
is a core library function that
returns Int
, so n
must also be of type
Int
. The expression of the function definition evaluates to
a Bool
value, so we conclude that our function will have a
type like (... * Int -> Bool)
. And what is the type of
the first argument?
In our example, we expect a list as the first argument, but there are
no any implications for its element type. Then it will be a polymorphic
list List['1]
, and the function type will be:
(List['1] * Int -> Bool)
.
But size
works not only for lists, but also for arrays.
To consider all sequence types (lists and arrays), the function type
must be more general:
(Sequence['2]['1] * Int -> Bool
where '2
stands for a sequence type (array or list) and
'1
for a sequence elements type.
In addition, size
also works for strings. Strings often
behave like sequences of characters (even though there is no new type
for characters - String
is used), so we have a more general
type class SequenceStr
that includes arrays, lists and
strings:
(SequenceStr['2]['1] * Int -> Bool
If '2
is String
, then '1
must
of course also be String
.
But that’s not all. There is a map type in Wafl, that is a
collection of elements accessed via keys. Its type is
Map['2]['1]
, where '1
is the element type and
'2
is the key type. And of course the function
size
also works for maps. So here is the final type of our
function isLongerThan
:
(Indexable['1]['2]['3] * Int -> Bool)
where '1
is the type of the indexable collection (list,
array, map or string), '2
is the type of the keys (for all
collections except maps must be Int
) and '3
is
the type of the elements.
Here is a list of some of the type classes we often use in Wafl:
Type Class / Description
Prime['1]
One of the primitive types: Int
, Float
,
Bool
and String
Value['1]
One of the types: Int
, Float
and
String
.
Numeric
One of the types Int
and Float
.
PrimeNotInt['1]
One of the types: Float
, Bool
and
String
.
PrimeNotFloat['1]
One of the types: Int
, Bool
and
String
.
PrimeNotString['1]
One of the types: Int
, Float
and
Bool
.
Sequence['2]['1]
A sequence, where '2
specifies the sequence class
(array, list) and '1
the element type.
SequenceStr['2]['1]
A sequence, where '2
specifies the sequence class
(array, list or string) and '1
the element type. If
'2
is String
, then '1
must also
be String
.
Indexable['3]['2]['1]
An indexable collection, where '3
specifies the
collection class (array, list, map or string), '2
the index
type, and '1
the element type.
There are a few other types and type classes, but this should be enough to get you started.