Table of Contents

1 Introduction

1.1 Command Line Interpreter

1.2 Hello World

1.3 Main Wafl Concepts

1.4 Introduction to Types

2 Program Structure

2.1 Program Is an Expression

2.2 Comments

2.3 Tuples

2.4 Local Definitions

2.5 Function Definition

2.6 Named Expression Definition

2.7 No Variables

2.8 The Order of the Definitions

2.9 Conditional Expression if

2.10 Conditional Expression switch

2.11 Recursion

2.12 Libraries

3 Programming With Functions

3.1 Strict Type Checking

3.2 Automatic Type Inference

3.3 Polymorphism

3.4 Higher Order Functions

3.5 Partial Application

3.6 Lambda Functions

3.7 Lambda Closures

3.8 Operators as Functions

3.9 Dot Operator

3.10 Explicit Computation State

3.11 Cached Functions

4 Primitive Types

4.1 Literals

4.2 Operators

4.3 Conversion Functions

4.4 Integer Functions

4.5 Float Functions

4.6 String Functions

5 List Type

5.1 List Literals

5.2 Basic List Functions

5.3 Basic List Processing

5.4 Advanced List Processing

5.5 More List Functions

5.6 Functions map and zip

5.7 Functions foldr and foldl

5.8 Functions filter, find and filterMap

5.9 Functions forall and exists

5.10 Functions count and countRep

5.11 Functions sort and sortBy

5.12 Functions in and contains

5.13 Lazy Lists

6 Structured Types

6.1 Array Type

6.2 Map Type

6.3 Tuple Type

6.4 Record Type

7 Elements of Wafl Library

7.1 Program Control

7.2 File Reading

7.3 File Writing

7.4 File Operations

7.5 Directory Operations

7.6 Regex Functions

7.7 Command Line Library

7.8 Web and HTTP Functions

7.9 Wafl to JSON

7.10 Wafl Program Evaluation

8 Parallelization

8.1 Wafl and Parallelization

8.2 Parallel Functions

9 Core Library Reference

9.1 Main Library

9.2 Command Line Library

9.3 Web Library

9.4 XML Library

9.5 Drawing Library (SDL)

9.6 Timer Library

9.7 Edlib Library

10 Command Line Reference

10.1 Command Line Options

10.2 ANSI Control Codes

11 More Content…

11.1 Soon…

 

 

Wafl

Wafl

Tutorial / 5 - List Type

Open printable version

5 List Type

List is the most important and most often used structured data type in functional programming languages. List is a sequential type, which means that a list may contain many elements in an ordered sequence. In Wafl, a list can consist of elements of any type, but all elements of any specific list must have the same type.

Because of the importance of the list processing concept, it is suggested that this chapter is carefully studied. The discussed functions are essential for programming in Wafl.

5.1 List Literals

