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 {
.foo = serverObject.foo
localObject}
if localObject.bar != serverObject.bar {
.bar = serverObject.bar // There was an (intentional)
localObject} // 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:
get(fromSelector: String) -> AnyObject?
func set(forSelector: String) -> () func
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 {
.foo = serverObject.foo
localObject}
if localObject.bar != serverObject.bar {
.bar = serverObject.bar
localObject}
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:
checkThenUpdate(selector) {
func if localObject.selector != serverObject.selector {
.selector = serverObject.selector
localObject}
}
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:
<Whole, Part> {
struct Lens: Whole -> Part
let get: (Whole, Part) -> Whole
let set}
(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:
: Lens<LocalPerson,String> = Lens(
let localName: { p in p.name },
get: { (oldPerson,newName) in
setvar newPerson = oldPerson
.name = newName
newPersonreturn newPerson
}
)
And you’d use it like this:
= LocalPerson(age: 46, name: "caoimhe")
let caoimhe .get(caoimhe) // 46
localName.set(caoimhe, "breifne") // LocalPerson(age: 46, name: "breifne") localName
Straight away, we’re able to do (something) like the checkThenUpdate
function:
func checkThenUpdate<A: Equatable>
(localLens: Lens<LocalPerson,A>, serverLens: Lens<ServerPerson,A>) {
= serverLens.get(serverObject)
let serverProp if localLens.get(localObject) != serverProp {
= localLens.set(localObject,serverProp)
localObject }
}
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 Name
associatedtype var name: Lens<Self,Name> { get }
static var name: Name { get set }
}
// Because the mutable property is included in the protocol, you can rely on
// it in extensions:
{
extension HasName var name: Lens<Self,Name> {
static return Lens(
: {$0.name},
get: { (w,p) in
setvar n = w
.name = p
nreturn n
}
)
}
var name: Name {
{ return Self.name.get(self) }
get { self = Self.name.set(self,newValue) }
set }
}
// This way, you can provide either the lens or the property, and you get the
// other for free.
: HasName {}
extension Place
// 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.