5. Functions

5.1 Subroutines

Sleep subroutines are mini programs. You can call them, with arguments, and they give back a value.

sub add { return $1 + $2; } $x = add(3, 4);

This example is an add subroutine. $x receives the result from the add subroutine. In this example the result is seven. Separate arguments with a comma. In this example 3 and 4 are arguments.

Within a function arguments are available by their number. I refer to these as anonymous arguments. $1 is the first argument, $2 the second, and $3 the third.

The array @_ also contains all of the anonymous arguments for a subroutine.

The return command stops the subroutine and gives a value back to the caller. The caller is the program that ran the subroutine.

Beware Side Effects: How arguments are passed

Sleep passes all subroutine arguments by reference. This is important to note. A change to an argument will affect the parent.

sub test { $1 = "bar"; } $fluffy = "foo"; test($fluffy); println("The value of \$fluffy is $fluffy");

The value of $fluffy is bar

This feature makes it easy to shoot yourself in the foot. To avoid this behavior stay away from directly modifying arguments. A change to an argument is a side effect. Functions with side effects are a potential source of subtle bugs. You can use the &watch function to watch for scalar changes, just in case.

sub test { $1 = "bar"; } $fluffy = "foo"; watch('$fluffy'); test($fluffy); println("The value of \$fluffy is $fluffy");

Warning: watch(): $fluffy = 'bar' at fluffy2.sl:3 The value of $fluffy is bar

Named Arguments

Use the key-value operator to pass a named argument. The key portion of the key-value operator is the variable name for the argument. Sleep places this value into the local scope of the function.

sub team { println("$first is a member of team: $team"); } team($first => "James", $team => "ramrod"); team($first => "Naji", $team => "ramrod"); team($first => "Jerard", $team => "ramnot");

James is a member of team: ramrod Naji is a member of team: ramrod Jerard is a member of team: ramnot

Named arguments do not affect the anonymous argument sequence or @_.

Arrays and Hashes as arguments

You can pass arrays and hashes as arguments to a function.

sub foo { println("Third element is: " . $1[2]); } @array = @("a", "b", "c"); foo(@array);

Third element is: c

Recursion

Sleep supports recursion. A function that calls itself is recursive. Factorial is a common example:

sub fact { if ($1 == 0) { return 1; } return $1 * fact($1 - 1); } $value = fact(11); println("11! is $value");

11! is 39916800

Trace function calls

Sleep's trace mode allows you to trace function calls. This mode is set with the &debug function. This mode prints each function name, argument, and return value with the script name and line number. This tool is valuable for understanding your scripts. Debug trace is level 8.

debug(debug() | 8); sub fact { if ($1 == 1) { return 1; } return $1 * fact($1 - 1); } println("Result is: " . fact(5));

Trace: &fact(1) = 1 at trace.sl:10 Trace: &fact(2) = 2 at trace.sl:10 Trace: &fact(3) = 6 at trace.sl:10 Trace: &fact(4) = 24 at trace.sl:10 Trace: &fact(5) = 120 at trace.sl:13 Result is: 120 Trace: &println('Result is: 120') at trace.sl:13

Profile your scripts

Sleep sports a built-in profiler. The profile mode records function calls and the execution time. Debug trace collects profile statistics automatically. Use &debug level 24 to enable only the profiler. The &profile function provides the profiler statistics

debug(debug() | 24); sub appendConcatOperator { $string = ""; for ($x = 0; $x < 8000; $x++) { $string = $string . "x"; } } sub appendBuffer { $string = ""; $handle = allocate(); for ($x = 0; $x < 8000; $x++) { writeb($handle, "x"); } closef($handle); $string = readb($handle, available($handle)); } # benchmark these 2 methods for building strings appendBuffer(); appendConcatOperator(); # print out the profiler output printAll(profile());

4.479s 1 &appendConcatOperator 0.372s 1 &appendBuffer 0.041s 8000 &writeb 0.0040s 1 &readb 0.0030s 1 &allocate 0.0010s 1 &closef 0.0s 1 &available

This example shows the profiler in action. Here I use two techniques for building a large string of data. One method uses the string concatenation operator within a for loop. The other uses a memory buffer and Sleep's I/O to build the string. The memory buffer is faster by an order of magnitude.

