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:

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

(List['1] -> '1)
Get a list head. Not defined on empty list.

tl

(List['1] -> List['1])
Get a list 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. It is not defined on empty lists.

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 usual in functional programming languages to have a function cons (constructs a list) equivalent to this operator. There is no cons function in Wafl, but we will often call operator ‘:cons-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:

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:

[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:

[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 suffix of the sequence with N elements:
    seq[N:]

A[ B : ]

(SequenceStr['2]['1] * Int -> SequenceStr['2]['1])
Extracts a prefix 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, mapIdx and zipWith

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)

zipWith

(Sequence['4]['1] * Sequence['4]['2] * ('1 * '2 -> '3) -> Sequence['4]['3])
Zips two sequences with given function:
    zipWith(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.asString() )

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

zipWith

Function zipWith(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.

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

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

The mapIdx function can be defined using zipWith as follows:

['a','b','c','d','e','f','g','h']
.mapIdx( \i,x: x + i.asString() )
where {
    mapIdx( lst, fun ) = (0 .. lst.length()-1).zipWith( lst, fun );
}

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

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, exists and count

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)

count

(Sequence['2]['1] * ('1 -> Bool) -> Int)
Count the sequence elements satisfying the condition:
    count(seq,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 )

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

{#
    [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) ),
    [1,2,3,4,5].count( operator>(_,2) ),
    [1,2,3,4,5].count( operator>=(_,2) )
#}

{# false, true, false, true, 3, 4 #}

5.10 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.11 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.