In the second part of metacall series, I shall try and contribute with my own C# implementation of the metacall
.
First thing first. I do realize that F# is much more natural choice for the kind-of-a programming metacall is all about : functional and all that. But Microsft made C# almost unavoidable. Betrand Le Roy (BLR) has contributed first and actually produced a critical C# idiom that allowed me to work furher on the C# binding of the metacall.
1 2 3 4 |
// (c) 2010 by Bertrand Le Roy public delegate metacall metacall (Action<object> action, object options = null); |
As BLR says : “..Tricky, but that actually works, a delegate can return its own type…” . I doubt that 99.9% of C# population knew this is possible. Me including.
I have to admit that last two years, I spent in (very) intensive JavaScript-ing. Inevitably my C# become a bit rusty, after I left it “temporarily” sometimes way back in 2005. But now I want to provide some input to metacall C# implementations that (it seems) will inevitably come in larger numbers.
Since release of C#3.0 , it is possible to do a lot of functional programming in C#, and that is a good thing. But, in C#, there is an sizeable “dark matter” accumulation. Some say, C# is by now, far from simple and elegant programming language. Also, dynamic and functional .NET languages like Iron Python and F# have started to appear. And F# now has a prefix Visual, and we all know what that means ;)
The point I am making is that C# is not the best programming language in the world ever, but it is an official .NET language, and in widespread use. And metacall concept can and should be quite nicely implemented in C#. As BLR has already shown, and I will try and formalize in this post.
Implementation
My current official shape of metacall is presented in the form of JavaScript source.
I have decided to try and present C# form of CallStream
and CallStreamBridge
. But what about the CallStreamConstructor
? I think that the JavaScript form of metacall, reveals completely the spirit of that language.
As a such it is not trivial to implement in C#, which is inherently NOT an functional language, and certainly not an prototypical language. Still there are at least several ways to do this, more or less successfully. I think it is best NOT to dictate a C# binding of that part of metacall. There is no generic “best” way to do this. Different kinds of C# components and programs will require different C# specific implementations of initialization (aka contruction) of the metacall encapsulation. So, I decided it is best to declare CallStreamBridge
, C# binding : ” an excersize for the reader” ;)
Now, back to the core metacall parts. Obviously BLR has already done a key contribution and I will use it. But with a twist: I will make it a bit more generic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/// <summary> /// Encapsulates an method that takes any number of parameters /// of any type /// and returns void /// </summary>public delegate void TheFunction(params object[] paramz); /// <summary> /// A signature of the method that is a bridge between metacall /// and some state implementation that metacall is /// "hiding" from the callers /// This method is used for each call in the metacall. /// </summary> /// <param name="function">the user provided function in the single call</param> /// <param name="options">optional arguments for the function provided </param> public delegate void metacallBridge<T> (T function, params object[] options); /// <summary> /// Encapsulate a metcall as something that takes an /// Action of type T and an optional params object array /// And that returns a delegate of its own type. /// Initial C# idea by: Bertrand Le Roy (MSFT) /// </summary> /// <param name="action">functor</param> /// <param name="options">optional parameters</param> /// <returns>Itself</returns> public delegate metacall<T> metacall<T>(T action, params object[] options); |
I think I can say that the whole implemention of the C# metacall binding , is in the three delegates above.
Now, let me show you the usage of the above, applied to two different kinds of metcalls : a functional kind and keyword driven streaming kind. Code bellow explains the best. Here is the trivial test unit which implements a metacall for streaming calls to functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
[TestClass] public sealed class function_calls_streaming{ /// <summary> /// this metacall bridge is just an /// simple function invoker (aka applicator) for the user provided function /// and 0 or more optional arguments /// </summary> void function_applicator(TheFunction function, object[] paramz) { function.Invoke(paramz); } /// <summary> /// An implementation of the metacall delegate /// where each call must begin with a function of type &quot;TheFunction&quot; /// All of the parameters are simply passed to the bridge method. /// </summary> /// <returns>itself</returns> metacall<TheFunction> function_streamer(TheFunction function, params object[] options) { function_applicator(function, options); return function_streamer; } [TestMethod] public void function_streamer_test() { function_streamer (p => Console.WriteLine(p), "Lambda Dumper Argument") (Behind.Dump, DateTime.Now.ToLocalTime().ToString()) (Behind.DumpFooAndBar, false, 2) (p => Behind.Dump(System.Collections.ArrayList.Repeat(p, 3).ToArray()), "Done."); Assert.AreEqual(1, 1); } } |
I could do it (and I did it) with nested type arguments to generic delegates , and other snazzy C# constructs, but at the end I decided to keep it simple.
Please note that “function_streamer” can be replaced with an different metacall_bridge implementation, and thus allow us to create a whole familly of “functional” metacall’s . Perhaps that could be also second type argument to generic metacall. Perhaps. But I decided it will be an overkill.
And yes, the code in this post does not require VisualStudio 2010 or C#4.0. Also an deliberate decision of mine.
Here is the next implementation of a metacall, which is for situations where one needs or wants to communicate with her implementation that is hidden behind, through some kind of micro langauge (maybe some simple query language) , using the keywords+parameters paradigm.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
[TestClass] public sealed class keywords_and_parameters_call_stream { /// <summary> /// This metacall bridge is keyword handler /// It deals also with 0 or more optional arguments for each keyword /// </summary> void keyword_applicator(string keyword, params object[] paramz) { Behind.Dump("KEYWORD: " + keyword); if (keyword.Equals("dump", StringComparison.CurrentCultureIgnoreCase)){Behind.Dump(paramz);} elseif (keyword.Equals("dumpfooandbar", StringComparison.CurrentCultureIgnoreCase)) { Behind.DumpFooAndBar(paramz); } else{ Behind.Dump("Unknown keword!"); } } /// <summary> /// An implementation of the metacall delegate /// where each call must begin with a keyword of type string /// followed with 0 or more optional parameters, of any type /// All of the parameters are passed to the bridge method. /// </summary> /// <returns>itself</returns> metacall<string> keyword_streamer(string keyword, params object[] options) { keyword_applicator(keyword, options); return keyword_streamer; } [TestMethod] public void keyword_streamer_test() { keyword_streamer ("Dump", DateTime.Now.ToLocalTime().ToString()) ("DumpFooAndBar", false, 2) ("Fantasy") ; Assert.AreEqual(1, 1); } } |
Please note how we can pass illegal keywords. They will be ignored, until we decide to implement an response to them. Inside a keyword handler we are using. And of course we can easily switch different keyword handlers, perhaps for different languages, etc.
Conclusion
I would not dare to call this trivial, but I think it is rather simple to implement different kinds of metacall and metacallBridge for your different and specific C# projects.
–DBJ
PS: here is the “Behind” class used above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
/// <summary> /// The "state" of the application is "hidden behind" /// Here are the functions that are written in such a way /// that they can be used by "functional_streamer" /// because their declarations conform to the "TheFunction" delegate /// which is used to compose that metacall implementation /// NOTE: this makes them some kind of monads, probably /// </summary> internal sealed class Behind { public static void Dump(params object[] options) { foreach (var j in options){Console.WriteLine(j);} } public static void DumpFooAndBar(params object[] options) { Console.WriteLine("Foo is: {0}, and bar is: {1}.", options.Length > 0 ? options[0] : "NULL", options.Length > 1 ? options[1] : "NULL" ); } } |