I will now give a very standard warning in the world of programming: Write code that works first. Once you have code that works use a profiler to find out where your code spends time. Optimize the slow portion if you must. Rinse, lather, and repeat until you have the performance you desire.

5.2 Scalar Scope

Sleep has three levels of variable scope. In this section I discuss global scope and local scope. I discuss the third, closure scope, later in this chapter. Scope defines where a scalar exists and how long it lasts for. A global scalar is accessible anywhere in a script. Global scalars exist for the lifetime of the program.

A local scalar is accessible during a function call. The variable exists while the function runs. Once the function exits the variable goes away. Local variables have higher precedence than global variables.

Use &local and &global to declare variables. These functions accept a string of variable names separated by whitespace. Sleep scalars have a global scope by default.

sub verdict { local('$decision'); $decision = "not guilty"; } $decision = "guilty"; verdict(); # what is the value of $decision?

This example declares the subroutine verdict. The subroutine assigns a value to $decision. Which scalar gets the value? The local or global $verdict? Sleep assigns the value to the local scalar. This function does not affect the global scalar. When &verdict returns the local scalar goes away. So what is the final value of $decision? The answer is "guilty".

Pass by Name

I introduced named arguments earlier in this chapter. A common use of named arguments is to pass a scalar into another script scope. Functions such as &lambda, &fork, and &invoke pass variables between scopes. Named arguments also pass variable between scopes. A function that has a local scalar $x can pass the local $x to another function with foo($x => $x). Typing $x => $x gets tedious. It is also ugly to look at. And no one likes it. And it doesn't have any friends. And.. I spit on it!!! Ok. I'm beating a dead horse. Let me get a bigger club.

It is for this common case that pass by name exists. Prefix a variable with a backslash to pass it by name. For example: foo(\$x). The parser compiles this form to $x => $x directly.

