A recent C# feature is static
interface members – this opens up some new possibilities for bending C# to make trait like functionality work. You may have already seen the technique:
public interface Addable<SELF> where SELF : Addable<SELF>
{
public static abstract SELF Add(SELF x, SELF y);
}
Note, how the Add member is static abstract
and that the interface has a constraint that SELF
is forced to inherit Addable<SELF>
.
We can then create two distinct types that inherit the Addable trait:
public record MyList<A>(A[] values) : Addable<MyList<A>>
{
public static MyList<A> Add(MyList<A> x, MyList<A> y) =>
new (x.values.Append(y.values).ToArray());
}
public record MyString(string value) : Addable<MyString>
{
public static MyString Add(MyString x, MyString y) =>
new (x.value + y.value);
}
Language-Ext takes this idea and uses it to implement 'higher-kinded traits' (with the K<F, A>
type being the anchor for
them all).
To continue reading about how this works, check out Paul Louth's Higher-Kinds series.
Sub modules
interface K <in F, A> Source #
Arrow kind: * -> *
used to represent higher-kinded types.
K<F, A>
should be thought of as F<A>
(where both F
an A
are parametric). It currently
can't be represented in C#, so this allows us to define higher-kinded types and pass them
around. We can then build traits that expected a K
where the trait is tied to the F
.
For example:
K<F, A> where F : Functor<F>
K<M, A> where M : Monad<M>
That means we can write generic functions that work with monads, functors, etc.
type | F | Trait type |
type | A | Bound value type |
interface K <in F, P, A> Source #
Arrow kind: * -> * -> *
used to represent higher-kinded types.
type | F | Trait type |
type | P | Alternative value type |
type | A | Bound value type |
class KExtensions Source #
method K<F, A> Kind <F, A> (this K<F, A> fa) Source #
Get the base kind type. Avoids casts mid-expression
method K<M, K<N, A>> KindT <M, N, NA, A> (this K<M, NA> mna) Source #
KindT
converts a nested Kind type (inherits K<M, A>
), where the inner
type is a concrete type and not K<N, A>
to the more general version - which
allows the other T
variant methods to work seamlessly.
The casting of nested types is especially problematic for C#'s type-system,
so even though this isn't ideal, it does allow for a truly generic system
of working with any nested types as long as there's a Functor
implementation
for the outer type.
type | M | Outer functor trait (i.e. |
type | N | Inner trait (i.e. |
type | NA | Concrete nested type (i.e. |
type | A | Concrete bound value type (i.e. |
param | mna | Nested functor value |
returns | More general version of the type that can be used with other |
var mx = Seq<Option<int>>(Some(1), Some(2), Some(3));
var ma = mx.KindT<Seq, Option, Option<int>, int>()
.BindT(a => Some(a + 1))
.MapT(a => a + 1);
.AsT<Seq, Option, Option<int>, int>();
method K<M, NA> AsT <M, N, NA, A> (this K<M, K<N, A>> mna) Source #
AsT
converts a nested Kind type (inherits K<M, A>
), where the inner type
is a general type (K<N, A>
) to its downcast concrete version.
The casting of nested types is especially problematic for C#'s type-system,
so even though this isn't ideal, it does allow for a truly generic system
of working with any nested types as long as there's a Functor
implementation
for the outer type.
type | M | Outer functor trait (i.e. |
type | N | Inner trait (i.e. |
type | NA | Concrete nested type (i.e. |
type | A | Concrete nested-monad bound value type (i.e. |
param | mna | Nested functor value |
returns | Concrete version of the general type. |
var mx = Seq<Option<int>>(Some(1), Some(2), Some(3));
var ma = mx.KindT<Seq, Option, Option<int>, int>()
.BindT(a => Some(a + 1))
.MapT(a => a + 1);
.AsT<Seq, Option, Option<int>, int>();
class TraitAttribute Source #
field string NameFormat Source #
constructor TraitAttribute (string nameFormat) Source #