The general list type is written as List['1], where '1 denotes a type variable (a pure polymorphic type). Each specific list in a program must have a specialized type, where this free type is fully determined.

A list without elements is called empty list. An empty list is written as nil or []. These two syntaxes are equivalent.

Nonempty list literals are written as elements enclosed in square brackets and separated by commas.

{#
    [],
    nil,
    [1,2,3],                    //  List[Int]
    ['a','b','c','d'],          //  List[String]
    [[1,2],[3,4,5],[6,7,8]]     //  List[List[Int]]
#}

{# [], [], [1, 2, 3], ['a', 'b', 'c', 'd'], [[1, 2], [3, 4, 5], [6, 7, 8]] #}

In the previous example we have the following lists:

  • an empty [];
  • an empty nil;
  • a list of integers with 3 elements;
  • a list of strings with 4 elements and
  • a list of list of integers, with 3 elements.

5.2 Basic List Functions

The processing of the list elements is often explained in terms of processing the list head and the list tail. The head represents the first element of the list and tail represents all but the first elements of the list. Both head and tail are meaningful only for non-empty list.

Basic list functions include elementary operations for checking the list size, extracting its head, tail or sub-list, constructing a list and comparing two lists.

Function / Type and Description

A = B

('1 * '1 -> Bool)
Equality comparison operator.

A != B

('1 * '1 -> Bool)
Inequality comparison operator.

empty

(Indexable['1]['2]['3] -> Bool)
Check if the collection is empty.

length

(Indexable['1]['2]['3] -> Int)
Get the collection size.

longerThan

(List['1] * Int -> Bool)
Check if list has more elements than the given integer.

size

(Indexable['1]['2]['3] -> Int)
Get the collection size.

hd

(Sequence['1]['2] -> '2)
Get the sequence head. Not defined on empty sequence.

tl

(Sequence['1]['2] -> Sequence['1]['2])
Get the sequence tail.

A : B

('1 * List['1] -> List['1])
List construction operator.

It may be strange to see a type like Indexable['1]['2]['3], but there is nothing special with it. It is any of the indexable collections (where '1 may be one of the indexable collections base, like list, array, map and string) with index of type '2 and elements of type '3. So, this type may reduce to List['3].

We could replace the types in the tables, but it is good to adapt to the notation.

List Comparison

Lists are comparable for equality. The operators == and != evaluate the expected results:

{#
    [] = nil,
    [] != nil,
    [] = [1,2,3],
    [] != [1,2,3],
    [1,2,3] = [1,2,3],
    [1,2,3] = [1,2,3,4,5],
    [1,2,3] != [1,2,3],
    [1,2,3] != [1,2,3,4,5]
#}

{# true, false, false, true, true, false, false, true #}

empty

To check if a list is empty, we can use function empty(l) or compare the list to the empty list literal.

{#
    empty( [] ),
    empty( nil ),
    empty( [1,2,3] ),
    [] = nil
#}

{# true, true, false, true #}

size, length and longerThan

Function length(l) computes the length of the list. Function size(l) is a synonym.

It is planned to keep both in the future. size is more general, but length is more natural for sequences.

{#
    length( [] ),
    length( nil ),
    length( [1,2,3] ),
    size( [1,2,3] )
#}

{# 0, 0, 3, 3 #}

In some cases, it is not efficient to compute the length of a list. For example, if a list represents a database query result, then the length is not computable until the complete result is fetched from the database. In such cases it is often useful to just check if the list length is greater than a number. Function longerThan(lst,n) checks if the lst is longer than n and return true if it is.

{#
    longerThan( [1,2,3], 0 ),
    longerThan( [1,2,3], 1 ),
    longerThan( [1,2,3], 2 ),
    longerThan( [1,2,3], 3 ),
    longerThan( [1,2,3], 4 ),
    longerThan( [1,2,3], 5 )
#}

{# true, true, true, false, false, false #}

hd, tl and A:B

Function hd(l) computes the first element of the given nonempty list. It is not defined on empty lists.

Function tl(l) computes the tail of the given nonempty list. The list tail is a list suffix consisting of all list elements except the first one. When applied to an empty list, it returns the same empty list.

Infix binary operator h:l constructs a list with head h and tail t. The operator type is ('1 * List['1] -> List['1]). It is often called cons-operator. (In many functional programming languages there is a function cons (constructs a list) equivalent to this operator.)

{#
    hd( [1,2,3] ),
    tl( [1,2,3] ),
    1:[2,3],
    1:2:3:nil,
    [2,3]:nil
#}

{# 1, [2, 3], [1, 2, 3], [1, 2, 3], [[2, 3]] #}

5.3 Basic List Processing

List elements are usually processed using recursion:

  • the terminal condition is the list emptiness;
  • if the list is not empty:
    • the first element of the list is processed and
    • the computation on the list tail is performed by recursive application of the same function.

We will try to apply this concept in few examples.

A List Sum

Let’s sum the elements of an integer list by applying the presented concept:

  • the terminal condition is the list emptiness;
    • the sum of the empty list is zero;
  • if the list is not empty:
    • we add the head to the sum…
    • …of the tail, which is performed by recursive application.

[1,2,3,4,5].sum()
where {
    sum( lst ) = 
        if lst.empty() then 0
        else lst.hd() + lst.tl().sum();
}

15

A List of Squares

Let’s compute a list of squares of natural numbers from 1 to 10. We may start from a list [1,2,...,10] and use a recursive function squares:

  • the terminal condition is the list emptiness;
    • the squares of the empty list is an empty list;
  • if the list is not empty:
    • we square the head and use it as a head of a new list…
    • …and the tail be the squares of the tail.

[1,2,3,4,5,6,7,8,9,10].squares()
where {
    squares( lst ) = 
        if lst.empty() then []
        else lst.hd() * lst.hd() : lst.tl().squares();
}

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

The same result may be computed based on the same idea but without explicitly writing a list literal:

squares(1,10)
where {
    squares( from, to ) = 
        if from > to
            then []
            else from * from : squares( from + 1, to );
}

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

An Integer Range

Like in the previous example, it is often useful to have a list of integers in a specific range. In this example the function mklst(n,m) computes the list of integers from n to m:

mklst(100,110)
where{
    mklst(n,m) =
        if n>m then []
        else n : mklst( n+1,m);
}

[100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110]

5.4 Advanced List Processing

In the examples of the previous section we may recognize some repeatability. Let’s focus on two topics:

  1. how to process the head element and
  2. how to combine the result of the head processing with the results on the rest of the list (tail).

It is usual to approach these two topics by two different functions. First, we will write the function processElements that processes each of the list elements and return the results in a new list in the same preserved order. Then, we will write function combineElements that combines the elements of the result list as we need.

Function processElements should have two arguments: the list of the elements to process and the processing function. This function is much like presented squares, so let’s test it by implementing the same thing, by applying it to the function \x:x*x:

[1,2,3,4,5].processElements( \x: x*x )
where {
    processElements( lst, fun ) = 
        if lst.empty() then []
        else lst.hd().fun() : lst.tl().processElements( fun );
}

[1, 4, 9, 16, 25]

Function combineElements should have three arguments: the list of the elements to combine, the function describing one combining step and the starting value. Let’s test it on the summing - the function is the operator+ and the starting value is zero:

[1,2,3,4,5].combineElements( operator+, 0 )
where {
    combineElements( lst, fun, value ) =
        if lst.empty() then value
        else lst.hd().fun( lst.tl().combineElements( fun, value ) );
}

15

Now we can use both functions for summing the squares of the numbers in a given list:

[1,2,3,4,5]
.processElements( \x: x*x )
.combineElements( operator+, 0 )
where {
    processElements( lst, fun ) = 
        if lst.empty() then []
        else lst.hd().fun() : lst.tl().processElements( fun );
    combineElements( lst, fun, value ) =
        if lst.empty() then value
        else lst.hd().fun( lst.tl().combineElements( fun, value ) );
}

55

As we can see, these two functions are very helpful. This is why the equivalents of these two functions, as well as many other higher order functions, are implemented in Wafl core library. The core library function map is equivalent for processElements, and foldr is equivalent for combineElements.

5.5 More List Functions

Some basic list functions are presented in a previous section. Sere we present some more complex list functions, before we deal with higher order functions in the following sections.

Type SequenceStr['2]['1] includes sequences and strings. Sequences are indexable types with integer indexes.

sub, subList

Function subList(l,p,n) computes a sub-list of the given list, beginning at position p and with n elements.

Function / Type and Description

sub

(SequenceStr['2]['1] * Int * Int -> SequenceStr['2]['1])
Extracts the subsequence from given 0-based position and given length:
    sub(seq,pos,len)

subList

(List['1] * Int * Int -> List['1])
Extracts the sub-list from given 0-based position and given length:
    subList(list,pos,len)

It has the same semantics as already presented sub and subStr for strings. Moreover, sub works for lists as well.

{#
    sub( [0,1,2,3,4,5,6,7], 0, 3 ),
    sub( [0,1,2,3,4,5,6,7], 2, 3 ),
    sub( [0,1,2,3,4,5,6,7], -2, 5 ),
    sub( [0,1,2,3,4,5,6,7], 5, 10 ),
    sub( [0,1,2,3,4,5,6,7], 5, -2 )
#}

{# [0, 1, 2], [2, 3, 4], [], [5, 6, 7], [] #}

List Index and Slice Operators

Function / Type and Description

A[ : B ]

(SequenceStr['2]['1] * Int -> SequenceStr['2]['1])
Extracts a prefix of the sequence with N elements:
    seq[:N]

A[ B : ]

(SequenceStr['2]['1] * Int -> SequenceStr['2]['1])
Extracts a suffix of the sequence with N elements:
    seq[N:]

A[ B : C ]

(SequenceStr['2]['1] * Int * Int -> SequenceStr['2]['1])
Extracts a sequence segment from Nth to (M-1)th element:
    seq[N:M]

A[ B ]

(Indexable['1]['2]['3] * '2 -> '3)
Extracts an element from the indexable collection:
    col[idx]

Index operator lst[i] and slice operators lst[:n], lst[:n] and lst[n:m] have the same semantics as for strings, so we will not discuss it in details here.

Note that indexing operator is not defined on empty lists.

{# 
  lst[-4], lst[-3], lst[-2], lst[-1],
  lst[0], lst[1], lst[2], lst[3], 
  lst[4], lst[6], lst[7], lst[8]
#}
where {
  lst = [0,1,2,3];
}

{# 0, 1, 2, 3, 0, 1, 2, 3, 0, 2, 3, 0 #}

{# 
    lst[:6],
    lst[:-2],
    lst[2:],
    lst[-6:],
    lst[2:6], 
    lst[2:-2], 
    lst[-6:6],
    lst[-6:-2]
#}
where {
  lst = [1,2,3,4,5,6,7,8];
}

{# [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6], [3, 4, 5, 6, 7, 8], [3, 4, 5, 6, 7, 8], [3, 4, 5, 6], [3, 4, 5, 6], [3, 4, 5, 6], [3, 4, 5, 6] #}

append and A ++ B

Function / Type and Description

A ++ B

(List['1] * List['1] -> List['1])
Append the second list elements to the first one.

append

(List['1] * List['1] -> List['1])
Append the second list elements to the first one.

Function append(l,r) computes a list containing all elements of the two lists in the preserved order. Operator ‘++’ is a synonym with infix operator notation:

{#
    [1,2,3] ++ [4,5,6],
    append( [1,2,3], [4,5,6] )
#}

{# [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6] #}

A..B, intRange, intRangeBy

Function / Type and Description

A .. B

(Int * Int -> List[Int])
Create a list of integers in the range:
    2..5 = [2,3,4,5]

intRange

(Int * Int -> List[Int])
Create a list of integers in the range:
    intRange(2,5) = [2,3,4]

intRangeBy

(Int * Int * Int -> List[Int])
Create a list of integers in the given range with a given step:
    intRange(2,9,2) = [2,4,6,8]

intRangeWithLast

(Int * Int -> List[Int])
Create a list of integers in the range:
    intRangeWithLast(2,5) = [2,3,4,5]

Integer range operator n..m computes the list of integers from n to m, including both limits. If n < m, the elements will be in the increasing order, ant if n > m, the elements will be in the decreasing order:

{#
    0..10,
    -5..5,
    5..-5
#}

{# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5], [5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5] #}

No parenthesis nor brackets are required when using the operator A..B. Moreover, if brackets are used, the [1..2] will compute [[1,2]] and not the expected [1,2].

Function intRangeWithLast is a synonym for the operator A..B.

Function intRange(n,m) is similar, but will not include the end of the range m in the list.

Function intRangeBy(n,m,d) creates a list of integers from n to m but uses a step d instead of 1. As with intRange, its result does not include the ending m value.

{#
    5..9,
    intRange(5,9),
    intRangeBy(5,9,2),
    intRangeWithLast(5,9)
#}

{# [5, 6, 7, 8, 9], [5, 6, 7, 8], [5, 7], [5, 6, 7, 8, 9] #}

5.6 Functions map and zip

Function / Type and Description

map

(Sequence['2]['1] * ('1 -> '3) -> Sequence['2]['3])
Map the function to sequence elements:
    map(seq,fun)

mapIdx

(Sequence['1]['2] * (Int * '2 -> '3) -> Sequence['1]['3])
Map the function to sequence elements:
    mapIdx(seq,fun)

zip

(Sequence['4]['1] * Sequence['4]['2] -> Sequence['4][Tuple['1, '2]])
Zips two sequences to a sequence of pairs.

zipBy

(Sequence['4]['1] * Sequence['4]['2] * ('1 * '2 -> '3) -> Sequence['4]['3])
Zips two sequences by given function:
    zipBy(seq1, seq2 ,fun)

zipByIdx

(Sequence['4]['1] * Sequence['4]['2] * (Int * '1 * '2 -> '3) -> Sequence['4]['3])
Zips two sequences and indexes by given function:
    zipByIdx(seq1, seq2 ,fun)

map

The function map( lst, fun ) is one of the most important higher order functions. It maps the list lst to a new list with the same number of elements. Each element x of the lst is mapped to fun(x) element of the new list, in the same order.

This function is equivalent to our function processElements from the previous examples.

In the following example map is used to compute a list of elements increased by one:

[1,2,3,4,5,6,7,8,9,10].map( \x: x+1 )

[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

The same result:

[1,2,3,4,5,6,7,8,9,10].map( operator+(_,1) )

[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

We will use map in many of the following examples.

mapIdx

Sometimes it may be useful to know at what position in the list is the processed element. We can achieve that by transforming (mapping) a list of values to a list of pairs (index, value) and than mapping that list of pairs where indexes are available. However, it is more efficient to have a specific function mapIdx(lst,fun), where fun(idx,x) maps the value x at position idx.

['a','b','c','d','e','f','g','h']
.mapIdx( \i,x: x + i$ )

['a0', 'b1', 'c2', 'd3', 'e4', 'f5', 'g6', 'h7']

zip

Function zip(lst1,lst2) returns a list of tuples, where each tuple contains the corresponding elements of the two given lists lst1 and lst2.

zip( [1,2,3], ['A','B','C'])

[{# 1, 'A' #}, {# 2, 'B' #}, {# 3, 'C' #}]

zipBy

Function zipBy(lst1,lst2,fun) returns a list where each element is a result of the fun(x,y) applied to the corresponding elements of the two given lists lst1 and lst2.

zipBy( ['a','b','c'], ['A','B','C'], operator+ )

['aA', 'bB', 'cC']

zipByIdx

Function zipByIdx(lst1,lst2,fun) returns a list where each element is a result of the fun(i,x,y) applied to the corresponding elements of the two given lists lst1 and lst2 and their index (position).

zipByIdx( ['a','b','c'], ['A','B','C'], 
    \i,x,y: i$ + '-' + x + '-' + y 
)

['0-a-A', '1-b-B', '2-c-C']

5.7 Functions foldr and foldl

Function / Type and Description

foldr

(Sequence['3]['2] * ('2 * '1 -> '1) * '1 -> '1)
Right associative folding of sequence elements:
    foldr([a,b,c],fn,zero) = fn(a,fn(b,fn(c,zero)))

foldl

(Sequence['3]['2] * ('1 * '2 -> '1) * '1 -> '1)
Left associative folding of sequence elements:
    foldl([a,b,c],fn,zero) = fn(fn(fn(zero,a),b),c)

foldr

Like the map, function foldr( lst, fun, zero ) is one of the most important higher order functions. It combines the lst elements, using zero as a starting value, and applying fun(x,res) co combine the element x with the result res of already completed combining.

This function is equivalent to our function combineElements from the previous examples. So let’s use it in an equivalent example:

[1,2,3,4,5].foldr( operator+, 0 )

15

To better understand what foldr evaluates, consider the following example:

['1','2','3','4','5'].foldr( \x,res: '('+x+','+res+')', '' )

(1,(2,(3,(4,(5,)))))

As we can see, foldr combines elements by combining the list head with already combined list tail. Thus, it really begins the combining from the end of the list, or from the right side, which is why it has an r at the end of the name.

foldl

If the combining function fun as an associative one (i.e. fun(fun(a,b),c) = fun(a,fun(b,c))), then the order of combining is not important. But for non-associative functions, like in the previous example the order is important. This is why we have foldl.

Function foldl combines the list elements from the beginning. It first combines zero and the list head, and then combines the result with the next list element and so on. Consider the following example and compare its result to the previous example, where foldr was used with the same arguments:

['1','2','3','4','5'].foldl( \x,res: '('+x+','+res+')', '' )

(((((,1),2),3),4),5)

Few Examples

If the result of the combining operation has the same type as the list elements, then the combining function has both arguments and the result of the same type. As we already emphasized, if the combining function is associative, then foldr and foldl evaluate the same result. Here we can confirm that for addition:

{#
    ['1','2','3','4','5'].foldr( operator+, '' ),
    ['1','2','3','4','5'].foldl( operator+, '' )
#}

{# '12345', '12345' #}

On the other side, if the function is not associative, but has the same argument types, then the result will not be the same:

{#
    lst.foldr( fun, '' ),
    lst.foldl( fun, '' )
#}
where {
    lst = ['1','2','3','4','5'];
    fun(x,y) = '(' + x + ',' + y +')';
}

{# '(1,(2,(3,(4,(5,)))))', '(((((,1),2),3),4),5)' #}

As we can see, foldl combines elements by combining the given previous result with the list head, assuming that the result will be passed further to the tail, for the same processing. Thus, it really begins the combining from the beginning of the list, or from the left side, which is why it has an l at the end of the name.

There are many combining functions that cannot be used with both foldr and foldl. If the result of the combining operation has a different type than the list elements, then the used combining function has to have different argument types, also. Such functions cannot be used with both foldr and foldl.

For example, let us write the append2 function that appends two lists (the equivalent to the library function append and operator ++). We can do that by combining the elements of the first list using cons-operator, and using the second list as a zero. Because cons-operator is not associative (moreover, it has the different argument types) it is only possible to use foldr:

[1,2,3].foldr( operator:, [4,5,6])

[1, 2, 3, 4, 5, 6]

In a similar way, we may append many lists:

[[1,2],[3,4],[5,6]].foldr( append, [])

[1, 2, 3, 4, 5, 6]

To use the cons-operator with foldl we need to reverse the arguments. But the result of the application will be a reversed list:

[1,2,3,4,5,6].foldl( \x,y: y:x, [] )

[6, 5, 4, 3, 2, 1]

The following example illustrates how to use foldr to compute a list of distinct elements of a sorted list. The combining lambda function assumes that the second argument is the already processed tail:

(1..100)
.map( operator%(_,10) )
.sort()
.distinct()
where {
    distinct( lst ) = 
        lst.foldr(
            \x,dist:
                if dist.empty() or x != dist.hd()
                    then x : dist
                    else dist
            , []
        );
}

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

5.8 Functions filter, find and filterMap

Function / Type and Description

filter

(Sequence['2]['1] * ('1 -> Bool) -> Sequence['2]['1])
Extracts the sequence elements which satisfy the given condition

filterN

(Sequence['2]['1] * ('1 -> Bool) * Int -> Sequence['2]['1])
Extracts at most N elements which satisfy the given condition.

find

(Sequence['2]['1] * ('1 -> Bool) -> Sequence['2][Int])
Finds the indexes of the elements which satisfy the given condition.

findN

(Sequence['2]['1] * ('1 -> Bool) * Int -> Sequence['2][Int])
Find at most N indexes of the elements which satisfy the given condition.

filterMap

(Sequence['3]['1] * ('1 -> Bool) * ('1 -> '2) -> Sequence['3]['2])
Maps a function to sequence elements which satisfy the condition.
    mapFilter(seq,cond,fn) == map(filter(seq,cond),fn)

filter

Function filter(lst,cond) computes the list of lst elements that satisfy the given condition cond(x). The element order is preserved.

{#
    [1,2,3,4,5]->filter( operator>(_,1)),   // numbers > 1
    [1,2,3,4,5]->filter( \x : x%2 > 0 ),    // even numbers
    [1,2,3,4,5]->filter( \x : x%2 = 0 )     // odd numbers
#}

{# [2, 3, 4, 5], [1, 3, 5], [2, 4] #}

filterN

Function filterN(lst,cond,n) computes the list of lst elements that satisfy the given condition cond(x), but it selects at most the first n elements satisfying the condition.

The element order is preserved.

{#
    [1,2,3,4,5]->filterN( operator>(_,1), 2 ), // numbers > 1
    [1,2,3,4,5]->filterN( \x : x%2 > 0, 1 ),   // even numbers
    [1,2,3,4,5]->filterN( \x : x%2 = 0, 1 )    // odd numbers
#}

{# [2, 3], [1], [2] #}

find

Function find(lst,cond) computes the list of zero-based indexes of lst elements that satisfy the given condition cond(x).

The indexes are selected in the increasing order.

{#
    [1,2,3,4,5]->find( operator>(_,1)),   // numbers > 1
    [1,2,3,4,5]->find( \x : x%2 > 0 ),    // even numbers
    [1,2,3,4,5]->find( \x : x%2 = 0 )     // odd numbers
#}

{# [1, 2, 3, 4], [0, 2, 4], [1, 3] #}

findN

Function findN(lst,cond,n) computes the list of zero-based indexes of lst elements that satisfy the given condition cond(x), but it selects at most the first n elements satisfying the condition.

The indexes are selected in the increasing order.

{#
    [1,2,3,4,5]->findN( operator>(_,1), 2 ),  // numbers > 1
    [1,2,3,4,5]->findN( \x : x%2 > 0, 1 ),    // even numbers
    [1,2,3,4,5]->findN( \x : x%2 = 0, 1 )     // odd numbers
#}

{# [1, 2], [0], [1] #}

filterMap

Function filter is often used to select some list elements before they are processed by map. This is why filterMap function is included, to do both the filtering and the processing in a same step.

Function filterMap(lst,filtFun,mapFun) is equivalent to the corresponding combination of filter and map. The following example computes a list of squares of odd list elements:

{#
    list.filterMap( odd, square ),
    list.filter(odd).map(square)
#}
where {
    list = 1..20;
    odd( n ) = n % 2 != 0;
    square( n ) = n * n;
}

{# [1, 9, 25, 49, 81, 121, 169, 225, 289, 361], [1, 9, 25, 49, 81, 121, 169, 225, 289, 361] #}

Function filterMap does not make the program code more readable, but it is more efficient, because it does not create a temporary list, as when separate filter and map are used.

5.9 Functions forall and exists

Function / Type and Description

forall

(Sequence['2]['1] * ('1 -> Bool) -> Bool)
Check if the given condition applies to all sequence elements:
    forall(list,cond).

exists

(Sequence['2]['1] * ('1 -> Bool) -> Bool)
Check if there is any sequence element satisfying the condition:
    exists(sequence,cond)

Function forall(lst,cond) checks if all list elements satisfy the given condition cond(x). It is equivalent to:

forall( lst , cond ) == lst.foldr( operator&&, true )

Function exists(lst,cond) checks if any list element satisfies the given condition cond(x). It is equivalent to:

exists( lst , cond ) == lst.foldr( operator||, false )

{#
    [1,2,3,4,5].forall( operator>(_,1) ),
    [1,2,3,4,5].forall( operator>=(_,1) ),
    [1,2,3,4,5].exists( operator<(_,1) ),
    [1,2,3,4,5].exists( operator<=(_,1) )
#}

{# false, true, false, true #}

5.10 Functions count and countRep

Function / Type and Description

count

(Sequence['2]['1] * ('1 -> Bool) -> Int)
Count the sequence elements satisfying the condition:
    count(seq,cond)

countRep

(Sequence['2]['1] -> Map['1][Int])
Create a catalog from the sequence. Keys are the distinct elements, and values are the number of occurrences.

Function count(lst,cond) counts how many list elements satisfy the given condition cond(x).

{#
    [1,2,3,4,5].count( operator>(_,2) ),
    [1,2,3,4,5].count( operator>=(_,2) )
#}

{# 3, 4 #}

Function countRep(lst) counts how many times each distinct element is repeated in the list. It returns a catalog, where the keys are all distinct list elements and the values are the number of occurrences.

['a','b','a','a','b','c'].countRep()

[<'a',3>, <'b',2>, <'c',1>]

['a','b','a','a','b','c'].countRep()['b']

2

5.11 Functions sort and sortBy

Function / Type and Description

sort

(Sequence['2][Value['1]] -> Sequence['2][Value['1]])
Sort value sequence.

sortBy

(Sequence['2]['1] * ('1 * '1 -> Bool) -> Sequence['2]['1])
Sort the sequence using given comparator.

Function sort(lst) return list sorted in increasing order. It works for sequences with elements of primitive types.

Function sortBy(lst,lessThan) return list sorted in increasing order, using the given comparison function lessThan(x,y). It works for sequences with any element types, if a corresponding comparison function is provided.

{#
    [2,1,4,3,5].sort(),
    [2,1,4,3,5].sortBy( operator< ),
    [2,1,4,3,5].sortBy( operator> )
#}

{# [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [5, 4, 3, 2, 1] #}

5.12 Functions in and contains

Function / Type and Description

in

('1 * Sequence['2]['1] -> Bool)
Checks if the element is in the sequence:
    el.in(seq)

contains

(Sequence['2]['1] * '1 -> Bool)
Checks if the element is in the sequence:
    seq.contains(el)

Function in(x,lst) checks if the list lst contains x. It is usually applied in a form x.in(lst). It works for sequences of any type.

{#
    1 .in([1,2,3]),
    11 .in([1,2,3]),
    {a:5}.in([ {a:4}, {a:5} ]),
    {a:6}.in([ {a:4}, {a:5} ])
#}

{# true, false, true, false #}

Function contains does the same, but with reversed operand order: contains(lst,x) is the same as in(x,lst).

{#
    [1,2,3].contains( 1 ),
    [1,2,3].contains( 11 ),
    [ {a:4}, {a:5} ].contains( {a:5} ),
    [ {a:4}, {a:5} ].contains( {a:6} )
#}

{# true, false, true, false #}

5.13 Lazy Lists

Wafl is an eager language, but like in the most of the languages, there are some lazy elements to prevent unnecessary computations. One such lazy element is the fetching of the database query results. The query result is a lazy list - from the database server only as much rows are fetched as required in the computation.

However, sometimes we know that we need to process all rows, but that may take some time and it may be useful to finish the communication with the database server as soon as possible. In such cases we can require the eager evaluation of the query list using the force function.

Function / Type and Description

forced

(List['1] -> List['1])
Returns the list, but forces any delayed evaluation.

Function force(lst) returns the same list, but forces its eager evaluation.

Wafl Home            Downloads            Wafl Tutorial v.0.6.8 © 2021-2024 Saša Malkov            Open printable version