Previously on SICPers, I defined objects as functions that return methods and built dynamic method dispatch in this object system. It’s time to tie up some loose ends.
Proper selectors
In languages like Smalltalk and Objective-C, an object’s range isn’t a small list of selectors like count
and at:
. It’s the whole of the String
type.
typealias Selector = String
Now an object takes a Selector
(i.e. a String
) and returns a method implementation. Continuing with the type-safe theme from the previous posts, I’ll define a few different types of implementation that I might want to use.
enum IMP {
case accessor(()->((Selector)->IMP)?)
case asInteger(()->Int?)
case methodMissing(()->((Selector)->IMP)?)
case mutator(((Selector->IMP))->Void)
case description(()->String?)
}
typealias Object = Selector -> IMP
Now I can create some proper objects.
func DoesNothing()->(_cmd:Selector)->IMP {
var _self : Object! = nil
func myself (selector: Selector)->IMP {
return IMP.methodMissing({assertionFailure("method missing: \(selector)"); return nil;})
}
_self = myself
return _self
}
let o : Object = DoesNothing()
func Integer(x: Int, proto: Object) -> Object {
var _self : Object! = nil
let _x = x
func myself(selector:Selector) -> IMP {
switch(selector) {
case "asInteger":
return IMP.asInteger({ return _x })
case "description":
return IMP.description({ return "\(_x)" })
default:
return proto(selector)
}
}
_self = myself
return _self
}
let theMeaning = Integer(42, o)
A better syntax
Usually you don’t think of method lookup as a function invocation, but rather as finding a member of an object (indeed in C++ they’re called member functions). A member-like syntax could look like this:
infix operator .. {}
func .. (receiver: Object?, _cmd:Selector) -> IMP? {
if let this = receiver {
let method = this(_cmd)
switch(method) {
case .methodMissing(let f):
return f().._cmd
default:
return method
}
}
else {
return nil
}
}
This system now has the same nil
behaviour as Objective-C:
nil.."asInteger" // nil
And it also has a limited form of default message forwarding:
func Proxy(target:Object)->((_cmd:Selector)->IMP) {
var _self : Object! = nil
var _target = target
func myself(selector:Selector) -> IMP {
return IMP.methodMissing({ return _target })
}
_self = myself
return _self
}
let proxyMeaning = Proxy(theMeaning)
let descriptionImp = proxyMeaning.."description"
descriptionImp!.describe() // (Enum Value)
But it’s going to be pretty tedious typing all of those switch
statements to unbox the correct implementation of a method. There are two ways to go here, one is to add methods to the enumeration to make it all easier. These just unbox the union and call the underlying function, so for example:
extension IMP {
func describe() -> String? {
switch(self) {
case .description(let f):
return f()
default:
return nil
}
}
}
descriptionImp!.describe() // "42"
Or if you’re really going to town, why not define more operators?
infix operator → {}
func → (receiver: Object?, _cmd:Selector) -> Object? {
if let imp = receiver.._cmd {
switch (imp) {
case .accessor(let f):
return f()
default:
return nil
}
} else {
return nil
}
}
func ℹ︎(receiver:Object?)->Int? {
if let imp = receiver.."asInteger" {
switch(imp) {
case .asInteger(let f):
return f()
default:
return nil
}
} else {
return nil
}
}
ℹ︎(theMeaning)! // 42
Mutable Objects
There’s no reason why an object couldn’t close over a var
and therefore have mutable instance variables. You can’t access the variables from outside the object in any way other than through its methods[*], even from objects that inherit from it. They’re all private.
[*] Unless you were to build a trap door, e.g. declaring the var
in a scope where some other function also has access to it.
Firstly, some similar notation:
infix operator ☞ {}
func ☞ (mutator:IMP?, value: Object) -> Void {
if let mut = mutator {
switch(mut) {
case .mutator(let f):
f(value)
default:
return
}
}
}
Here is, somewhat weirdly, a class of Point
objects where you can redefine both the x
and y
coordinates after creation.
func Point(x: Int, y: Int, proto: Object)->((_cmd:Selector)->IMP) {
var _self : Object! = nil
var _x = Integer(x,o), _y = Integer(y,o)
func myself (selector:Selector) -> IMP {
switch (selector) {
case "x":
return IMP.accessor({
return _x
})
case "y":
return IMP.accessor({
return _y
})
case "setX:":
return IMP.mutator({ newX in
_x = newX
})
case "setY:":
return IMP.mutator({ newY in
_y = newY
})
case "description":
return IMP.description({
let xii = ℹ︎(_self→"x")!
let yii = ℹ︎(_self→"y")!
return "(\(xii),\(yii))"
})
default:
return proto(selector)
}
}
_self = myself
return _self
}
In use it’s much as you’d expect.
let p = Point(3, 4, o)
(p.."description")!.describe() // "(3,4)"
ℹ︎(p→"x") // 3
(p.."setX:")☞(Integer(1,o))
ℹ︎(p→"x") // 1
(p.."description")!.describe() // "(1,4)"
But there’s no need to bother
It’s possible to build mutable objects out of immutable objects. Given a Point(3,4,o)
, you can make an object that looks exactly like a Point
at (1,4) by building an object that has replacement implementations for x
and description
, but otherwise forwards all methods to the original.
In this way, mutation would look a lot like the Command pattern. You have your original state, you have a linked list (via the prototype chain) of modifications to that state, and the external view is as if you only had the final state.
Further further advances
It’d be good to be able to tell a method what its self
is, to make inheritance work properly. For example, if a Point
‘s description
knew what object was self
, then replacing x
on a subtype would automatically get the description right. Ben Lings shows how this might work on the previous post’s List
objects.
Further thanks
Jeremy Gibbons initiated the discussion on mutable objects that led to the implementation shown above. Thanks to Lawrence Lomax for lots of feedback and discussion about how this should all work, including an alternate implementation.