Thursday, April 2, 2015

Custom Swift Switch Matchers

Joshua Emmons:

Wouldn’t it be great if we could teach switch new matchers that knew how to deal with our custom objects? Believe it or not, there’s an operator for that.

It’s called the pattern match operator, sometimes known by its (less googleable) syntax: ~=. And switch statements use it behind the scenes to compare values to their cases’ matching patterns.


This is yet another example of Swift employing polymorphism in an unexpected way to make the type system do work for us. Work that results in very clean, concise, and — dare I say it — declarative code.

4 Comments RSS · Twitter

I already envision myself digging through dozens of source code files, until I found where the ~= operator was redefined.

Barbara Liskov wept.

I suspect the real problem (excepting programmers' endless self-adoration of their own extreme cleverness, of course) is that Swift's pattern matching simply isn't powerful, expressive, or concise enough in its current form. (First attempts at a new language design always suck.) No way should users have to type wibble like case let x where"foo": just to match on an attribute. Just treat the object like a record and match case {name="foo"}:. That being said, deficiencies in Swift are still no excuse for bad programming; even less so for broadcasting it.

What really made me headdesk though is the "employing polymorphism in an unexpected way" at the end of it all. Sure it's "unexpected"... if you are utterly blissfully ignorant of 99% of what Computer Science is all about. Anyone who's spent 5 minutes bothering to learn about languages that aren't just repetitive knockoffs of the cultural catastrophe that is C knows functional languages have been doing extremely powerful, expressive, and concise pattern matching for decades now.

How ML and Haskell folks must look upon the rest of us, and just laugh. :$

I'm in this to learn, so I welcome pointing out the ways in which I'm wrong about something. Still, if all one offers is criticisms without solutions, the appearance is less of one interested in improving the community, and more of one enamoured with the sound of their own voice.

@jemmons: The point of ~= is to match one whole object against another whole object, without the need to write an explicit cast each time if the objects' types are different. It's like having the convenience of coercions, but in a type-safe way.

Look again at the Apple example you linked: it preserves all the original data, and only compensates for the different representations. The way you've done it, you've completely changed the behavior of the ~= operator. Now instead of comparing two objects ignoring type, it's manipulating one of those objects and then comparing the result of that operation against the other object.

You're abusing the type system to do additional, non-type-related work that should be done in your own code. Google "Liskov substitution principle". Just because you can do a thing does not mean it's the correct thing to do. As Ilja implies, pity the poor soul (often yourself!) who has to make sense of such invisible magical behaviors emerging out of nowhere a few years from now.

And honestly, you've gotten yourself so lost in all this extreme cleverness, you've completely missed the correct (i.e. 100% non-magical, obvious, straightforward, simple, and self-explanatory) solution:

switch ( {
    case "Foo":
    case "Bar":

Don't feel entirely bad - we all fall into these traps sometimes - but if you're not vigorously headdesking yourself after this then you really need to keep re-reading and re-learning and kicking your own beliefs and assumptions until you do.

Leave a Comment