Summary | ||
---|---|---|
Concept | Scheme | Self |
Arithmetic | (+ (* 2 3) 2)
| (2 * 3) + 2
|
Variables |
(let ((x 3)) (set! x (+ x 1))) |
| x <- 3 | x: x + 1 |
Functions |
(define (f) (set! x (+ x 1))) (f) |
| f = (x: x + 1) | f |
Multiple arguments |
(define (timesPlus x a b) (+ (* a x) b)) (timesPlus 3 2 2) |
| times: a Plus: b = ((a * self) + b) | 3 times: 2 Plus: 2 |
Conditional | (if (< x 0) -1 1)
| (x < 0) ifTrue: -1 False: 1
|
Block | (let ((x 3) (y 4)) (+ x y))
| [| x <- 3. y <- 4 | x + y ]
|
Loop |
(for-each (lambda (v) (set! x (+ x v))) '(1 2 3)) | (1 & 2 & 3) asList do: [|:v| x: x + v]
|
Object | none | (| x = 3. y <- 4. parent* = lobby |)
|
Reflection | none | (| x = 3 |) _Mirror at: 'x' Put: 4
|
(2 * 3) + 2 "prints 8" (2 @ 3) + (1 @ -2) "addition of two points; prints 3 @ 1" 'hello' , ' ' , 'there' "comma concatenates strings" (2 & 'foo' & 4.5) asSequence "a sequence of integer, string, float" (2 & 'foo' & 4.5) asList "a list" (2 & 'foo' & 4.5) asSet "a set" (('a' @ 2) & ('b' @ 3)) asDictionary "a dictionary"To create the various kinds of collections, we use '&' to make a generic collection and then "type-cast" it.
_AddSlots: (| x = 3 |) "bind x to 3; x is read-only" x + 1 "prints 4" _AddSlots: (| x <- 3 |) "bind x to 3; x is mutable" x: 4 "change x to 4" x: x + 1 "increment x; prints 5"When x is defined as a mutable slot, a method called "
x:
" is also defined. Calling this method with an argument
changes the value in the slot.
Since the syntax for adding slots is cumbersome, we're sometimes going
to be a bit sloppy and use the abbrevation | x = 3 |
instead of _AddSlots: (| x = 3 |)
.
_AddSlots: (| f = (x: x + 1) |) "bind f to a function" f "call the function" x "prints 6"There is no difference between reading a variable and calling a function (as in the case with f). This means that we can switch between storing a value to generating it on the fly, without making clients aware of it. In this sense, Self really doesn't have a special notion of "variable"; it only has slots and function calls which can modify slots.
Every function has an implicit argument called self, which is the value to the left of the function name at the point of call. Here is a function which uses self:
lobby _AddSlots: (| plus1 = (self + 1) |) "define the successor function" 4 plus1 "prints 5"By adding the function to the
In Self parlance, function calls are called
"Define a function which multiplies the receiver, self, by 'a' and adds 'b' to the result." lobby _AddSlots: (| times: a Plus: b = ((a * self) + b) |) 3 times: 2 Plus: 2 "prints 8"By comparison, in Sather we would say something like
3.timesPlus(2, 2)
. Note that the first keyword is
uncapitalized, while all successive keywords are capitalized. This is
used to disambiguate nested calls.
Setting a variable, e.g. x: 4
is really just a procedure
call with two arguments. The first argument is self (the
shell) and the second is the number 4.
(if (< x 0) -1 1)translates into
(x < 0) ifTrue: -1 False: 1
(let ((x 3) (y 4)) (+ x y))translates into
[| x = 3. y = 4 | x + y ]A few words of explanation are required here. A block has the form
[|slots| code ]where both the slots and the code are optional. The value of a block is the result of executing the code in the environment defined by the slots. Slots are separated by periods, and have the form
name =
value
(immutable slot) or name <- value
(mutable
slot). We saw this notation earlier with respect to objects. In
fact, blocks are just a kind of short-lived object; their slots can be
accessed from the outside and they can be passed to functions (but
not returned from them).
[x > 0] whileTrue: [ x: x - 1 ] "evaluates the right block until the left block answers false" (1 & 2 & 3) asSequence do: [ x: x + 1 ] "increments x three times" (1 & 2 & 3) asSequence do: [|:v| x: x + v] "adds 6 to x"The last example demonstrates a block which takes an argument v. The argument is designated by a slot name with a leading colon. The
do:
method for a sequence calls the
block with every member of the sequence. In the second case, the
block had no arguments so these values were ignored. We can create
new objects which behave like sequences just by defining a
do:
function for them. Or we can define any new control
structures we want.
_AddSlots:
works: it copies the
slots in the object on the right into the object on the left. There
is no syntax for adding a slot to an existing object, so we have to
create new objects and throw them away.
To print an object in a low-level format, use the _Print
function:
((| x = 3 |) _AddSlots: (| y <- 4 |)) _Print "prints ( | x = 3. y <- 4. | )"
A slot defined with an arrow is a normal data member. A slot defined with the equals sign is a read-only constant member. If the slot name ends with a star, then the slot is a parent slot. If a slot lookup fails on an object, then the parent slots are searched in order. This is similar to multiple inheritance in Python. The difference is that in Self a parent slot can be a mutable data member, allowing an object to change its behavior at runtime.
_Mirror
on it.
Dictionaries are accessed using the at:
and
at:Put:
functions:
_AddSlots: (| d = (('a' @ 2) & ('b' @ 3)) asDictionary. o = (| a = 2. b = 3 |) |) d at: 'b' "prints 3" d at: 'b' Put: 4 d at: 'b' "prints 4" o _Mirror at: 'b' "prints b = 3" o _Mirror at: 'b' Put: 4 o _Mirror at: 'b' "prints b = 4" (o _Mirror at: 'b') name: 'c' "changes name of slot b to c" o c "prints 4"