Table of Contents
Wafl is a strongly typed functional programming language, with implicit type inference. It was initially designed as an experimental application of FP languages in Web development. However, few years later, Wafl became a simple and very fast scripting language, with a simple and efficient interface to databases.
Wafl Tutorial presents the language constructions, command line interpreter usage, elements of the core library and a lot of 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. Its goal is to provide some basic information that will allow us to go further in the following chapters.
For most of this tutorial, we assume the use of the Wafl command line
interpreter. The name of the command line interpreter program may vary
depending on the version of the installation package. It may be
clWafl
, clwafl
or wafl
. In this
tutorial we assume that it is clwafl
.
To check if the interpreter is ready, open the OS command shell and type:
clwafl
If everything is set up well, 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 using the command line interpreter. To evaluate a program
saved in example.wafl
, type and run:
clwafl example.wafl
Command line interpreter options are discussed in details in Command Line Reference.
To download Wafl, visit Wafl Downloads. Current version is available as a Linux deb package and as a Windows installer. Each installation package includes an offline version of this tutorial.
To simplest way to compute "Hello World!"
string is to
write the program consisting of only that string value:
"Hello World!"
Hello World!
If you write this program and save it as a new file with name
“hello.wafl
”, then you can use Wafl command line
interpreter to evaluate it:
clwafl hello.wafl
If everything is set up well, the output should look like this:
Hello World!
Wafl is a strongly typed eager functional programming language, with implicit type inference.
Let’s discuss the first sentence in details.
Wafl is a functional programming language. Wafl program has syntactical form of an expression. In imperative programming we talk about program execution, but here we talk about program computation or evaluation instead. Program processing flow is not explicitly determined by the program source file, but by implicitly defined rules for expression evaluation.
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 language core is defined in such a way that we can talk about its large pure subset. Most examples in the 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 usual in imperative programming languages. Even if there are some kinds of these concepts, they are not present in the usual imperative form.
Wafl is an eager language. That means, in short, that for the most of the functions (and operators), all arguments are fully evaluated before the evaluation of the function (or operator). There are some usual exemptions, like logical operators and conditional expressions.
However, there are some lazy elements. The most important lazy subsystem is database query processing. The query results are not fully fetched before the query result processing. The query result is treated as a lazy list of rows, instead.
Wafl is a strongly typed programming language. Each program is fully analyzed and type-checked before the evaluation. There can be no type-related errors during the program evaluation. However, Wafl syntax does not include explicit type specification. All types are implicitly inferred. Usually, the program evaluation does not present the inferred definition types to users, but if any type error is detected during the type checking, all inferred types are reported to support the problem localization.
To be able to understand the notation of function types, we need to get to know the types notation. That requires at least a light introduction to Wafl type system. For exact types, notation is relatively simple, but for polymorphic types, it can be more complex.
The types are never written in Wafl programs. The types notation is important only because it is used in interpreter messages on type-checking errors and in Wafl documentation (including library reference and this tutorial).
Wafl support implicit polymorphism. That means that each function
will have a strictly determined type, but as general as possible, to
make it work for all types for which its definition may apply. For
example, we can define a function add3
:
= x + y + 3; add3(x,y)
This is a definition of function add3
. It is easy to
check that add3
can work only for integer arguments
(because no implicit conversions are allowed and we have an explicitly
specified integer: 3
). We can write this function type
as:
(Int * Int -> Int)
Function types are always in parenthesis. This type says that
function has two arguments of Int
types and the result of
the same Int
type.
Let us now define a function add
:
= x + y; add(x,y)
The function add
can work for any types for which its
definition expression can evaluate correctly. That includes all types
that have binary operator ‘+
’ defined. In Wafl that
includes primitive types: Integer
, Bool
and
String
. So, function add
have a polymorphic
type that includes exact types (Int * Int -> Int)
,
(Float * Float -> Float)
and
(String * String -> String)
. To make it easier to write,
we use the parametrized notation:
(Value['1] * Value['1] -> Value['1])
The Value
represents a specific 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 have the form of a quote followed by an
integer. A type parameter can represent any type which can correspond to
the context (i.e. any type from the given set), but the same type in the
whole type notation. In this example '1
is one of the
Value
types - Int
, Float
or
String
, but the same in all three occurrences.
So, if we replace '1
by 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)
.
Well, we agree that it was almost more complex to explain that than
to use the set of types. But there are sets which 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 in theory. If
there is a List[A]
in this set, for a type A
,
then List[List[A]]
is also in this set, and so on… For any
natural number N
there is at least 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 have Int
type, too. The function definition expression evaluates 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 on its element type. It will then be a polymorphic
list List['1]
, and the function type will be:
(List['1] * Int -> Bool)
.
But size
does not work only for lists, it also works for
arrays. To include all sequence types (lists and arrays), the function
type has to be more general:
(Sequence['2]['1] * Int -> Bool
where '2
represents a sequence type (array or list) and
'1
represents a sequence elements type.
Moreover, size
works for strings, too. Strings often
behave as sequences of characters (even if there is not a 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
Of course, if '2
is String
, then
'1
has to be String
also.
But that’s not all. There is a map type in Wafl, that
represents a collection of elements accessed by keys. Its type is
Map['2]['1]
, where '1
is the element type and
'2
is the key type. And, of course, the size
function works for maps as well. So, here is the final type of our
function isLongerThan
:
(Indexable['1]['2]['3] * Int -> Bool)
where '1
is the type of indexable collection (list,
array, map or string), '2
is the type of keys (for all but
maps this has to be Int
) and '3
is the element
type.
Here is a list of some type classes that we often use in Wafl:
Type Class / Description
Prime['1]
On of the primitive types: Int
, Float
,
Bool
and String
Value['1]
On of the types: Int
, Float
and
String
.
Numeric
On of the types Int
and Float
.
PrimeNotInt['1]
On of the types: Float
, Bool
and
String
.
PrimeNotFloat['1]
On of the types: Int
, Bool
and
String
.
PrimeNotString['1]
On of the types: Int
, Float
and
Bool
.
Sequence['2]['1]
A sequence, where '2
denotes the sequence class (array,
list) and '1
denotes the element type.
SequenceStr['2]['1]
A sequence, where '2
denotes the sequence class (array,
list or string) and '1
denotes the element type. If
'2
is String
, then '1
has to be
String
also.
Indexable['3]['2]['1]
An indexable collection, where '3
denotes the collection
class (array, list, map or string), '2
denotes the index
type, and '1
denotes the element type.
There are some other types and type classes, but this should be enough for the start.