For example, here's a definition of a super generic function that takes a functor of string and maps it to a functor of int.
public static FB ParseInts<Functor, FA, FB>(FA input)
where Functor : struct, Functor<FA, FB, string, int> =>
default(Functor).Map(input, Int32.Parse);
The secret to it working is the constraint that constrains Functor generic argument to be a struct and a Functor<FA, FB, string, int>. The struct bit means I can call default(Functor) and get a valid reference back (because structs can't be null).I can then call it with a List:
var list = List("100", "50", "25");
Lst<int> res1 = ParseInts<FLst<string, int>, Lst<string>, Lst<int>>(list);
Or an Option: Option<int> res2 = ParseInts<FOption<string, int>, Option<string>, Option<int>>(opt);
And it will happily map the bound values.FLst and FOption are essentially the 'class instances'. Functor is the 'type class', it's just a C# interface [2]:
public interface Functor<FA, FB, A, B>
{
FB Map(FA ma, Func<A, B> f);
}
The key thing is that 'this' won't be used, so the first argument is the value to be mapped.FLst looks like this [3]:
public struct FLst<A, B> : Functor<Lst<A>, Lst<B>, A, B>
{
public Lst<B> Map(Lst<A> ma, Func<A, B> f) =>
ma.Map(f);
}
And FOption like so [4]: public struct FOption<A, B> :
Functor<Option<A>, Option<B>, A, B>,
BiFunctor<Option<A>, Option<B>, Unit, A, B>
{
public Option<B> BiMap(Option<A> ma, Func<Unit, B> fa, Func<A, B> fb) =>
ma.IsNone
? fa == null
? Option<B>.None
: fa(unit)
: fb == null
? Option<B>.None
: fb(ma.Value);
public Option<B> Map(Option<A> ma, Func<A, B> f) =>
ma.IsSome && f != null
? Optional(f(ma.Value))
: None;
}
As you can probably tell, the amount of clutter from specifying generic type-parameters that the compiler could (relatively) easily work out on its own, is pretty annoying. Which limits it's usefulness somewhat. But if you want to write truly generic code, it's doable (with some caveats of course. you'll notice that the Functor type isn't quite as strict say the definition in Haskell).I'm currently updating my language-ext project to add type-classes [5] and class-instances [6], in the hope that the C# team will take pity on me and make this technique a language feature (it's been seriously turning my head inside out trying to make it work with higher-order types like monads).
This is already being investigated by some Roslyn team members [7]. I figured this feature was unlikely to move seriously in the near term without some indication of real world need.
[1] https://github.com/louthy/language-ext/blob/type-classes/Lan...
[2] https://github.com/louthy/language-ext/blob/type-classes/Lan...
[3] https://github.com/louthy/language-ext/blob/type-classes/Lan...
[4] https://github.com/louthy/language-ext/blob/type-classes/Lan...
[5] https://github.com/louthy/language-ext/tree/type-classes/Lan...
[6] https://github.com/louthy/language-ext/tree/type-classes/Lan...
[7] https://github.com/MattWindsor91/roslyn/blob/master/concepts...