(let ((x 3)) (set! x (+ x 1)))
| x <- 3 | x: x + 1
(define (f) (set! x (+ x 1))) (f)
| f = (x: x + 1) | f
(define (timesPlus x a b) (+ (* a x) b)) (timesPlus 3 2 2)
| times: a Plus: b = ((a * self) + b) | 3 times: 2 Plus: 2
(for-each (lambda (v) (set! x (+ x v))) '(1 2 3))
(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 |
_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
((| 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.
_Mirroron it. Dictionaries are accessed using the
_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"