|
Recently one of Nemerle users played a little bit with our macro system and implemented a feature allowing dynamic invocation of methods or accessing properties in a quite clean way (as you probably know, Nemerle is entirely statically typed and forces your code to be compliant with .NET standard object-oriented access to classes). The simplest example is
Here we just want a method to fetch the Length property of given object and late keyword introduced by macro allows us to do so easily. The biggest advantage here is that we do not care about the intefaces and common supertypes of object we use this on, so we can apply this method to ANY object (which is assumed to contain the correct property). The call will be made using .NET's dynamic invocation feature.
The idea is a little bit similar to something called duck typing (though the name is a bit politically controversial in my country at the moment ;p), but in case of our macros it works in a different way. We do not introduce any dummy "types" for objects, which are not type-checked, but we allow user to specify which parts of code should be dynamic (for more detailed specification see a deciated page).
Now the question is why is it really useful? In general I'm always heading towards the perfect world and I prefer my programs to be proved being perfect and safe. Type inference is a powerful Nemerle's feature, which helps here - I do not need to specify types of variables and compiler can infer what I mean; if it can't, it usually means I made some mistake. But here comes the problem, sometimes the code we write is simply not compatible with language / platform type system and compiler complains about my code even though I know it is right. Let us consider another example:
Now as you can see my perform function is quite tricky - it does not call Move on object if I do not specify with_move parameter to be true. So its contract is something like "calls Draw, but if we want move also calls Move", which is not expressible in any simple type system (e.g. .NET). With our late binding macro we just ignore the type system and perform all calls dynamically. :)
One more thing to notice here is that usually we can avoid dynamic calls by using well designed object hierarchy and interfaces (if we are not too lazy of course ;) ). In our example we can easily make the call to Draw statically safe, by introducing the IDrawable interface.
Note that now type inference comes in help - the signature perform (x : IDrawable, with_move : bool) : void is automatically guessed by compiler. The only late bound operation now is a call to Move. To solve this we could do two things:
The other cases when late binding might be useful is when you use a platform's feature, which is very dynamic by nature - like using types obtained through COM.