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.