Primitive Objects
Nil
- The default value of new slots.
thisContext
- The current block closure context. This is the implicit first argument to message-sends within block closures, so it is normally not seen.
lobby
- The 'root' namespace which delegates to namespaces with the major core Slate libraries and holds globals.
True
- The Boolean for truth.
False
- The Boolean for falsity.
Unlike common object-oriented languages, Slate does not have a designated "self" or "this" or "super" object pseudo-variable; instead, objects have whatever names within their methods that are bound in the definition. This is a consequence of multi-method dispatch semantics.
Object Names
Object identifiers start with a letter and end in any character but a reserved character. This allows a greater freedom for object naming, but means that programmers should use whitespace properly. This may change in the future. They are also case-sensitive.
To be safe, follow this pattern:
letter ( letter | digit )*
Unlike some Smalltalks, underscores are not reserved.
Also, as a general convention, capitalization is used relatively sparingly, usually for permanent objects that have "proper" names.
Comments
Comments are as in Smalltalk, they are delimited by double-quotes and can span multiple lines."This is a meaningless comment. It spans a couple of lines."
Literals
Characters
$x "A character is any character (even unprintable ones), preceded by a dollar sign" $3 "Don't be shy about characters that are digits" $< "or symbols" $$ "or even the dollar sign"
Some involve escapes:
$\e "Escape" $\n "NewLine" $\r "CarriageReturn" $\t "Tab" $\b "Backspace" $\0 "Null" $\s "Space"
This will likely evolve into something more expansive.
Strings
'a string comprises any sequence of characters, surrounded by single quotes' 'strings can include the "comment delimiting" character' 'and strings can include embedded single quote characters by escaping\' them' 'strings can contain embedded newline characters' 'and escaped \ncharacters' '' "and don't forget the empty string"
Strings are arrays of characters, and are not resizable. Arrays in Slate are indexed from 0, not 1 as in Smalltalk.
Symbols
#'A string preceded by a hash sign is a Symbol' #orAnyIdentifierPrefixedWithAHashSign #orAnIdentifierEndingWithAColon: #or:several:identifiers:each:ending:with:a:colon: #here's:a:symbol:that:isn't:a:keyword #- "A symbol can also be a hash followed by '-' or any special character" #+< "or a hash followed by any pair of special characters"
Any identifier, also, is a symbol when prefixed by #
. Escapes via \
are also allowed.
- The primary difference between a symbol and a string is that all symbols comprising the same sequence of characters are the same instance. Two different string instances can both have the characters
'test one two three'
, but every symbol having the characters#'test one two three'
is the same instance. This "unique instance" property means that Symbols can be efficiently compared, because equality (=
) is the same as identity (==
). - "Identifier with ending colon" Symbols (e.g.,
#a:keyword:selector:
) are often referred to as keyword selectors, for reasons that will be made clear later. - "Single or dual symbol" Symbols (e.g.,
#*
or#++
) are often referred to as binary selectors. - The following are permissible special characters:
+/*\~<=>%|&?!;^_
- Note: Smalltalk and Self users will find that
@
and`
are reserved in Slate, but not;
or^
or_
. - Note that
#--
is not a symbol (or a binary selector). On the other hand,#'--'
is a symbol (but not a binary selector).
Arrays
Array literal syntax begins with{
and ends with }
. They may contain nested arrays, given by {} braces, not needing the #
prefix at any point as Smalltalk uses. Expressions within are evaluated, and their return values are stored as the elements. The element-separator is a period (".").
Arrays are indexed from 0
to below their size
.
Compile-time Literals
Prefixing array or block literals with the #
character will cause it to be evaluated at compile-time, and have its result inserted in place within the surrounding expression. Arrays return themselves, and blocks return their last expression or a pre-mature explicit return value.
The #
prefix also can generate Smalltalk-style #()
literal arrays, in which every expression within is just read as its symbolic name, and nothing is evaluated. Whitespace is used as the element separator.
All of these literal expression types can be nested within each other without the #
prefix since it all has been forced to compile-time already.
Slot-assignments
Slate creates two methods for slot access when slots are created.
foo addSlot: #x.
generates methods #x
and #x:
on foo
.
Local and input variables in blocks and methods (dispatched blocks) use this very same mechanism.
Assignments are expressions and return the assigned value.
In later Slate versions, there will be hooks to provide other accessor and mutator methods on slot creation.
Because keyword syntax is used, there is a precedence issue if you cascade assignments:
y: (x: (z w: 4)).
Messages
These are identical to those in Smalltalk and Self, only extended somewhat since Slate reserves different characters.
Unary Messages
theta sin quantity sqrt nameString size 1.5 tan rounded as: String "same result as (((1.5 tan) rounded) as: String)"
- Unary messages are messages without arguments.
- Unary messages are the most "tightly parsed" messages, and are parsed left to right. Hence, the last example answers the result of sending
#as: String
to the result of sending#rounded
to the result of sending#tan
to1.5
Binary Messages
3 + 4 " ==> 7 " 3 + 4 * 5 " ==> 35 (not 23) " 3 + 4 factorial " ==> 27 (not 5040) " total - 1 total <= max "true if total is less than or equal to max" (4 / 3) * 3 = 4 "==> true equality is just a binary message, and Fractions are exact" (3 / 4) == ( 3 / 4) "==> false two equal Fractions, but not the same object"
- Binary messages have a receiver, the left hand side, and a single argument, the right hand side. The first expression above sends
3
the message comprising the selector#+
with the argument4
. - Binary messages are always parsed left to right, without regard to precedence of numeric operators, unless corrected with parentheses.
- Unary messages bind more tightly than binary messages
Keyword Messages
12 between: 8 and: 15 " ==> true " {$t. $e. $s. $t} at: 2 " ==> $s " array at: index put: value "==> answers value, after storing value in array at index" array at: index factorial put: value "same, but this time stores at index factorial" 1 to: 3 do: aBlock "This sends #to:do: (with two parameters) to integer 1" (1 to: 3) do: aBlock "This sends #do: (with one parameter) to the Interval given by evaluating '1 to: 3'"
- Keyword messages have 2 or more arguments
- Keyword messages are the least-tightly binding messages. Binary and unary messages are resolved first unless corrected with parentheses.
Expression Sequences
expressionSequence ::= expression (. expression)* (.)opt
box origin: (Point x: 20 y: 30) corner: (Point x: 60 y: 90). box containsPoint: (Point x: 40 y: 50)
- Expressions separated by periods are executed in sequence.
- Value of the sequence is the value of the final expression.
- The values of all of the other expressions are ignored.
- A final period is optional.
(NOTE to Smalltalk and Self users: there is not yet a binary selector for Point creation. Also, there are no cascaded message-sends since their is no preferred "receiver" argument).
Block Expressions
Blocks are actually instances of the class Method. They are used all the time to build control structures. Blocks are created using the [ ] syntax around a sequence of expressions.
[expressionSequence] "block without arguments" [| (: identifier)+ | expressionSequence ] "block with arguments" [| (: identifier)+ identifier+ | expressionSequence ] "block with arguments and local variables"Smalltalkers should note that an extra vertical bar precedes declaration of local and input slots, and no vertical bars are required between inputs and locals.
[ 1. 2. 3 ] "a block which, when evaluated, will answer the value 3" [ object doWithSideEffects. test] "a block which, when evaluated, will send #doWithSideEffects to object, and answer the object test" [| :param | param doSomething ] "a block which, when evaluated with a parameter, will answer the result of sending #doSomething to the parameter."
- A block represents a deferred sequence of actions.
- The value of a block expression is an object that can execute the enclosed expressions at a later time, if requested to do so. Thus
[ 1. 2. 3 ] value ==> 3
- Language experts will note that blocks are equivalent to lambda-expressions, anonymous functions, or closures. Also, Slate is properly tail-recursive, though this can be disabled for debugging purposes in early versions.
Evaluation Messages for Methods
block value
- Evaluate the block represented by the receiver and answer the result.
block value: arg
- Evaluate the block represented by the receiver, passing it the value of the argument, arg.
block values: anArray
- Evaluate the block represented by the receiver. The argument is an Array whose elements are the arguments for the block. Signal an error if the length of the Array is not the same as the the number of arguments that the block was expecting.
Notes
- The message
#value
, sent to a block, causes the block to be executed and answers the result. The block must require zero arguments. - The message
#value: arg
, causes the block to be executed. The block must require exactly one argument; the corresponding parameter is initialized toarg
. - Slate methods also recognize
#value:value:
and#value:value:value:
. If you have a block with more than three parameters, you must use#values:
.
Activation methods
Activations are separate from Methods (blocks) in Slate.thisContext
refers to your activation rather than how currentMethod
obtains the block or method itself.
^ foo
Sends the ^
message to the context, causing it to exit to the next-outermost named method, returning foo as its value. Since this is a binary selector instead of reserved punctuation as in Smalltalk, returning of binary and keyword selector expressions must have its precedence adjusted, e.g.:
^ 3 + 4 ==> 3 ^ (3 + 4) ==> 7 ^ 3 bitAnd: 4 ==> 3 ^ (3 bitAnd: 4) ==> 0
resend
- Evaluates the innermost named message with the exact same arguments, but with the next method applicable. This is in contrast to super-sends in Smalltalk. This does not perform a return; its value can be stored in a local variable or used in another expression.
Other types of resends are defined upon Symbols.(Refer to the manual for these and their behavior.)
Named method definition:
_@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 its 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:
- No 'self' keyword - This is significant as it means that all variable-accesses must be done using accessors.
- Block argument declarations - We use double vertical bars to delimit the declarations, and inputs and locals can be mixed freely, but input variable order must match block value-binding order.
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:
- By default, blocks evaluate each expression in order and returns the value of the last expression. Since there is no 'self' or preferred argument, there's no un-ambiguous way to have an implicit return, so the last expression is always what's returned.
- Since there's an implicit return, any use of
^
returns is for early returns.^ oc newEmpty
in the above example exits all the blocks that enclose it up to the last method-call. One thing that's not obvious is that^
is actually a binary message send to the current method activation and the expression to return; like in Self, messages can be sent with an implicit first argument - the current method activation. So if you return a keyword expression, it has to be thus:^ (oc newSize: 20)
in order to evaluate things in the proper order.