Page 39 - MSDN Magazine, November 2018
P. 39
Figure 2 Dynamically Chaining Delegates
object of loosely coupled functions. Thus the code is declaring a chain of functions that transforms an object sequentially, accord- ing to the symbols I choose to put into my code. For the record, you shouldn’t normally inherit from List, but to keep the example short, I decided to do it to illustrate the main idea.
Expanding upon this idea is simple. The exercise is to find the smallest common denominator for a generic delegate that can describe any programming construct you know, which just so happens to be the following delegate:
delegate object Function(List<object> arguments);
This delegate can represent nearly every programming structure ever invented. Everything in computing can take a list of input arguments and return something back to the caller. This delegate is the very definition of input/output fundamental to all comput- ing ideas, and becomes an atomic programming structure that you can use to solve all your computing problems.
Meet Lizzie
As I wrote this article, I created a programming language embody- ing the preceding idea. I wrote the entire language—which I call Lizzie—after my girlfriend, Lisbeth—in a couple of all out, full-moon weekends. The language is entirely contained in a single assembly, roughly 2,000 lines of code. When compiled, it’s only 45KB on my disc, and its “compiler” is only 300 lines of C# code. Lizzie is also easily extendable and lets anyone add their own “keywords” to it, allowing you to easily create your own domain-specific language (DSL). One use case for such a language is a rule-based engine, where you need to tie together code more dynamically than C# allows. With Lizzie you can add dynamic script code to your stat- ically compiled C# application that’s Turing-complete snippets of functionality. Lizzie is to C# what spice is to your dinner. You don’t want to eat only spice, but if you add some spice to your steak, your experience obviously becomes more pleasant. To try Lizzie out, create an empty console application in C#, add Lizzie as a NuGet package, and use the code in Figure 3.
In just 22 lines of code I’ve arguably created my own DSL and added my own domain-specific keyword to the language.
The main feature of Lizzie is that you can bind your Lizzie code to a context type. The LambdaCompiler.Compile method in Figure 3 is, in fact, a generic method, but its type argument is automatically inferred by its first argument. Internally, Lizzie will create a binder that it binds to your type, making all methods with the Bind attri- bute available to you from your Lizzie code. When your Lizzie code isevaluated,ithasanextrakeywordcalled“write.”Youcanbindany method to your Lizzie code as long as it has the correct signature. And you can bind your Lizzie code to any type.
Lizzie has several default keywords, which it makes available to you for your own code, but you don’t have to use these if you don’t want to. Figure 4 shows a more complete example that uses some of these keywords.
The Lizzie code in Figure 4 first creates a function called “my-function,”thenitinvokesthatfunctionwithtwointegerargu- ments. Finally, it writes out the result of the function invocation to the console. With 21 lines of C# code and eight lines of Lizzie code, I’ve evaluated a piece of dynamic code that creates a function in a
using System;
using System.Linq;
using System.Collections.Generic;
public class Chain<T> : List<Func<T, T>> {
public T Evaluate(T input) {
foreach (var ix in this) {
input = ix(input); }
return input; }
}
class MainClass {
public static void Main(string[] args) {
var keywords = new Dictionary<string, Func<string, string>> {
["capitalize"] = (input) => {
return input.Replace("e", "EE"); },
["replace"] = (input) => {
return input.Replace("o", "0"); }
};
string code = "capitalize replace";
var tokens = code.Split(' ');
var chain = new Chain<string>();
chain.AddRange(tokens.Select(ix => keywords[ix]));
var result = chain.Evaluate("join the revolution, capitalize and replace"); Console.WriteLine(result);
} }
in Figure 1, therefore, becomes a “programming language.” In effect, it’s a symbolic delegate.
This example is obviously not that interesting and is only meant to illustrate the core idea. I’m going to create something slightly more intriguing by chasing these ideas a little bit further down the rabbit hole, as shown in Figure 2.
Now I have something that looks almost useful—the ability to dynamically chain together delegates, which results in a lambda
Figure 3 Creating a Domain-Specific Language
using System; using lizzie;
class Demo1 {
[Bind(Name = "write")]
object write(Binder<Demo1> binder, Arguments arguments) {
Console.WriteLine(arguments.Get(0));
return null; }
}
class MainClass {
public static void Main(string[] args) {
var code = "write('Hello World')";
var lambda = LambdaCompiler.Compile(new Demo1(), code); lambda();
} }
msdnmagazine.com
November 2018 33

