Here's an introduction for a language that's just in its infancy, directed to people who don't need to hear the Smalltalk basics:
Slate is a language providing a proof of concept of the Prototype-based Multiple Dispatch (PMD) method dispatch algorithm, using Smalltalk / Self's method syntax, with important but necessary differences. Slate was implemented in a few days' time in Common Lisp, and works right now.
Unpack the files into a single directory, load up your favorite common lisp, and load the interpreter by evaluating:
(load "slate.lisp")
You may notice some warnings, but they may be safely ignored for now. If the interpreter does not already show a "Slate >" prompt, enter the main loop by typing:
(repl)
At this point you are in the interpreter's main loop, with no code loaded.
To load the libraries, you invoke as follows:
'./src/init.slate' fileIn.which in turn loads in the other separate library files in a proper sequence. This may take awhile as the current implementation of the interpreter is rather slow (but will improve in the future). Now you can enter some basic expressions, but note that the REPL does not complete as a loop until a stop (".") is entered at the top-level. Consider it equivalent to Smalltalk's bang("!").
Unary, binary, and keyword messages are all the same as in Smalltalk, and core Squeak utility libraries have been ported:
3 + 4. => 7 7 factorial. => 5040
The libraries currently ported that are known to work include Numerics, Booleans, Nil control-flow, Magnitudes, and minimal tools for managing the Prototypes and their Traits and delegations.
Smalltalk's core Collection library and the core Streams are ported, but currently there are bugs being tracked down which prevent these from entirely working.
At the top level, the constants are: True, False, Nil (a note: we moved from & and | to /\ and \/)
Integers and Floats are provided as primitive numerics.
Array literals are enclosed in curly braces, elements separated by periods, and recursively nestable. They do not introduce local scopes; methods can even be defined within them.
Slate's semantics:
There's no 'self' (default receiver); however, you can still bind an argument to the identifier 'self', but it's not reserved. Any argument to a method may be dispatched upon.
Our design decisions:
So, this brings us to method definition. We'll use a familiar example from the Booleans.
_@True ifTrue: block ifFalse: _ [block value]. _@False ifTrue: _ ifFalse: block [block value].
Here we have two methods with the same name.
We have appropriated the use of the @ character to note when an argument is significant to dispatch. The expression following "@" in this context is where the method should be placed. Keep in mind that this has nothing to do with type declaration: objects are just as flexible as in Self, and this just means that the method is defined upon that object as that particular argument position for use in method dispatch.
For input arguments, you can see the use of both _ and a regular variable name. The underscore just tells the parser that the argument at that position can be ignored, because we're not going to use it's value -- we're merely dispatching upon it. Why name something that doesn't have a use?
At the end, we see code enclosed in brackets. That's the method code itself. Every method uses fully general block syntax and semantics, which means it can take additional input variables, and its temporaries are just block temporaries.
For the purists, we should explain the special syntax above in terms of its method-call basis using the following translation of a (silly) method definition:
x@(Integer traits) + y@(Float traits) [ (x as: Float) + y ]. [| :x :y | (x as: Float) + y] asMethod: #+ on: {Integer traits. Float traits}.
These are exactly equivalent. Most of the syntax here is Smalltalk-compatible, with a few exceptions:
There's more, which needs another example method to illustrate:
oc@(OrderedCollection traits) copyFrom: start to: end [| newOC | end < start ifTrue: [^ oc newEmpty]. newOC: oc traits newSize: end + 1 - start. start to: end do: [| :index | newOC addLast: (oc at: index)]. newOC ].
Here's a method translated from Squeak which provides good examples:
_@3 + _@2 ['five']. 1 addDelegate: #three. 1 three: 3. _@1 + _@2 [resend]. 3 + 2. => 'five' 1 + 2. => 'five'
This example also illustrates the powerful delegation-based object model the language has inherited from Self. Literal objects aren't special. You can define methods upon them and make them delegate to other objects just as any other.
Mostly, we want to make a clean start out of Smalltalk and Self. The translation of methods from Smalltalk is very direct; we accomplished the successful porting of core functionality in a few days, and Collections and Streams should follow shortly. Our most significant deterrence is the lack of a suitable editor for performing basic syntax checks. This also should be remedied shortly.
A lot of our intended direction is in the improvement of the libraries, and reducing the amount of effort needed to refactor, learn, or extend them. Multi-methods gain a lot of this by separating out the interactions further from protocols into protocols per argument position and input signatures. One useful utility that we have been building up is a more generic type-conversion process, as:, which is nicely self-contained since we can simply refine it per pair of types and which supplants the normal Smalltalk asFoo convention. You simply supply a prototype as the second argument to as: which represents the desired conversion you wish to take place.
One of our primary concerns has been to relieve a lot of classes of "side-roles", like factoring out methods from generic stream classes that deal with things such as files or ASCII text. Why do we have collection types that have exactly one interpretation of arithmetic operators? Why do they have any interpretation built into them?
Tiny Example: You have some objects represented on the screen. Select them all to make a collection, and then display all the message selectors that they commonly respond to.