Wafl Tutorial

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

1 Introduction

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.

1.1 Command Line Interpreter

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.

1.2 Hello World

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!

1.3 Main Wafl Concepts

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.

The order of evaluation is not important, in most cases, but where it is important, it can be assumed that all functions’ and operators’ arguments are evaluated from left to right.

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.

1.4 Introduction to Types

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:

add3(x,y) = x + y + 3;

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:

add(x,y) = 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:

isLongerThan( list, n ) = size( 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.