Plan (former name) / Lota / Lupin / Avla / Reva

Notes from 2013-02-10 onwards:

(“Reva” looks to me like the most likely name right now.)

Namespacing ideas:

Problems with the Python way in the context of PLLAR.inject(:/) are that our namespace is basically flat, at least as it stands. There’s a convention of using ‘/’ to separate parts of a hierarchical namespace (within the ‘prefix part’ of a symbol, for example), but it's not enforced.

Another problem: symbols only have a prefix and a suffix. This is a problem when you want a "symbol in a symbol" for combining namespacing and type dispatch, for instance.

Current notes on Reva

Reva is ‘emergently typed’ as Tav might say: types are nothing more than predicates around objects. It’s like Scheme in this regard — though there may well be a type-of function, the main way to enquire about the type of an object will be a predicate defined about that type. To help avoid collisions, there are two ‘magic’ things: the first is that every mutable object (conses, arrays, sets, dicts, strings) carry around metadata maps like Clojure’s (TODO: decide whether keys should be limited to being symbols. There’s little reason, afaict, for allowing other key types) so you can define a type as being ‘any kind of object with this kind of tag.’

Type dispatch is done with symbol prefixes. (See above for the problem of namespacing.) If I define a type called ‘stream’ based on a cons, I could define a version of cdr that works on it thusly, assuming the default cdr is in the ‘cons’ namespace:

(deffn (cdr stream:x)
  (force (cons:cdr x)))

The idea of functions being able to have multiple versions based on their argument structure is an interesting implementation challenge. Currently my idea is that deffn expands (as a macro) to a (def name (fn (…) …)). The difference between set and def is connected with multiple value return. Namely, def is used to create a variable that returns multiple values to the continuation that calls it. (def also sets the name metadata on the object to the variable name.) Referencing a variable that exists in multiple imported namespaces causes them to be returned like multiple values, using the ‘most sensible one’ first. This is all rather crazy. When a multiple-value–holding variable appears in functional position, the first one that matches the types of its arguments is called.

So let’s say you have two modules, json and xml, imported into your program. Each of them defines a function parse twice, once that accepts a string and one that accepts an I/O port. (I don’t actually recommend using this! They should be called json-parse and xml-parse. This is just an example.) If you just type parse in, you’ll see something like this, assuming the json module was imported after the xml module:

#<function json:parse (io:input)>
#<function json:parse (string:source)>
#<function xml:parse (io:input)>
#<function xml:parse (string:source)>
;=> 4 values

When you call (parse "<example />"), the interpreter will run through these definitions in order. Since #<function json:parse (string:source)> is the first one that matches the argument type, a string, it’ll end up trying to call the JSON parser on an XML string. You’ll have to use (xml:parse "<example />") to get it to work. If you’d imported the xml module after the JSON module, the original code would work, and you’d have to use (json:parse "[1,2,3]") to parse some actual JSON.

There should also be some ‘magic namespaces’:

In addition to deriving types from cons and string and all the built-in types and so on, there should be a way to define custom ‘record types’. Possible ways to do this is to do something like Perl’s bless, except that all it does is stop something from matching predicates for the type it’s defined from. (So, in other words, almost completely unlike Perl’s bless.) Another alternative is something like RecordsCowan, which is perhaps a little *too* low-level, but could of course be used to implement something more sophisticated in terms of it. (Something like Ruby structs, for instance)

does-not-understand, Smalltalk-style? Hmm. That would allow something like Ruby openstructs, too …

callables: If the item in functional position is not a function, and it responds to (call item args ...)​ it will be treated as a "callable object." This can be used to make e.g. regexps callable: ​(r/^[a-z]+\n/ str)​. The ​regexp​ type defines ​(call regexp:self string:target)​ in that case.)

The ‘import’ construct

It's important not to fuck this up. Need to support:

Experiments:

(import json)

(import (as j json))       ; import json as j
(import (from json parse)) ; from json import parse
(import (from json (as json-parse parse))) ; from json import parse as json-parse

; problematic: 'from' and 'as' take their arguments in the opposite order to each other. use keyword args instead?:

(import json)

(import json as: j)
(import parse from: json)
(import parse as: json-parse from: json)

; much cleaner, but potentially long-winded for lots of renaming imports
; even this is slightly confusing, because the first argument to import
; is the namespace/name that will be imported, in some cases, and in
; other cases it's what to import from. i think 'as' is actually the
; wrong word … but what's the acceptable opposite? hmm

(import json-parse from: json:parse) ; aha, perhaps! that would also allow:
(import json:parse)

The ‘export’ construct

(export http-server) ; declare everything to be in the http-server namespace. should come before any imports.

; not sure about this ... 'private' would do just as well
(export http-server only: (make-http-server run))