Welcome to the Valix Wiki!
There are three types of methods, syntactically speaking. Unary, binary, and keyword methods differ only in how they are called. They are very similar in that the method name follows the object after a space. Unary methods have no argument, and binary methods have exactly one argument. Keyword methods can have one or more arguments.
5 factorial. // 120 5 + 7. // 12 5 logBase: 2. // 2.32
“Cascades” are also allowed, which allows one to send multiple messages to one object rather than to the result of the previous message:
Terminal print: 'a'; print: 'b'; print: 'c'.
myVar = value. // assignment myVar == value. // equality myVar is: value. // identity myVar isa: value. // relation (inheritance)
(a, b, c) // array "abc" // string 'a' // character 123 // integer 12.3 // double
There are no immediate ways to declare dictionaries or linked list. Instead, constructions like the following are used:
myList = LinkedList new: (1, 2, 3). myDict = HashTable new: ("abc" -> 1, "def" -> 2, "ghi" -> 3).
Blocks are objects that act as first-class closures. All methods are represented as blocks in the language, and blocks can be used like lambda functions in other languages. The terms “Block” and “Closure” can be used interchangeably.
// The basic format of a block: { :arg1 :arg2 | localvar1 localvar2 | /* do some code here */ } // arguments can be left out, as can local variable declarations: { :arg1 :arg2 /* do some code here */ } { | localvar1 localvar2 | /* do some code here */ } { /* do some code here */ }
| myBlock | myBlock = { Console print: "world!" }. (5 < 6) ifTrue: { Console print: "Hello, ". } (7 < 8) ifTrue: myBlock. myBlock = { :x :y x * y }. Console print: (myBlock :20 :12).
The output of that code would be
Hello, world!240
Object definitions are contained in @{} to differentiate with block definitions. There are no classes; instead of subclasses, objects may inherit features of other objects by indicating another object as its prototype. Traits may also be specified (traits are discussed later). A prototype object must be specified (usually “Object”).
myObject = [ PrototypeObject, Trait1, Trait2 | | someMethod: arg1 and: arg2 { | localvars | /* method body here */ } unaryMethod { /* ... */ } + arg /* this is a binary method definition, defining the call "myObject + val" */ { /* ... */ } ].
Traits are simply a set of methods. An object which inherits from a trait will gain all its methods (unless they are explicitly overridden with a different definition). Traits cannot access or define state variables directly, but they can call methods on self to interact with the object implementing that trait.
If two traits define the same method, the trait listed first in the object definition takes priority.
myTrait = [ someMethod: someArgument { /* method declaration */ } ].
This example illustrates the working model of worlds. “thisWorld” is a special variable. More information at http://www.vpri.org/pdf/tr2011001_final_worlds.pdf
| point A | point = [ Object | | x y | set: _x and: _y { x = _x. y = _y. } print { Console print: x. Console print: " ". Console print: y. Console print: "\n". } ]. point set: 5 and: 6. point print. A = thisWorld spawn. A eval: { point set: 8 and: 9. }. point print. A eval: { point print. }. A commit. point print. // output: 5 6 5 6 8 9 8 9
Since worlds control side effects, they are used to implement error handling.
thisWorld spawn eval: { Console print: (1 / 0). } on: (Exception,) do: { :error Console print: error. thisWorld revert. }. // output: DivideByZero