This is the second part of the Higher Kinded Types in C# series. For your convenience you can find other parts in the table of contents in Part 1 — Introduction
So now we can use invoice with items as Brand< Started, string>
. However, what we really want to use is Started< string>
. How can we do that?
We need to use some magic to seamlessly translate Started< string>
into Brand< Started, string>
. Notice that there are two different Started
types here: one is generic, one is not. And they are completely unrelated when it comes to class hierarchy.
Let’s start with this:
1 2 3 4 5 6 |
public class Brand<T, A> where T : BrandProvider, new() { public object Target { get; set; } public dynamic GetHKT() => new T().GetReal<A>(Target); public static Brand<T, A> FromHKT(object target) => new Brand<T, A> { Target = target }; } |
So we store the target of this object (which is our Started< string>
instance). We also have some helper methods to translate to and from higher kinded type.
We need this:
1 2 3 4 |
public interface BrandProvider { dynamic GetReal<A>(object target); } |
So we have some provider of real object casted to correct type. We go with dynamic
just to make it simpler when it comes to type casting since .NET doesn’t support overriding method with covariant return type so we’d need to cast it on the caller side (which we want to avoid).
Now, markers:
1 2 3 4 5 6 7 8 9 |
public class Started : BrandProvider { public dynamic GetReal<U>(object target) => (Started<U>)(target); } public class Finished : BrandProvider { public dynamic GetReal<U>(object target) => (Finished<U>)(target); } |
So we just cast the type correctly.
Now we process invoice in this way:
1 2 3 4 |
public static Invoice<Brand<U, string>> ProcessBrand<T, U>(Invoice<Brand<T, string>> invoice, Func<Brand<T, string>, Brand<U, string>> lambda) where T : BrandProvider, new() where U : BrandProvider, new() { return new Invoice<Brand<U, string>> { Items = invoice.Items.Select(i => lambda(i)).ToList() }; } |
To finish an item we do this:
1 2 3 4 |
public static Brand<Finished, T> FinishBrand<T>(Brand<Started, T> started) { return Brand<Finished, T>.FromHKT(Finish(started.GetHKT())); } |
So we reuse Finish
method which we used last time. That method worked on normal generic typ, now we reuse it to work on our flattened higher kinded type.
Let’s now create invoice and run the magic:
1 2 3 4 5 6 |
Invoice<Brand<Started, string>> startedItemsBrand = new Invoice<Brand<Started, string>>() { Items = new List<Brand<Started, string>>() { Brand<Started, string>.FromHKT(new Started<string> { Value = "Tadam!" }) } }; Invoice<Brand<Finished, string>> finishedItemsBrand = ProcessBrand<Started, Finished>(startedItemsBrand, FinishBrand); foreach (var item in finishedItemsBrand.Items) { Console.WriteLine(item.GetHKT().Value); } |
Crucial parts here are:
1 |
Brand<Started, string>.FromHKT(new Started<string> { Value = "Tadam!" }) |
and
1 |
item.GetHKT().Value |
This way we can transform items from one to the other.
This handles homogenous items. Next time we will see how to have items of different types.