Lenses are Static Selectors

So I don’t really know what KVC is, or much about performSelector functions. This blogpost, from Brent Simmons, let me know a little bit about why I would want to use them.

It centred around removing code repetition of this type:

if localObject.foo != serverObject.foo {
  localObject.foo = serverObject.foo

if localObject.bar != serverObject.bar {
  localObject.bar = serverObject.bar // There was an (intentional)
}                                    // bug here in the original post

To clean up the code, Brent used selector methods. At first, I was a little uncomfortable with the solution. As far as I could tell, the basis of a lot of this machinery used functions with types like this:

func get(fromSelector: String) -> AnyObject?
func set(forSelector: String) -> ()

Which seems to be extremely dynamic. Stringly-typed and all that. Except that there are two different things going on here. One is the dynamic stuff; the ability to get rid of types when you need to. The other, though, has nothing to do with types. The other idea is being able to pass around something which can access the property (or method) of an object.

Let’s look at the code that was being repeated:

if localObject.foo != serverObject.foo {
  localObject.foo = serverObject.foo

if localObject.bar != serverObject.bar {
  localObject.bar = serverObject.bar

The logical, obvious thing to do here is try refactor out the common elements. In fact, the only things that differ between the two actions above are the foo and bar. It would be great to be able to write a function like this:

func checkThenUpdate(selector) {
  if localObject.selector != serverObject.selector {
    localObject.selector = serverObject.selector

And then maybe a single line like this:

[foo, bar, baz].forEach(checkThenUpdate)

That’s pretty obviously better. It’s just good programming: when faced with repetition, find the repeated part, and abstract it out. Is it more dynamic than the repetition, though? I don’t think so. All you have to figure out is an appropriate type for the selector, and you can keep all of your static checking. To me, it seems a lot like a lens:

struct Lens<Whole, Part> {
  let get: Whole -> Part
  let set: (Whole, Part) -> Whole

(This is a lens similar to the ones used in the data-lens library, in contrast to van Laarhoven lenses, or LensFamilies. LensFamilies are used in the lens package, and they allow you to change the type of the Part. They’re also just normal functions, rather than a separate type, so you can manipulate them in a pretty standard way. Swift’s type system isn’t able to model those lenses, though, unfortunately.)

It has two things: a getter and a setter. The getter is pretty obvious: it takes the object, and returns the property. The setter is a little more confusing. It’s taking an object, and the new property you want to stick in to the object, and returns the object with that property updated.

For instance, if we were to make a Person:

struct LocalPerson {
  var age: Int
  var name: String

We could then have a lens for the name field like this:

let localName: Lens<LocalPerson,String> = Lens(
  get: { p in p.name },
  set: { (oldPerson,newName) in
    var newPerson = oldPerson
    newPerson.name = newName
    return newPerson

And you’d use it like this:

let caoimhe = LocalPerson(age: 46, name: "caoimhe")
localName.get(caoimhe) // 46
localName.set(caoimhe, "breifne") // LocalPerson(age: 46, name: "breifne")

Straight away, we’re able to do (something) like the checkThenUpdate function:

func checkThenUpdate
  <A: Equatable>
  (localLens: Lens<LocalPerson,A>, serverLens: Lens<ServerPerson,A>) {
  let serverProp = serverLens.get(serverObject)
  if localLens.get(localObject) != serverProp {
    localObject = localLens.set(localObject,serverProp)

And it could be called pretty tersely:

checkThenUpdate(localName, serverLens: serverName)

The biggest problem with this approach, obviously, is the boilerplate. In Haskell, that’s solved with Template Haskell, so the lens code is generated for you. (I’d love to see something like that in Swift)

There’s a protocol-oriented spin on lenses, also. One of the variants on lenses in Haskell are called "classy-lenses". That’s where, instead of just generating a lens with the same name as the field it looks into, you generate a typeclass (protocol) for anything with that lens. In Swift, it might work something like this:

struct Place {
  var name: String

// Instead of just having a lens for the name field, have a whole protocol
// for things with a name field:

protocol HasName {
  associatedtype Name
  static var name: Lens<Self,Name> { get }
  var name: Name { get set }

// Because the mutable property is included in the protocol, you can rely on
// it in extensions:

extension HasName {
  static var name: Lens<Self,Name> {
    return Lens(
      get: {$0.name},
      set: { (w,p) in 
        var n = w
        n.name = p
        return n
  var name: Name {
    get { return Self.name.get(self) }
    set { self = Self.name.set(self,newValue) }

// This way, you can provide either the lens or the property, and you get the
// other for free.

extension Place: HasName {}

// Then, you can rely on that protocol, and all of the types:

func checkEqualOnNames
  <A,B where A: HasName, B: HasName, A.Name: Equatable, A.Name == B.Name>
  (x: A, _ y: B) -> Bool {
    return x.name == y.name

This protocol lets you do a kind of static respondsToSelector, with all of the types intact.

Other people have spoken about the other things you can do with lenses in Swift (Brandon Williams – Lenses in Swift), like composing them together, chaining operations, etc. (One other thing they can emulate is method cascading) Unfortunately, in current Swift, the boilerplate makes all of this a little unpleasant. Still, they’re an interesting idea, and they show how a good type system needn’t always get in the way.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s