Functions

Table of contents

  1. Recursion
  2. Passing functions
  3. Currying
  4. Closures
  5. Reference parameters
  6. Variadic functions
  7. Optional arguments
  8. Named arguments
  9. Argument list unpacking
  10. Argument count
  11. Methods
  12. Function application
  13. Multiple return values
  14. Static variables
  15. Lazy evaluation

Recursion

Let sum be a function that takes two arguments a,b and returns their sum a+b. There is a recursive algorithm which only needs the functions successor and predecessor to perform the computation.

succ = |x| x+1
pred = |x| x-1

sum = |a,b| a if b==0 else sum(succ(a),pred(b))

Inside of the last function there is a call to the global variable sum which points to the function itself. Note that because of this we are not allowed to change the name of the function and overwrite its old name. Instead of using a global variable we can use a private variable which will point to the function itself more directly. Then overwriting the old name becomes possible.

sum = fn f|a,b| a if b==0 else f(succ(a),pred(b)) end

# Now valid
old_sum = sum
sum = some_other_function

Passing functions

Normal control flow has a specific restriction which is dissolved if we regard functions as objects that can be stored in variables and passed to other functions.

In normal control flow a program can call a subprogram which can call another subprogram which can call another subprogram and so on. The program can choose which subprogram is called by using if-statements, but knows already what subprograms are avaiable. What if the program does not know about the subprograms it can call? Not that the number of possible subprograms is too large, but that the program really does not know the subprogram in before.

If the subprogram to call is stored in a global variable, a compound object or passed as an argument, the program only needs to know the subprogram just before the call.

One of the easiest examples is the function map which applies a given function to each element of a list.

function map(a,f)
   b = []
   for x in a
      b.push(f(x))
   end
   return b
end

print(map([1,2,3,4],|x| 2*x))
# Output:
# [2, 4, 6, 8]

Interestingly, passing functions allows the construction of custom control structures. A basic for loop is written without much effort.

function For(a,b,f)
   i=a
   while i<=b
      f(i)
      i+=1
   end
end

For(1,4,fn|i|
   print(i, "^ = ", str(2^i).rjust(2,"0"))
end)
# 2^1 = 02
# 2^2 = 04
# 2^3 = 08
# 2^4 = 16

Currying

Let us implement numerical differentiation. The first way one can think to do this, is to use the definition of the derivative directly.

function diff(f,x)
   h = 0.001
   return (f(x+h)-f(x))/h
end

This is a little bit naive. The following algorithm does it better, because it cancels out errors.

