Friday, August 23, 2013

Monkeypatch or mixin? -- it's all in the compiler, not the runtime

And C# extension methods are really monkey-patching.

Let's look at this example -- MSFT provided events and a handler mechanism; ElegantCode writes an extension method to do the necessary null handler check, and only when I include the ElegantCode namespace does the compiler see that myEvent.Fire(...) is a valid call. The MyExtensions type has disappeared from sight -- it doesn't appear as a useful type at any point; and the extension method only appears to the compiler when an object is named explioitly -- no implicit this from inside a type named in an extension method.

In contrast, let's consider the following Scala snippet (which parallels the types in my previous post)

If we build this with the (once again abandoned) .net compiler, then decompile, we get this C# code

The underlying mechanism by which the mixin (trait) is rendered -- static methods in a separate class holding the implementation -- is the same as for C# extension methods having the same effect (see previous post). The difference is that the trait type Stringify has appeared as a significant type : it's an interface, and the static methods are on a separate synthetic class.

The difference then continues into the consumers -- a type has to opt in to the trait explicitly

or, when we decompile,

and here is the trade-off. We have a significant type (which other types can consume), and we do get implicit this, but the trait has to be inherited to become available, and delegating methods are injected to provide implicit this. (Parenthetical note -- Scala makes the use of the token Library in the type definition unambiguous; another compiler-level difference)

Extension methods are completely backwards compatible -- you can use LINQ without having to change all the collection types in your code simply by including the namespace of the new extension methods, but you don't get a new orthogonal type that expresses "those classes expressing the extensions" -- the common class is the pre-existing one that the extension method has as its this parameter.

If you want to, you can manually add a marker interface to your C# code, and have your extension methods work on instances of that interface as the this parameter, in the same way as Stringify in the Scala example appears as an interface type. That provides something closer to mix-in behaviour that can be applied across otherwise unrelated types, but that is a separate manual step, and still leaves you with monkeypatched methods -- you can't declare the mix-in methods in that interface, and you still need explicit this, even though the IL is much the same in each case.

No comments :