I didn’t realise this at the time, the previous entry wasn’t the last Objective-Swift post.
The inheritance mechanism in ObjS is prototypical, meaning that an object inherits from a single other object rather than getting its behaviour from a class. This is the same system that Self and languages that, um, inherit its approach use.
This form of inheritance gives us two capabilities: decorating an object by attaching new methods, or updating an object by shadowing existing methods. And that means that setting a variable can be mimicked by returning an object where the accessor for that variable has been overridden. So in fact the objects in this system won’t be mutable at all, but they will have mutators that return replacement values.
It sounds weird, but there are reasons to really do this. In Postscript, you can mimic variables by creating named functions that just push a value onto the stack. Rather than mutating the data, you “set” the variable by rebinding the name to a function that pushes the updated value.
Digression: updating the dispatch system
In rewriting setters to be immutable I also noticed it was possible to consolidate the “accessor” and “mutator” implementation types into a single type, using variadic arguments. I have genuinely no idea why I didn’t realise that before, when the type of an ObjC method is usually expected to be (id, SEL, id...) -> id
.
enum IMP {
case method((Selector->IMP, Selector, Selector->IMP...)->(Selector->IMP)?)
case asInteger((Selector->IMP, Selector, Selector->IMP...)->Int?)
case methodMissing((Selector->IMP, Selector, Selector->IMP...)->(Selector->IMP)?)
case description((Selector->IMP, Selector, Selector->IMP...)->String?)
}
The asInteger
and description
types gain variadic arguments but still need to be present so you can exit the O-O type system and get back to Swift types. For the moment, methodMissing
is still modelled as a separate case in the enumeration, even though it has the same type as a regular method.
Mutation as a function
Given an object, a selector to override, and the replacement value, return a new object that will return that value when messaged with that selector.
infix operator ☞ {} func mutate(receiver: Object, selector: Selector, value:Object) -> Object { return { _cmd in switch (_cmd) { case selector: return .method({ _ in return value }) default: return receiver(_cmd) } } } func ☞(message:(receiver: Object?, selector: Selector), value:Object) -> Object? { if let receiver = message.receiver { return mutate(receiver, message.selector, value) } else { return nil } }
Taking the Point
objects from the further advances post, but deleting their mutators, this can be used to make updated objects.
let p = Point(3, 4, o) 📓(p) // (3,4) let p2 = (p,"x")☞(Integer(1,o)) 📓(p2) // (1,4)
Mutation as a method
The same function can be used in the implementation of an Objective-Swift method. Here’s the setY:
method on Point
s:
return IMP.method({ (this, aSelector, args : Object...) in
return (this, "y")☞args[0]
})
Now a bit of notation to make calling mutator methods easier (I’m starting to run out of reasonable symbols):
infix operator ✍ {} func ✍(message:(receiver:Object?, selector:Selector), value:Object) -> Object? { if let imp = message.receiver..message.selector { switch imp { case .method(let f): return f(message.receiver!, message.selector, value) default: return nil } } else { return nil } }
Done.
let p3 = (p2, "setY:")✍(Integer(42, o)) 📓(p3) // (1,42)
Fork me
I have put the current playground (which is written for the Swift 1.2 compiler, because I’m risk averseold fashioned) on GitHub.