sub foo { local('$explorer $year $password'); $explorer = "Christopher Columbus"; $year = 1492; $password = "OceanBlue"; # not passed! bar(\$explorer, \$year); } sub bar { println("The explorer is: $explorer"); println("bumped around : $year"); println("password is : $password"); } foo();

The explorer is: Christopher Columbus bumped around : 1492 password is :

You can use pass by name anywhere you would use the key-value operator.

Strict Mode

Sleep has a debug mode that requires scripts to declare all variables. This is strict mode. Set it with the &debug flag 4. Any attempt to use an undeclared variable in strict mode will fire a runtime warning. This warning will state the variable name, script name, and line number of the offending value.

Strict mode will help find bugs from the following errors:

Declared variables are your friend. Here we find a misspelled variable name.

debug(debug() | 4); sub foo { local('$x'); $xx = 3; return $x; } # why is this value null? println("foo is: " . foo());

Warning: variable '$xx' not declared at declare.sl:6 foo is:

This example finds a scope mistake.

debug(debug() | 4); sub foo { local('$x'); $x = 4; return bar(); } sub bar { $x = $x * 3; return $x; } # why does this output 0 and not 12? println(foo());

Warning: variable '$x' not declared at declare2.sl:12 0

I highly recommend strict mode at all times.

Inline Subroutines

Sleep's solution to the macro is the inline subroutine. An inline subroutine executes within the current context. Inline subroutines inherit the local and closure variables of the calling function. Attempts to yield, return, or callcc will affect the parent.

Parameter passing to inline subroutines is the same as for normal subroutines. $this refers to the parent function and not the inline subroutine. Sleep restores the parent function's anonymous arguments upon returning from an inline subroutine.

Declares inline subroutines with the inline keyword.

inline printx { println("\$x is $x"); } sub foo { local('$x'); $x = 12345; printx(); } foo();

$x is 12345

You can use &pushl to create a new local scope within an inline function. You can then declare variables within the local scope without worry. You must dispose of the local scope with &popl prior to the completion of the inline subroutine.

inline swap { pushl($a => $1, $b => $2); local('$temp'); $temp = $b; $b = $a; $a = $temp; popl(); } sub bar { local('$x $y $temp'); $temp = 100; $x = 3; $y = 9; println("\$x: $x and \$y: $y"); swap($x, $y); println("\$x: $x and \$y: $y (and $temp $+ )"); } bar();

$x: 3 and $y: 9 $x: 9 and $y: 3 (and 100)

Do not call inline subroutines within an expression. The inline subroutine will return $null.

Inline subroutines can call other inline subroutines.

5.3 Closures

Sleep functions are first class types. You can assign them to variables, pass them as parameters to functions, and invoke them from a variable. A function scalar is an object scalar that references a sleep.bridges.SleepClosure object.

Subroutines declared with sub are named closures. You can refer to a named closure by prefixing an ampersand to its name. You can call closures with an object expression.

sub my_sub { println("My name is: $1"); } # same as my_sub("Raphael"); [&my_sub: "Raphael"];

My name is: Raphael

For named closures there isn't much excitement surrounding a new syntax. However this section focuses a lot on anonymous closures. Specify an anonymous closure with a block of code in place.

To assign an anonymous closure to a variable:

$closure = { println("My name is: $1"); }; [$closure: "Raphael"];

My name is: Raphael

You can also invoke an anonymous closure in an object expression. The object expression evaluates the object and assumes the result is a function it can call.

[{ println("Hello $1 $+ !"); } : "World!"];

Hello World!!

Use the &setf function to bind an anonymous closure to a name.

sub foo { println("foo!"); } setf('&foo', { println("bar!"); }); foo();

bar!

Closure Scope

Closures have their own scope. A closure variable persists between calls to the closure. Use the &lambda function to create a new closure and initialize its values.

sub accum { return lambda({ $i = $i + $1; return $i; }, $i => $1); } $a = accum(3); println("a: " . [$a: 1]); println("a: " . [$a: 1]); $b = accum(30); println("b: " . [$b: 2]); println("b: " . [$b: 2]); println("a: " . [$a: 3]); println("b: " . [$b: 3]);

a: 4 a: 5 b: 32 b: 34 a: 8 b: 37

This example shows a Sleep Accumulator Generator. An Accumulator Generator is a function that returns an accumulator function. The accumulator function contains an initial value and increments itself with each call by some parameter.

Each call to lambda creates a new closure. You can call and set values within these closures independent of other closures--even if they refer to the same code.

Use the &this function to place values into the scope of "this" closure.

$myfunc = { this('$a $b $c @array %hash'); # do stuff... };

Each closure contains the scalar $this. $this is a reference to the current closure.

Index Operator

Use the index operator to get a value from the this scope of a closure.

$closure = lambda({ println("\$x is $x"); }, $x => 33); [$closure]; $closure['$x'] = "test!"; [$closure]; println("Accessing a value: " . $closure['$x']);

$x is 33 $x is test! Accessing a value: test!

Closures as Psuedo Objects

Object expressions accept a message parameter as $0. This is akin to a method name in a class.

$closure = { println("Message is $0 argument is $1"); }; [$closure foo: "bar"];

Message is foo argument is bar

This parameter allows you to create closures and manipulate them much like objects. This example shows a closure acting as a Stack object.

sub BuildStack { return { this('@stack'); if ($0 eq "push") { push(@stack, $1); } if ($0 eq "pop") { return pop(@stack); } if ($0 eq "isEmpty") { return iff(size(@stack) == 0, 1, 0); } }; } $mystack = BuildStack(); [$mystack push: "apple"]; [$mystack push: "bananna"]; [$mystack push: "cat?!?"]; while (![$mystack isEmpty]) { println("Pop!: " . [$mystack pop]); }

Pop!: cat?!? Pop!: bananna Pop!: apple

This example works much like the accumulator generator. Each call to &BuildStack generates a new closure.

Seriously, Sleep doesn't have classes and objects?

All of this discussion so far has lead to this. Seriously. Sleep doesn't have classes and objects. Sleep's closures are very powerful though. If you want a generic data structure that groups together data and functionality, try this on for size:

# everything you need for Sleep OO sub object { local('$function'); $function = function("& $+ $type $+ :: $+ $0"); if ($function !is $null) { return invoke($function, @_, $0, $this => $this); } throw "$type $+ :: $+ $0 - no such method"; } sub newObject { local('$object'); $object = lambda(&object, $type => $1); # invoke the constructor invoke($object, sublist(@_, 1), "init", $this => $object); return $object; }

The &newObject function creates a new closure from &object with a $type variable in its scope. The other arguments to &newObject are passed to "& $+ $type $+ ::init" which acts as a constructor. The constructor is invoked with the $this scope of the new closure.

The &object closure contains code for a generic method dispatcher. When invoked, it looks at $0 and finds a method matching "& $+ $type $+ :: $+ $0". If found, the method is invoked within the scope of the object closure instance.

You can think of the closure scope as a class scope.

Now that we can combine functions and data, let us discuss classes.

include("object.sl"); # define our person object sub person::init { this('$name $age'); ($name, $age) = @_; } sub person::print { println("Person: $name ( $+ $age yrs old)"); } # use it $raffi = newObject("person", "Raphael", 27); [$raffi print]; $frances = newObject("person", "Frances", 26); [$frances print];

Person: Raphael (27 yrs old) Person: Frances (26 yrs old)

And that's all there is to it. Implementing inheritance and other object oriented features is left as an exercise to you dear reader.

Can I interoperate with Java this way?

This question is beyond the scope of this chapter. The short answer is--yes. Sleep has a feature to use closures as psuedo objects that respond to certain interfaces. This topic is covered in chapter 7.2 Proxy Instances.

Memoization

Chapter 3 introduced ordered hashes along with removal and miss policies. You can use the miss policy of an ordered hash to transparently enable a powerful optimization known as memoization. Memoization caches the results of a function call to prevent unnecessary calculations.

Here is a naive implementation of the Fibonacci sequence with runtime statistics.

sub fib { if ($1 == 0) { return 0L; } else if ($1 == 1) { return 1L; } else { return fib($1 - 1) + fib($1 - 2); } } println("Fib no. " . fib(30L));

$ java -jar sleep.jar --time fib2.sl Fib no. 832040 time: 50.368s

Not very fast is it? This naive implementation requires 2,692,537 recursive function calls to complete the computation.

This next function shows how to implement memoization in Sleep. This function accepts a closure argument and returns a memoized version of it.

sub memoize { local('%cache'); %cache = ohash(); setMissPolicy(%cache, lambda( { # $2 is the requested key as provided return invoke($function, $2); }, $function => $1) ); return lambda({ return %cache[@_]; }, \%cache); }

How does it work? This function constructs an ordered hash. The miss policy of the ordered hash calls the function we want to memoize. The miss policy puts the return value of the function into the ordered hash.

Now we apply memoization to the Fibonacci program.

include("memoize.sl"); sub fib { if ($1 == 0) { return 0L; } else if ($1 == 1) { return 1L; } else { return fib($1 - 1) + fib($1 - 2); } } setf('&fib', memoize(&fib)); println("Fib no. " . fib(30L));

$ java -jar sleep.jar --time fib1.sl Fib no. 832040 time: 0.25s

As you can see the numbers are quite different this time. Memoization is not a catch-all though. Do not use memoization on functions that have side effects.

5.4 Continuations

Closures can pause their execution and call another function that does something with the paused closure. A paused closure is a continuation. Calling a paused closure resumes its execution.

When pausing a closure, Sleep saves the compiled code, call stack, program counter, local variables, and closure variables into a closure object.

To save a continuation use the callcc command and specify a function to call. Read callcc as: call the specified function with a continuation of the current function as an argument.

callcc &closure;

This example shows callcc with the producer and consumer problem.

$buffer = $null; sub produce { for ($x = 0; $x < 3; $x++) { println("Produce: $x * 3"); $buffer = $x * 3; callcc &consume; } } sub consume { println("Consume: $buffer"); [$1]; # resume the calling function } produce();

Produce: 0 * 3 Consume: 0 Produce: 1 * 3 Consume: 3 Produce: 2 * 3 Consume: 6

The producer pauses itself and sends control to the consumer function. The consume function consumes some data. It then invokes its first argument. Think of callcc as a functional goto with a rope to go back to the old function.

Example: Mobile Agents

Agent programming is a paradigm for distributed computing. An agent system is multiple independent actors executing asynchronous of one another. Clever programmers will create a generic template of functionality and customize each instance with various parameters on deployment. In this way you can gain a lot of functionality for very little code.

An agent system requires a means to implement the agents themselves and middleware to execute the agents within. Here is a closure implementation of an agent:

sub InfoAgent { local('$user'); $user = systemProperties()["user.name"]; println("1) I am $user"); move($destination); println("2) On $source I am $user"); $user = systemProperties()["user.name"]; println("2) Here I am $user"); move($source); println("3) Back home, I was $user"); }

You may notice the &move function. This is the agent requesting to move to another computer. Agents that can relocate themselves are mobile agents. &move is an inline function hiding a callcc that calls sendAgent with the desired host and continuation.

inline move { callcc lambda( { sendAgent($host, $1); }, $host => $1); }

The &sendAgent function is shown below. Serialization allows scripts to persist and reconstitute scalars to and from any I/O channel. Sleep uses serialization to write and read continuations to and from a socket.

# sendAgent("host", $continuation) sub sendAgent { local('$handle'); $handle = connect($1, 8888); writeObject($handle, $2); closef($handle); }

The server for the &sendAgent function is the middleware for the Sleep agents. This server is a loop that listens for a connection, reads an object, and creates an isolated thread that executes the read-in object.

while (1) { $handle = listen(8888, 0); $agent = readObject($handle); closef($handle); fork({ [$agent]; }, \$agent); }

With these pieces in place, the final step is to launch the agent into the system:

$agent = lambda(&InfoAgent, $source => "sleep.dashnine.org", $destination => "apollo.ecs.syr.edu"); sendAgent("sleep.dashnine.org", $agent);

[raffi@dashnine ~]$ java -jar sleep.jar launch.sl apollo 185: java -jar sleep.jar mw.sl 2) On sleep.dashnine.org I am raffi 2) Here I am rmudge [raffi@dashnine ~]$ java -jar sleep.jar mw.sl 1) I am raffi 3) Back home, I was rmudge

To execute this example include the &move and &sendAgent functions in your mw.sl and launch.sl files.

Coroutines

Use the the yield command to pause a closure. yield can give back a value like return. A coroutine is a function that is paused with yield.

yield expression;

Coroutines allow elegant solutions to problems that require recursion and tracking state.

The return command discards the state of a coroutine. State is discarded when the function ends.

Sleep coroutines can call themselves or eachother recursively.

Generators with Coroutines

A generator is a function that generates a sequence. Coroutines are natural for implementing generators.

sub range { # Returns a new function that returns the next number in the # range with each call. Returns $null at the end of the range return lambda( { local('$counter'); for ($counter = $begin; $counter <= $end; $counter++) { yield $counter; } }, $begin => $1, $end => $2); }

This example implements a range function that returns a generator. I use a foreach loop to iterate over the generator.

foreach $value (range(8, 13)) { println($value); }

8 9 10 11 12 13

Example: List Co-iteration

This example iterates over two arrays with a generator and an assignment loop.

sub both { local('$a $b'); ($a, $b) = @_; while (size($a) > 0 || size($b) > 0) { # yield the heads of our lists yield @($a[0], $b[0]); # set the lists to the rest values $a = sublist($a, 1); $b = sublist($b, 1); } } @a = @("a", "b", "c", "d"); @b = @(1, 2, 3); while @items (both(@a, @b)) { ($x, $y) = @items; println("$x and $y"); }

a and 1 b and 2 c and 3 d and

Each iteration through the &both function returns the head (first element) of the two lists stored in it. The heads of the two lists are then set to the sublist consisting of all elements from the second element to the end of the list.

Example: Tree Traversal

You can use coroutines to traverse through graph and tree data structures.

This example is an interpreter that uses a postorder traversal of the pictured expression tree. Compilers generate an expression tree and walk it in this way to generate or interpret code.

# Represent a tree as a hashmap with the following values: # left - left branch; # right - right branch; # label - label for this portion # this function returns a tree: sub n { return %(label => $1, left => $2, right => $3); } # do a postorder traversal of a tree.. sub postorder { local('$x'); if (-ishash $1) { while $x (postorder($1["left"])) { yield $x; } while $x (postorder($1["right"])) { yield $x; } yield $1["label"]; } else { yield $1; } return $null; } # create an expression tree for: (3 * (4 + 7)) + (5 + 8) $tree = n("+", n("*", 3, n("+", 4, 7)), n("+", 5, 8)); # evaluate the expression tree...: while $node (postorder($tree)) { if (-isnumber $node) { push(@stack, $node); println("push $node"); } else { $b = pop(@stack); $a = pop(@stack); push(@stack, expr("$a $node $b")); println("oper $node [ $+ $a $+ , $b $+ ]: " . @stack[-1]); } } println("Final answer: " . @stack[0]);

push 3 push 4 push 7 oper + [4, 7]: 11 oper * [3, 11]: 33 push 5 push 8 oper + [5, 8]: 13 oper + [33, 13]: 46 Final answer: 46