Skip to content

Further Advances in Objective-Swift

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.

Post a Comment

Your email is never published nor shared. Required fields are marked * Comments are moderated; please make sure that your post is civil and valuable before submitting it to improve the chance it will be accepted.