function diff(f,x)
   h = 0.001
   return (f(x+h)-f(x-h)/(2*h)
end

The differential operator may also be seen as an operator that takes a function and returns a function (the derivative). If we use currying, we can use both views directly.

D = |f| |x| diff(f,x)

Now D is the differential operator, D(f) is the first derivative and D(f)(x) is the same as diff(f,x). And that simply is currying. The operator D is the curried form of diff.

Closures

In Moss we are able to generate functions from data at runtime. To achieve this, it must be possible to bind some data to a function dynamicly. Such a binding is called closure.

A simple example is the conversion of a list a into a function f, so that f(i)==a[i] for all indices of a.

> seq = |a| |i| a[i]
> f = seq([4,2,3,1])

Furthermore, the bound data can itself be a function, because data, that are all kinds of objects. A basic example is again the differential operator.

Reference parameters

Moss has only (only has) "call by value". The following code does not work, because city is called by value. We cannot modify the variable city from inside of the function stars.

function stars(s)
   s = "*"+s+"*"
end

city = "London"
stars(city)
print(city)
# London

If we want reference parameters we have to use a reference variable. Such a variable can be simulated as a list with only one element.

function stars(a)
   a[0] = "*"+a[0]+"*"
end

city = ["London"]
stars(city)
print(city[0])
# *London*

A table object can be used instead of a list. If there are many arguments, this has better readability.

function stars(s)
   s.value = "*"+s.value+"*"
end

city = table{value = "London"}
stars(city)
print(city.value)
# *London*

Variadic functions

If the formal argument of a function is prefixed by an asterisk, the argument list might have any argument count and will be automatically converted into a list.

> p = |*a| a.join("|")
> p(1,2,3,4)
"1|2|3|4"

There may be obligatory arguments.

> p = |x,y,*a| a.join("|",x,y)
> p("(",")",1,2,3,4)
"(1|2|3|4)"

In most situations it is preferable to use explicit lists instead. That is, to simply remove the asterisks. Then the function call is as follows.

> p("(",")",[1,2,3,4])
"(1|2|3|4)"

Optional arguments

For the last formal argument(s), a default value may be specified. If the actual argument is omitted, this default value is taken.

> p = |x,y,sep=", "| "({}{}{})" % [x,sep,y]
> p(1,2)
"(1, 2)"

> p(1,2,"|")
"(1|2)"

You may even branch to different behavior.

> p = |x,y=null| p1(x) if y is null else p2(x,y)

Named arguments

Named arguments can be simulated by maps.

> p = |m| "({}{}{})" % [m["x"],m["sep"],m["y"]]
> p({x=1,y=2,sep="|"})
"(1|2)"

> p = |m| "({x}{sep}{y})" % m
> p({x=1,y=2,sep="|"})
"(1|2)"

For convenience, such a map can be unpacked, allowing named default arguments:

function p(m)
   {x,y,sep=", "} = m
   return "({}{}{})" % [x,sep,y]
end

function p({x,y,sep=", "})
   return "({}{}{})" % [x,sep,y]
end

A default configuration may be given for everything:

function p(m={})
   {x="x", y="y", sep=", "} = m
   return "({}{}{})" % [x,sep,y]
end

Curly brackets around actual arguments may be omitted:

> p({x=1, y=2, sep="|"})
"(1|2)"

> p(x=1, y=2, sep="|")
"(1|2)"

Positional and named arguments may be mixed:

function p(x,y,m={})
   {sep=", "} = m
   return "({}{}{})" % [x,sep,y]
end
> p(1,2,sep="|")
"(1|2)"

> p(sep="|",1,2)
"(1|2)"

> p(1,2)
"(1, 2)"

In many situations it is desirable to check for invalid named arguments, but this must be done explicitely. One does so by depleting the argument map and checking whether some arguments are leftover:

use unpack: drain, assert_empty

function p(m)
   {x,y,sep} = drain(m)
   assert_empty(m)
   return "({}{}{})" % [x,sep,y]
end
> p(x=1,y=2,sep="|",left="(",right=")")
Traceback:
  in p
  in assert_empty
Value error: unexpected named arguments:
  {"left": "(", "right": ")"}.

Note that a function that takes named arguments needs to have a fixed number of positional arguments. If the function shall be variadic or take optional positional arguments, those arguments must be put into a list:

function p(a,m)
   {sep} = m
   return a.join(sep,"(",")")
end
> p([1,2,3,4],sep="|")
"(1|2|3|4)"

Argument list unpacking

Sometimes we want a variadic function f to take the elements of a list as its arguments. To make this task simple, there is the generalized application operation f(*a). The prefix operator "*" is called list unpacking operator, also known as splat.

# a variadic summation function
> s = |*a| a.sum()

# a normal call
> s(1,2,3,4)
10

# we want to do this
> a = [1,2,3,4]
> s(a[0],a[1],a[2],a[3])

# more pleasant, via unpacking
> a = [1,2,3,4]
> s(*a)
10

Now we can precisely state a feature of print.

# One can transform
print("abcd") ≡≡ print("a","b","c","d")
≡≡ print(*["a","b","c","d"]) ≡≡ print(*list("abcd"))

# So, in general, if s is a string
print(s) ≡≡ print(*list(s))

Unpacking may be mixed with implicit and explicit self arguments. So, if x is an object and m is a two argument method of x, we have

x.m(a,b) ≡≡ x.m(*[a,b]) ≡≡ x.m(x;*[a,b]).

Argument count

If a function f is not variadic, it has a fixed number of arguments. This number is determined by f.argc().

An application is a general truth table printing program, that determines the table to print based on the number of arguments of a given boolean function.

function truth_table(f)
   a = [false,true]^f.argc()
   for x in a
      print((x+[f(*x)]).map(int))
   end
end

Methods

In Moss, every function has a hidden argument, called self. In a function call of the form x.m(a,b), the function m takes a,b as normal arguments, and x as its self argument. Viewed this way, m is called a method of x.

The self argument can be given explicitly:

f(a,b) ≡≡ f(null; a,b),
x.m(a,b) ≡≡ x.m(x; a,b).

A simple example shows how self arguments work.

> m = |a,b| [self,a,b]
> m(1,2)
[null, 1, 2]

> m("tea";1,2)
["tea", 1, 2]

If m is inserted in String, all strings will have m as a method.

> String.m = m
> "tea".m(1,2)
["tea", 1, 2]

Conversely, we can extract some method of String.

> "tree".upper()
"TREE"

> upper = String.upper
> upper("tree";)
"TREE"

But in most cases this is not what we want. If you want the method upper to be converted into a function, simply create such a function.

> upper = |s| s.upper()
> upper("tree")
"TREE"

There is still a little thing to be noted: One can choose a unique name instead of "self". Let us use "x" instead of "self". Then the example from above reads:

> m = |x;a,b| [x,a,b]
> m("tea";1,2)
["tea", 1, 2]

This is needed in case an inner function literal shadows an outer one. So, for example

Function.plus = |g| |x| self(x)+g(x)

will no work as intended. Instead one has to state this as

Function.plus = |f;g| |x| f(x)+g(x)

wich, in this case, also looks clearly more likeable. One could have written:

Function.plus = fn|g|
   f = self
   return |x| f(x)+g(x)
end

But that is not for the sake of brevity.

Function application

As just said, in Moss there is no invisible times. Instead of ab or a b one has to write a*b. But the function application operator is invisible. The expression f(x,y) means

f applied to (x,y).

The parenthesis pair on the right hand side is obligatory. Now note that the left hand side of this operator may itself be an expression. If e is some expression, then (e)(x,y) means

(e) applied to (x,y).

Here are some examples:

f(x)(y) means (f applied to x) applied to y
a[k](x) means a[k] applied to x

Let us define addition of functions pointwise. Look what that means.

Function.plus = |f;g| |x| f(x)+g(x)

Now one can write (f+g)(x) instead of f(x)+g(x).

If t is a pair, one may write f(*t) instead of f(t[0],t[1]). That is a more general way of function application, because we can always write f(*[t[0],t[1]]). In general we have

  f(x,y) ≡≡ f(null;x,y) ≡≡   f(*[x,y]) ≡≡ f(null;*[x,y]),
a.m(x,y) ≡≡ a.m(a;x,y)  ≡≡ a.m(*[x,y]) ≡≡ a.m(a;*[x,y]).

Multiple return values

In Moss one can assign two values to two variables in one statement:

x,y = 360,240

This is the same as:

x=360; y=240

But there is more going on here. Actually it is a shorthand. Firstly the numbers on the right hand side are bundled, so that a list is constructed. The square brackets are simply omitted. Secondly the list is deconstructed on the left hand side. The long, more powerful, form of this statement is as follows:

[x,y] = [360,240]

We want x,y to be the coordinates of some point of a circle line. This can be achieved by the following function f.

use math: pi, sin, cos

f = |t,r| [r*cos(2*pi*t), r*sin(2*pi*t)]

for i in 0..11
   x,y = f(i/12,1)
   print("x = {}; y = {}" % [x,y])
end

List unpacking (also known as destructuring) on the left hand side is more general:

t = [["x","y"],[1,2,3,4]]

[[x,y],[a,b,c,d]] = t

Static variables

Static variables are provided by using closures. Let us take a simple counter as an example. It shall have an internal static variable k, which holds its state.

function counter(k)
   return fn||
      k+=1
      return k-1
   end
end
> c = counter(0)
> c(), c(), c(), c()
[0, 1, 2, 3]

> c = counter(0)
> c.list(4)
[0, 1, 2, 3]

> c = counter(10)
> c.list(4)
[10, 11, 12, 13]

Lazy evaluation

Lazy evaluation is not standard in Moss, but one can simulate it like in Scheme.

Moss Scheme
t=|| expression (define t (delay expression))
t() (force t)

Now let us build such an infinite tree:

                    0
                   / \
                  /   \
                 /     \
                /       \
               /         \
              /           \
             /             \
            /               \
           /                 \
          /                   \
         0                     1
        / \                   / \
       /   \                 /   \
      /     \               /     \
     /       \             /       \
    0         1           1         2
   / \       / \         / \       / \
  0   1     1   2       1   2     2   3
 / \ / \   / \ / \     / \ / \   / \ / \
.. ... .. .. ... ..   .. ... .. .. ... ..

This won't work:

function tree(x)
   table{x=x, left=tree(x), right=tree(x+1)}
end
print(tree(0).right.right.right.x)

It produces infinite recursion. We have to use explicit lazy evaluation:

function tree(x)
   table{x=x, left=|| tree(x), right=|| tree(x+1)}
end
print(tree(0).right().right().right().x)

In some constructions, a value could either be a lazy expression or a normal value. In this case, lazy expressions should have their own data type.

Lazy = table{
   string = || "lazy expression"
}

function delay(f)
   table Lazy{call=f}
end

function force(x)
   x.call() if x: Lazy else x
end

t = delay(|| 1+2)
print([force(2), force(t)])

But in most constructions, a value is never a function. In such cases we can simply check for a function.

function force(x)
   x() if x: Function else x
end

print([force(2), force(|| 1+2)])