re-motion mixins basics -- duck typing
Duck typing in dynamic languages
What advocates of "dynamic" (dynamically typed) languages like about them is duck typing.
In python, for example, you can pass and return values of any type, the functions working with them don't care at compile type:
def SomePythonFunction (s, i): print i return i + 3
The result is less typing for data and less typing for fingers, but also more flexibility.
The disadvantage is that there is no guarantee that it is really an integer that is passed as i
, so the + 3
operation might fail at run-time. (This is less of a problem than most aficionados of static languages like C# assume, but the biggest disadvantage is this: static compilers can safely remove all sorts of run-time checks and gain more insights into the programmer's intention. A compiler for a statically typed language gives you faster code.)
The lack of static typing at compile time comes with an added benefit – so-called "duck typing". Let's say your python function prints the string in a Name
property:
def SomeDumperFunction (obj): print obj.Name
If you pass an integer as obj
, this function will obviously fail, because an integer does not have a Name
property (called "attribute" in python).
On the plus side: WHATEVER has a Name
property will work, no matter of what type obj
is. Only the name is significant here. You can pass Person
objects, File
objects, Pet
objects as obj
and SomeDumperFunction
will happily evaluate .Name
at run-time, not caring about obj
's type.
The Person
-, File
- and Pet
classes don't need to be related in any way, namely, they don't need to be derived from a common base class sporting that Name
property.
In other words: whatever works out syntactically at run-time (based solely on identifiers) is good to go for evaluation.
This phenomenon resembles the old American proverb "Whatever looks like a duck, walks like a duck, quacks like a duck and swims like a duck is as cute as a duck", therefore: "duck typing".
Note that duck-typing makes interfaces obsolete, because no extra insulation or casts between "unrelated" types are required.
Duck-typing in re-motion mixins
Conversely, static languages provide some of duck-typing's flexibility with interfaces. Interfaces provide type-safety at compile-time and flexibility at run-time via casts to particular interfaces. This flexibility requires that the programmer signals to the compiler that a certain class implements one ore more specific interfaces, as in
class Foo : IBar, IBaz
What's more, languages like C# and Java provide powerful reflection – unlike the older, less abstract C and C++. Just like python (or ruby, or perl), C# can access functions, types and properties at run-time by name, although much more work is required to do so.
Reflection and code generation are used by re-mix. A byproduct of its design and implementation is form of duck-typing: you can tuck an interface to an implementation without accessing the implementation's source code. A target class like Parrot
does not need to know that somewhere in your source code there is an IParrot
interface that happens to include members that share the names and signatures with some or all in the target class. This feature is closely related to the discussion presented in re-motion mixins basics -- the 'Extends' attribute#Sneak preview on re-motion mixins and duck typing.
This can be very important for frameworks and classes for which you don't have access to the source code.
For best effect, imagine that you don't have source code for the involved target classes.
Duck-typing for pets
What if a new class of speakers enters our parrot architecture? [ The Parrot
class was introduced in re-motion mixins basics -- what about sub-classes of the target class?. ] All of a sudden, we not only have Parrot
who can say something, but also Person
. Persons can talk, so
Person
has a Say
method, just like Parrot
. However, in our example, Person
and Parrot
have no common ancestor. Here is the Person
class:
public class Person { public string FirstName { get; set; } public string Surname { get; set; } public virtual void Say (string s) { // an artificially awkward implementation // to show that this 'Say' is TOTALLY unlike // 'Parrot's 'Say' foreach (var c in s) { Console.Write (c); } } }
If we want to give our Person
acting capabilities for performing a short dialog on the phone, we can extend it with the OnThePhoneMixin
.
To this end, we could use the IParrot
interface from the previous page. However, it is more appropriate to rename that interface to ISpeaker
:
public interface ISpeaker { void Say (string s); }
Again, it is NOT necessary to declare Person
or Parrot
to implement ISpeaker
for mixing. In this simple example, the mixin class does not override anything, so we can resort to the single-parameter Mixin<TThis>
:
[Extends (typeof (Parrot))] [Extends (typeof (Person))] public class OnThePhoneMixin : Mixin<ISpeaker>, IOnThePhoneMixin { public void PretendToTalkOnThePhone () { This.Say ("Ring ring"); This.Say ("Ring ring"); This.Say ("Halloho?"); Wait (1500); This.Say ("Oh! Hi!"); Wait (3000); This.Say ("I'm fine! And yourself?"); Wait (1500); This.Say ("Glad to hear that! I love you! Bye!"); } public void Wait (int milliSeconds) { Thread.Sleep (milliSeconds); } }
The declaration listed above extends both Parrot
and Person
with the OnThePhone
feature, although these classes have nothing in common and don't even implement the ISpeaker
interface. ISpeaker
, passed as TThis
parameter to Mixin<TThis>
provides only the information that the mixin class can expect the Say
method implementations SOMEWHERE in the target class. The eventual implementation, wiring up everything, in the mixed class is done at run-time. The mixer code will take care that OnThePhone
's Say
is delegated to the target class' Say
. Here is client code for convincing yourself that this actually works:
// you must force the .NET runtime to load a reference to the // Remotion.dll assembly. Otherwise the compiler will remove the // facilities for loading the Remotion.dll assembly and your application // will throw a type initializer exception, because it can't load it. // The easiest way to touch the assembly is to use the IMixinTarget // identifier: FrameworkVersion.RetrieveFromType (typeof (IMixinTarget)); // instantiate parrot thespian var myPetParrot = ObjectFactory.Create<Parrot> (ParamList.Empty); Console.WriteLine ("Parrot:"); ((IOnThePhoneMixin) myPetParrot).PretendToTalkOnThePhone (); Console.WriteLine (); // instantiate person thespian var myPetPerson = ObjectFactory.Create<Person> (ParamList.Empty); Console.WriteLine ("Person"); ((IOnThePhoneMixin) myPetPerson).PretendToTalkOnThePhone (); Console.ReadLine ();
Sample code
The sample code for this wiki-page is located in subversion.