Part III — Objects

Classes, instances, methods, extension, the meta-object, mixins, and how a message is dispatched to a method (including multimethods).

Nav: Foundations · Blocks & control · Objects · Patterns & errors · Concurrency & iteration · Networking & the web · Types · Tooling · Library & reference · Appendices


10. Classes, methods & extension

Rules

Point <- { |@x @y|
    .meta <-- {
        newX:y: -> { |x y| .new:{ x = x; y = y } }   "* a class-side factory
    }

    x -> { @x }
    y -> { @y }
    dist: -> { |other| (((@x - other.x) * (@x - other.x))
                      + ((@y - other.y) * (@y - other.y))).sqrt }
}

Point <- Point3D <- { |@z|                            "* subclass adds @z
    z -> { @z }

    "* Override dist: for 3D, reusing the parent's .x / .y accessors
    dist: --> { |other| (((.x - other.x) * (.x - other.x))
                       + ((.y - other.y) * (.y - other.y))
                       + ((@z - other.z) * (@z - other.z))).sqrt }
}

var p = Point.newX:3 y:4
p.x                                                   "* -> 3
var a = Point3D.new:{ x = 0; y = 0; z = 0 }
var b = Point3D.new:{ x = 1; y = 2; z = 2 }
a.dist:b                                              "* -> 3

Reopen a class with <-- to add methods later; extend a single value with <-- to give just that object new behavior (a singleton/eigenclass, named $Type internally). Note that a sealed class refuses both (§12) — and the value built-ins (Integer, Double, Boolean, Nil, List, Map, Set, NumberRange) ship sealed (the full list, and why sealing matters to the optimizer: Part VII §34); String is open:

String <-- { shout -> { .upper + '!' } }      "* every String gains shout
'code'.shout                                  "* -> 'CODE!'
var s = 'plain'
s <-- { fancy -> { '~' + self + '~' } }       "* only this one string gains fancy
s.fancy                                       "* -> '~plain~'

Note — redefining vs. adding a variant. Within one class, a later definition with the same signature (same parameter types, no guard) replaces the earlier one: bar -> {1} then bar --> {2} makes bar return 2. --> additionally requires the selector to already exist in the hierarchy. Definitions that differ by parameter type or carry a guard are instead kept as distinct multimethod variants (§13) and dispatched by argument, not replaced. (A subclass defining an inherited method — as Point3D does with dist: above — takes precedence for its own instances via method resolution; see §12.)


11. Construction & initialization

Rules

Person <- { |@name @greeting|
    init: -> { |name| @name = name; @greeting = 'Hello, ' + name }
    greeting -> { @greeting }
}

(Person.new:{ name = 'Ada' }).greeting        "* 'Hello, Ada'

new (no block) runs the init of every class in the hierarchy. new:{…} runs the block first (binding the fields you assign), then runs the chain, with each class's init: receiving the block fields whose names match its parameters.

⚠ Gotcha — a plain-assignment init: is redundant. Fields named in the new:{…} block are copied into the object before any init: runs, so init: -> { |a| @a = a } just re-does work already done — it behaves identically to having no init: at all. Use init: for derived or validated state, not plain copies.

Note — there is no super. A subclass init: cannot call its parent's initializer with computed arguments; the parent runs first off the raw block fields. If a child needs to set a parent's field, it assigns @field directly.


12. Inheritance & mixins

Rules

Greeter <- { hello -> { 'hi from ' + .class.name.s } }

Widget <- {
    .mix:Greeter
    name -> { 'widget' }
}

Widget.new.hello       "* 'hi from Widget'   (found via the mixin)

13. Multimethod dispatch

Rules

Describer <- {
    describe: -> { |n:Integer| 'int ' + n.s }
    describe: --> { |s:String|  'str ' + s }
    describe: --> { |n:Integer { n > 100 }| 'big number' }   "* a guard refines :Integer
}

var d = Describer.new
d.describe:5         "* -> 'int 5'
d.describe:'hi'      "* -> 'str hi'
d.describe:150       "* -> 'big number'

Type-based variants are the right tool when you want different behavior per argument type; the dispatcher chooses the most specific match: 5 fails the guard, so the plain :Integer variant handles it, while 150 passes it and the guarded variant outranks the unguarded one.


Next: Part IV — Patterns & errors.