Pluvo is many things: functional, imperative, interpreted, dynamically and weakly typed, lexically scoped, object oriented, duck typable, self-modifyable. It contains first class functions (closures), prototypes, Strings, Numbers, URIs, Regexps, Flags, and more. It also has some quirks of its own such as coördination and variable searchpathing. This is a demonstration of all of these features, what they mean for the language, why they were chosen, and how they work together.
In general, Pluvo is lexically, i.e. statically, scoped. This means that variables within a closure are determined at the time when the closure was created, not when it's subsequently called. For example:
x = "pass (static)" f = () { return x } g = () { x = "FAIL (dynamic)"; return {f} } say g => pass (static)
Though x was set to FAIL before f was called, since the value of x at the time when f was created was pass, that's the value that's returned.
As Wikipedia says on its article about scoping, "Static scoping also makes it much easier to make modular code and reason about it, since its binding structure can be understood in isolation. In contrast, dynamic scope forces the programmer to anticipate all possible dynamic contexts in which the module's code may be invoked." Dynamic scoping is relatively rare in programming languages even though it's easier to implement—Pluvo was dynamically scoped in the very early stages of its implementation. Some languages allow for both static and dynamic scoping, but this isn't deemed necessary in Pluvo, not least because modules allow for a certain amount of dynamic scoping.
Pluvo's scoping rules even apply to anonymous closures passed on command lines, which is important for recursive functions to work properly. For example, the following function:
factorial = (n) { if {n == 0} { return 1 } n * { factorial { n - 1 } } }
Would have to be rewritten like this if it were not possible for { n - 1 } to have n remain the value at the call time rather than during the next step in the recursion:
factorial = (n) { if {n == 0} { return 1 } n * { nonanon = () { n - 1 } factorial { nonanon } } }
(This is still valid code, but completely unneccesary.)
Note that it makes no difference where the scope is (see the section on general scoping for more details) to how a variable will be set when the closure is created:
x = "FAIL" test = () { x = "pass (static)" f = () { return x } g = () { x = "FAIL (dynamic)"; return {f} } return {g} } say test => pass (static)
This changes when we use the basic variable space. Normally the @add function is a builtin function that adds members to Tables, but let's subvert to hold a String identifying whether the scoping is static or dynamic:
@add = "FAIL (static)" f = () { return @add } g = () { @add = "pass (dynamic)"; return {f} } say g => pass (dynamic)
This time it's dynamic. This is because @add is in a different namespace, and when something is set in that namespace, it would be a significant overhead to go adding that namespace to the closures too—especially since it's not fully enumerated and depends on a searchpath. But this is how other languages operate, e.g. Python, when you set an attribute on a module, so it's not a surprising behaviour. It's useful to have builtin functions be dynamic because if you're changing such basic behaviours then you're probably doing so to hack around some problem in an upcoming block to be evaluated.
Prototypes are behavioural classes used for a form of object oriented programming. The most well known examples of programming languages using prototype object orientation are Javascript and Self. With prototypes, there are no separate class objects: instead you create prototype closures and then to make a "new instance" you simply make a copy of it. In Pluvo, the model is such that you also call the closure when copying it. Take, for example, the following prototype definition:
Person = (name homepage) { method details = () { say "Name: $name" say "Homepage: $homepage" } }
When instantiated, i.e. copied and called as a prototype, as a scope it will have three bound variables: name (passed on the constructor), homepage (passed on the constructor), and details (from the prototype), the latter of which is a method. The method function must be used to define new methods because otherwise the variable would be bound to a parent scope instead of the prototype copy. This means that "details" is only available inside the prototype.
Here's another example, followed by an instantiation and method call:
Dog = (name) { method speak = (bark) { say "$name says: " say "$bark! $bark! I am a dog." } } rover = Dog("Rover") rover.speak "WOOF" => Rover says: => WOOF! WOOF! I am a dog.
Coördination is the process of finding which argument in a command line is the callable to be used, the actual function. In most languages that have command lines, e.g. bash and other shell dialects, the first argument of a command is the one that will be called. In Pluvo, it doesn't have to be first as long as it's clear which arg is the first callable. So consider, for example, a simple join command:
say { join "." ("p" "q" "r") } => p.q.r
This can be rewritten as follows, since "." is clearly not callable:
say { "." join ("p" "q" "r") } => p.q.r
So for each command line, a coördinator searches through the arguments for a callable function, and reorders them as necessary. Note that it's also possible for functions to decline to be used, which usually happens in the case of their argspecs not being suitable. Consider the following:
chain = (first second) { first; second } date = () { say "2006-05-13" } time = () { say "11:31 AM" } date chain time => 2006-05-13 => 11:31 AM
Even though date is a function, because it takes zero args and we're trying to pass it two (chain and time), it declines to be called and the coördinator skips to the next argument, chain, to see if it will take two arguments. It does (first and second), so it gets used as the function.
(Incidentally, that is not a Heavy Metal Umlaut. The New Yorker spells such words with a diaeresis, so why shouldn't I?)
Each program consists of a series of commands, which are function calls with optional arguments. Commands may be grouped into blocks, using braces, and if these are given an argspec then they become functions; but even blocks without argspecs can be executed.
"One of the more common definitions states that weakly-typed programming languages are those that support either implicit type conversion, ad-hoc polymorphism (also known as overloading) or both." - Wikipedia, Weak Typing. In Pluvo most coercions take place within functions, so you can pass args in various contexts and have them be automatically converted, à la scalar contexts in Perl.
Sean B. Palmer