The Stunt server supports verb calls on values of primitive type. For example, it's possible to program the server so that the expression "one two three":split() evaluates to {"one", "two", "three"}. If you use programming languages like Ruby, this idiom—"everything is an object"—will be familiar.

Duck Typing

It's more accurate to say "everything looks like an object" because the Stunt server implements these verb calls using special prototype objects. The primitive package consists of a set of these prototype objects, a library of utility verbs (like you would find on the various $xyz_utilsobjects in LambdaCore) with support for Frobs and functional programming thrown in.

How it Works

When a verb is called on a value (either a literal or a variable) and the value is a primitive type, the Stunt server looks for a special prototype object based on the value's type—$int_proto for values of type INT, $float_proto for values of type FLOAT, and so on. If no such prototype exists, the server raises E_TYPE, as usual. If a valid prototype exists, verb resolution takes place on the prototype as it would for any object, and if the server finds a suitable verb it calls it... with one slight difference—the variable thisholds the primitive value rather than the object number of the prototype.

An Example

Assume $int_proto holds the object number #100 and the object with that object number has a callable verb named square with the following single line of verb code: return this * this;. If the user interactively evaluates ; 5:square(), the server will call this verb with the value 5 in this (argswill be the empty). The result of the evaluation will be 25.

Frobs

A while back there was a discussion on the MOO-cows mailing list about a data structure known as a "Frob". Frobs are lightweight objects, kind of like waifs. It's easy to implement about 80% of what you get with waifs using verb calls on lists (and maps) but with far less server hackery. Frobs are lightweight and reference counted; they are not opaque, however, so they are not a good choice for capabilities and similar uses where the state needs to be protected/hidden.

How it Works

Frobs require one more layer of indirection. In the primitive package, both $list_proto and $map_proto include a "star verb" (a wildcard verb that responds to every verb invocation). The star verb looks for an object number in the variable this (which is a list or map) to use as the Frob prototype; in the case of a list, it is the first element; in the case of a map, it is the value of the key "prototype". Then it looks for the Frob prototype in $frobs to ensure that only lists/maps intended to be Frobs are treated as Frobs. If it's not a Frob, it's handled just like a primitive list/map. If it is a Frob, the verb call is repeated on the Frob prototype. Since the "star verb" adds another verb call, the original primitive list or map is available to the Frob verb in caller.

An Example

A great demonstration of how Frobs work is found in the primitivepackage itself. Read on!

Lambda

Loosely speaking, a lambda is an anonymous verb/function. In Stunt, the verb $lambda() creates a new lambda. A lambda is represented as a Frob. The lambda prototype object is known as $lambda_proto. $lambda_proto is included in $frobs.

How it Works

The call $lambda("x", "y", "return x + y;"); creates the list {$lambda_proto, "x", "y", "return x + y;"}. This list is the internal representation of the lambda. The Frob prototype object referenced by $lambda_proto defines a verb named call() that causes the lambda to be evaluated. This verb accesses the internal representation of the lambda in the variable named caller. Internally, the verb uses the built-in function eval()to evaluate the body of the lambda (the string "return x + y;"). Because it's just a list, a lambda can be assigned to a variable and evaluated at a later time—perhaps several times with different arguments.

l = $lambda("x", "y", "return x + y;");
l:call(1, 2); /* evaluates to 3 */
l:call(2, 3); /* evaluates to 5 */

Functional Programming

The essence of functional programming is that functions are values that can be passed as arguments to other functions. The primitive package defines the usual suite of these verbs which operate on lists, maps and strings: each, map, reduce, etc. In the example below, ; x * x is shorthand for the more verbose return x * x;—these verbs are often used in one-liners, so brevity matters.

square = $lambda("x", "; x * x");
{1, 2, 3}:map(square); /* evaluates to {1, 4, 9} */
double = $lambda("x", "; x + x");
{1, 2, 3}:map(double); /* evaluates to {2, 4, 6} */

The following code is nearly identical to the first example, above. If a list is passed in to eachet al, the verb will happily create a lambda for you.

{1, 2, 3}:map("x", "; x * x"); /* evaluates to {1, 4, 9} */

Performance

Assuming the implementation is roughly the same, a call to $string_utils:trim("...") takes as long as a call to "...":trim(), so as far as utility functions on primitives go, there's little benefit to the old way of doing things. Combine the savings in keystrokes with the ease with which you can chain operations together, and there's no decision, as far as I'm concerned.

Calls to verbs that simply wrap built-in functions (like "foo bar baz":length(), for example) make an additional verb call. Calling the built-in (length("foo bar baz")) is definitely faster, however the chainability of the former might be useful when composing a longer expression.

Calls that evaluate lambdas are the slowest of the lot because they must evaluate the body of the lambda, which is a string. This requires parsing and is much, much slower that an inline for/while loop that computes the same value. On the other hand, ticks aren't that expensive, and with less for/while loop boilerplate to type, there's less code. Less code means fewer bugs.

Feedback

I'm available via the MOO-talk Google Group. Post questions and comments there.

Todd Sundsted