Mutable objects in immutable objects in object-oriented programming in functional programming in Swift

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 Points:

  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.

About Graham

I make it faster and easier for you to create high-quality code.
This entry was posted in FP, OOP. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.