This is the third 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
This time we start with this invoice:
1 2 3 4 5 |
public class HeterogenousInvoice<T> where T : BrandProvider, new() { public Brand<T, string> Item1 { get; set; } public Brand<T, int> Item2 { get; set; } } |
Obviously, this breaks things now. We need to add these two things to Brand
:
1 2 |
public Brand<T, object> ToObject() => new Brand<T, object> { Target = this.Target }; public Brand<T, V> FromObject<V>() => new Brand<T, V> { Target = this.Target }; |
Since we cannot use covariance and contravariance easily, we need to do upcasting and downcasting in generics “the hard way”.
We create invoice this way:
1 2 3 4 5 |
HeterogenousInvoice<Started> startedHeterogenous = new HeterogenousInvoice<Started>() { Item1 = Brand<Started, string>.FromHKT(new Started<string> { Value = "String" }), Item2 = Brand<Started, int>.FromHKT(new Started<int> { Value = 123 }) }; |
Okay, now how to process it? We’d like to have one function translating Started< T>
into Finished< T>
and we already have it, it’s called Finish
. How to reuse it?
We cannot use it directly as it works on Started< T>
type. But we cannot use FinishBrand
method either because then compiler will go crazy as it cannot handle one lambda for Started< string>
and Started< int>
. What can we do? Upcasting and downcasting:
1 2 3 4 |
public static Func<Brand<T, V>, Brand<U, V>> Rebind<T, U, V>(Func<Brand<T, object>, Brand<U, object>> lambda) where T : BrandProvider, new() where U : BrandProvider, new() { return (Brand<T, V> v) => (lambda(v.ToObject())).FromObject<V>(); } |
So we accept lambda working on the base type object
. The trick is, we can transform original lambda in this way:
1 2 3 4 5 6 7 8 |
public static HeterogenousInvoice<U> ProcessHeterogenous<T, U>(HeterogenousInvoice<T> invoice, Func<Brand<T, object>, Brand<U, object>> lambda) where T : BrandProvider, new() where U : BrandProvider, new() { return new HeterogenousInvoice<U> { Item1 = Rebind<T, U, string>(lambda)(invoice.Item1), Item2 = Rebind<T, U, int>(lambda)(invoice.Item2) }; } |
So we just magically transform lambdas using upcasting and downcasting.
Let’s see this in action:
1 2 3 4 5 6 7 8 |
HeterogenousInvoice<Started> startedHeterogenous = new HeterogenousInvoice<Started>() { Item1 = Brand<Started, string>.FromHKT(new Started<string> { Value = "String" }), Item2 = Brand<Started, int>.FromHKT(new Started<int> { Value = 123 }) }; HeterogenousInvoice<Finished> finishedHeterogenous = ProcessHeterogenous(startedHeterogenous, FinishBrand); Console.WriteLine(finishedHeterogenous.Item1.GetHKT().Value); Console.WriteLine(finishedHeterogenous.Item2.GetHKT().Value); |