↑ Up |
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
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
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
.
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.
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*
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)"
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 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)"
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]).
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
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.
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]).
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 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 is not standard in Moss, but one can simulate it like in Scheme.
Moss | Scheme | ||
---|---|---|---|
t=|| expression
|
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)])