Functional programming

Table of contents

  1. Higher order functions
    1. Map
    2. Filter
    3. Reduce
    4. Count
  2. Transformations
    1. Zip
  3. Comprehensions
    1. Preliminary remark
    2. List comprehensions
    3. Set comprehensions
    4. Map comprehensions
    5. Generator comprehensions

Higher order functions

A function is called a higher order function if it takes a function as its argument or returns a function as its result. The data types List and Iterable already provide a number of such functions. The most well known are map, filter and reduce.

Map

By a.map(f) we return a copy of the list a with f applied to every element.

> list(1..4).map(|x| 2*x)
[2, 4, 6, 8]

The same function is provided by Iterable, with the difference that it returns an iterator.

> (1..).map(|x| 2*x).list(4)
[2, 4, 6, 8]

> iter(list(1..4)).map(|x| 2*x).list()
[2, 4, 6, 8]

Besides A.map(f) it is possible to write f[A]. Here A is seen as a set, and f[A] the image of the set A under f. But it works not only on sets, but on any iterable object. For example, to convert a string s into the list of its character values, use ord[s].

> ord["abcd"]
[97, 98, 99, 100]

If f is a binary operator, f[a,b] allows to operate a and b pointwise.

> a = list(1..4)
> add = |x,y| x+y
> add[a,a]
[2, 6, 4, 8]

> zip(a,a).map(|[x,y]| x+y).list()
[2, 6, 4, 8]

Filter

By a.filter(p) we filter every element from a that fulfills the predicate p. For example, let us filter all divisors of n.

> divisors = |n| list(1..n).filter(|k| n%k==0)
> divisors(12)
[1, 2, 3, 4, 6, 12]

Let us find some Pythagorean triples.

> (list(1..10)^3).filter(|[x,y,z]| x^2+y^2==z^2)
[[3, 4, 5], [4, 3, 5], [6, 8, 10], [8, 6, 10]]

Again, Iterable has a version that produces an iterator.

> list(1..).filter(|x| x%2==0).list(4)
[2, 4, 6, 8]

Reduce

By a.reduce(f) we insert f as a binary operator between the elements of a.

> 1+2+3+4+5+6+7+8+9+10
55

> list(1..10).reduce(|x,y| x+y)
55

Note that reduce works not just on lists, but on any iterable:

> (1..10).reduce(|x,y| x+y)
55

A start element may be specified.

> 1000+1+2+3+4
1010

> (1..4).reduce(1000,|x,y| x+y)
1010

Count

By a.count(p) we count how often the predicate p is fulfilled. Let us count all divisors of a number n.

> d = |n| (1..n).count(|k| n%k==0)

A number is prime if it has two divisors.

> isprime = |n| d(n)==2
> (1..).filter(isprime).list(10)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

Transformations

Zip

The operation zip(a,b) joins a and b elementwise.

> zip([1,2,3,4],["a","b","c","d"]).list()
[[1, "a"], [2, "b"], [3, "c"], [4, "d"]]

For convenience:

> zip(1..,"abcd").list()
[[1, "a"], [2, "b"], [3, "c"], [4, "d"]]

> "abcd".enum(1).list()
[[1, "a"], [2, "b"], [3, "c"], [4, "d"]]

Comprehensions

Preliminary remark

In Moss, if we want to list the square numbers x2 for x from 1 to 10, we have the following choices:

a = list(1..10).map(|x| x^2)

a = list(x^2 for x in 1..10)

a = (|x| x^2)[1..10]

a = (1..).map(|x| x^2).list(10)

a = (x^2 for x in 1..).list(10)

a = list((1..10).map(|x| x^2))

You might ask why there are so many ways to achieve this. Let me explain how the following three forms have a slightly different purpose.

a.map(f)
Maps a function f to each element of a. This is abstract and will work even for matrix data types.
f(x) for x in a
Creates an iterator which can be turned into a list, set or map. This allows multiple for- and if-clauses. Thus it can be used for constructions involving subsets of cartesian products.
f[a,b,c,...]
Performs an implicit zip-operation. Reflects the notation f[X] for the image of the set X under f.

For example, a cartesian product is created this way:

prod = |a,b| list([x,y] for x in a for y in b)

prod = |a,b| a.map(|x| b.map(|y| [x,y])).chain()

prod = |a,b| (|x| (|y| [x,y])[a])[b].chain()

prod = |a,b| a*b

The addition of two lists as if they would be vectors is achieved this way:

add = |v,w| list(zip(v,w).map(|[x,y]| x+y))

add = |v,w| list(x+y for x,y in zip(v,w))

add = |v,w| (|x,y| x+y)[v,w]

List comprehensions

A list comprehension is basically filter, map or chaining both:

a = list(1..10).filter(|x| x%2==0).map(|x| x^2)

a = list(x^2 for x in 1..10 if x%2==0)

Abstractly, we have:

a = list(r).filter(p).map(f)

a = list(f(x) for x in r if p(x))

In these expressions, r is some range or iterable, p is a predicate and f is a function/mapping. A predicate is a function that returns values of type Bool.

The predicate may be omitted:

a = list(r).map(f)

a = list(f(x) for x in r)

The mapping may be omitted:

a = list(r).filter(p)

a = list(x for x in r if p(x))

Set comprehensions

In mathematics sets are stated by set-builder notation:

{f(x) | xAp(x)}

For finite sets this can be stated in Moss:

set(f(x) for x in A if p(x))

For infinite sets you might at least want a function that depends on a number n, examining finite subsets:

B = |n| set((f(x) for x in A() if p(x)).take(n))

Map comprehensions

A map comprehension allows to inverse a map:

inv = |m| map([value,key] for key,value in m.items())

An imperative formulation of inv is slightly longer:

function inv(m)
   m2 = {}
   for key in m
      m2[m[key]] = key
   end
   return m2
end

Another example. The numbers from 0 to 25 are shuffled and a random permutation map of the latin alphabet is obtained from that:

m = map([chr(x+ord('a')),chr(y+ord('a'))]
   for x,y in zip(0..25,list(0..25).shuffle()))

For convenience:

m = map(zip("a".."z",list("a".."z").shuffle()))

Generator comprehensions

We want to find solutions of the Diophantine equation

x3 + y3 + z3 = w3.

A generator comprehension prevents the program from memory exhaustion and allows to print found solutions instantly.

g = |n| ([x,y,z,w]
   for x in 1..n for y in 1..n
   for z in 1..n for w in 1..n
   if x^3+y^3+z^3 == w^3)

g(200).each(print)