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.
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_utils
objects in LambdaCore) with support for Frobs and functional programming thrown in.
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 this
holds the primitive value rather than the object number of the prototype.
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
(args
will be the empty). The result of the evaluation will be 25.
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.
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
.
A great demonstration of how Frobs work is found in the primitive
package itself. Read on!
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
.
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 */
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 each
et al, the verb will happily create a lambda for you.
{1, 2, 3}:map("x", "; x * x"); /* evaluates to {1, 4, 9} */
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.
I'm available via the MOO-talk Google Group. Post questions and comments there.
Todd Sundsted