When I talk about dynamic programming on iOS and Mac, I mean this:
An app can learn about its structure at runtime.
An app can do things based on what it knows about its structure.
An app can make changes to its structure.
Brent is explaining this in an effort to see that the worthwhile qualities of Objective-C are not sacrificed to the gods of method call devirtualization. But his criteria are interesting. Almost everything about it is possible in nearly every language – some things are just major pains in the ass.
For example, in C#, my day time language (and increasingly night time), all of them are possible. 1 is easy due to reflection, 2 is easy due to reflection and the type system and 3 is easy if you share a predilection for hand-writing IL, where IL is the ungodly Intermediary Language used by the CLR virtual machine. (Okay, it’s a little bit easier with the modern Roslyn compiler which has an API for compiling C# code and loading code in memory, but the process for unloading it or replacing it is an emphatic “uh, let me get back to you on that”.)
Swift started out not very dynamic at all, but is at least now largely introspectable through “mirrors”, which I’m told is a sane(r) form of reflection. In Objective-C, the Objective-C runtime API’s information is willfully obtuse, because guess what, it’s the same API used to implement all the behind the scenes shenanigans to make message passing and classes and metaclasses and instance creation and method adding a thing in the first place, and it’s been optimized out the wazoo. Hello, Type Encoding and goodbye, scrutability.
In Swift, mirrors are comically loosely-typed-feeling, but still hard to decipher and still technically an API in flux. I have tried to write a dynamic JSON deserializer-and-class-populator and it was not self-describing. In C#, the reflection metadata is almost annoyingly precise and a task like that is just about as easy as that sort of thing will ever be likely to get.
The question isn’t really whether dynamism is needed. Many sharp brains have pointed out that the frameworks underlying Cocoa today would not be readily implementable in the pure Swift. Brent does a good job explaining why “just writing the code to solve the problem while using Swift idiomatically below” is a step backwards. Drew Crawford explains why there sometimes really really is no other solution.
What I’m wondering is whether there’s some amount of room for a third way. All of this, or at least parts, may be completely insane. I have not done any engineering of compilers or runtimes before.
What no one wants or likes is to have to wire up everything yourself. Code generation is a partial solution for this, but ginormous switch statements and lookup tables are also considered wildly inelegant. Brent quotes Guy English: “If you see a switch statement or dispatch table they blew it. Boilerplate that needs to be managed is a stagnant pool for nasty bugs.”
But lookup tables are exactly how message passing works. The problem isn’t that they exist, it’s any solution where, as Guy reminds us, you have to manage and see boilerplate.
So what if Swift let you code classes that looked like Swift but behaved dynamically? What if Swift let the virtualness of methods bleed out into runtime, and it could gradually be bolted down as the application ran, depending on the actual types, actual objects, actual methods, actual messages passed, call site by call site? What if that metadata was allowed to remain, could be used later and maybe even be extended?
I know that Swift creates “witness” intermediaries to make sure the right usage or definition of a method is captured. What if sending the
foo:bar: message could be translated into a special failable method call on a compiler-construed catch-all protocol extension, for example, ensuring that sending it down a responder chain wouldn’t cause everything to come tumbling down?
I know that the Java HotSpot VM was capable of doing specialization optimizations that it could back out of whenever the optimizations stopped making sense – does that really take a JIT to do and not “just” (LLVM committers are given free permission to sneer knowingly now) some self-healing trampolines? What if suddenly sending that message to a proxy object would still end up working fine, without selling out the potentially much faster optimized version that is already being run 500 times per second? Objective-C was compiled because C was compiled. Swift is compiled by choice. What’s to say that it has to continue to follow the one-big-compilation model, or compile all the code, or compile only the code apparent when the build starts?
What if dynamic things could be dynamic, and Swift could be good at them too?
What if I’m full of shit and/or out of my depth here? The things I mention are things that to my intuition seem like puzzle pieces that could snap together. But I’m just following a few things I have read a bit about and mentioning them in the vicinity of each other. If it turns out that I’m wrong, well, I fully expect to be.
What I also expect is there to be a dialogue right now about these kinds of details by people who know what they’re talking about, about how Swift could actually become more dynamic without copying the flaws of its progenitors and without selling out the feel, mechanics and (most of the) performance of the language. I’m not seeing too much of it, and even if nothing came of it I’d love to read those sorts of thoughts too – even just a description about how and why my spitballing is logically inconsistent grasping at straws and the language design equivalent of alphabet soup, if that’s what it boiled down to.