Okay, so I'm having a carstarter installed in my car and obviously have nothing better to do than keep tinkering with this DSL stuff.
So I'm thinking... If we create a DSL for the person writing the code, then the debug experience should also be DSL-like. Now I'm not about to write my own debugger, but instead of showing a plain DateTime (what kind of datatype is that anyway?!?!?) we're talking animals, persons and real 'talk' date of birth stuff here.
So I create a special DateOfBirth class which overrides the ToString() method and this creates a 'pretty' experience in my debug window. Notice the values for DateOfBirth. This could ofcourse be extended to person, which is still displayed as the same old school type information: 'DSL.Person'.
The complete code comes at the end of this post, but lets have a look at te code complexity when going from
Person p = new Person();p.DateOfBirth = new DateTime(1972,9,25);
to our DSL
I guess the code is not too bad. Visual Studio still scores it green on the maintainability scale, but it is clear that quite a large amount of code is needed to create a DSL. And DSL's like this get more context sensitive. Imagine if I would also like to support birds with my DSL? Birds get hatched not born. I'd have to expand my extensions class, or maybe create a whole new set of classes and methods, having a class 'HatchedOn'.
Anyway, they just called, my car is done.
Here is the code for the final sample:
namespace DSL{ class Program { static void Main(string[] args) { Person mark = Person.Named("Mark").BornOn(Day.the25th).Of(Month.September).InTheYear(1972); Animal watson = Animal.Named("Watson").BornOn(Day.the2nd).Of(Month.January).InTheYear(2001).AndItIsA(Animal.KindOf.Dog); } }
public class DateOfBirth { public int Year; public Month Month; public Day Day;
public static implicit operator DateTime(DateOfBirth dob) { return new DateTime(dob.Year, (int)dob.Month, (int)dob.Year); }
public override string ToString() { return "Born on " + Day.ToString() + " of " + Month.ToString() + " in the year " + Year.ToString() + "."; } }
public enum Day : int { the1st = 1, the2nd = 2, the25th = 25 }
public enum Month : int { January = 1, February = 2, September = 9 }
public interface IDateOfBirth { DateOfBirth DateOfBirth { get; set; } }
public class Person : IDateOfBirth { public DateOfBirth DateOfBirth { get; set; } public string Name { get; set; }
public static Person Named(string name) { Person p = new Person(name); p.DateOfBirth = new DateOfBirth(); return p; }
protected Person(string name) { Name = name; } }
public class Animal : IDateOfBirth { public enum KindOf { Dog, Cat }
public DateOfBirth DateOfBirth { get; set; } public string Name { get; set; } public KindOf KindOfAnimal { get; set; }
public static Animal Named(string name) { Animal animal = new Animal(); animal.Name = name; animal.DateOfBirth = new DateOfBirth(); return animal; } public Animal AndItIsA(KindOf kind) { KindOfAnimal = kind; return this; } }
public static class DateOfBirthExtensions { public static T BornOn<T>(this T p, Day day) where T : IDateOfBirth { p.DateOfBirth.Day = day; return p; }
public static T Of<T>(this T p, Month month) where T : IDateOfBirth { p.DateOfBirth.Month = month; return p; }
public static T InTheYear<T>(this T p, int year) where T : IDateOfBirth { p.DateOfBirth.Year = year; return p; } }
}