Skip to content

Instantly share code, notes, and snippets.

@micmarsh
Last active September 26, 2025 15:34
Show Gist options
  • Select an option

  • Save micmarsh/812184999bcbbc10bac989504c1d30c5 to your computer and use it in GitHub Desktop.

Select an option

Save micmarsh/812184999bcbbc10bac989504c1d30c5 to your computer and use it in GitHub Desktop.
// Requires LanguageExt V5
using LanguageExt;
using LanguageExt.Traits;
namespace Kleisli;
public readonly record struct Kleisli<M, A, B>(Func<A, K<M, B>> Invoke) : K<Kleisli<M, A>, B>, K<CoKleisli<M, B>, A>
where M : Monad<M>
{
public Kleisli<M, A, C> ComposeK<C>(Kleisli<M, B, C> next)
{
var self = this;
return new (a => self.Invoke(a).Bind(next.Invoke));
}
public Kleisli<M, A, C> ComposeK<C>(Func<B, K<M, C>> next)
{
var self = this;
return new (a => self.Invoke(a).Bind(next));
}
public static Kleisli<M, A, B> operator >> (Kleisli<M, A, B> lhs, Kleisli<M, B, B> rhs) =>
lhs.ComposeK(rhs);
public static Kleisli<M, A, B> operator >> (Kleisli<M, A, B> lhs, Func<B, K<M, B>> rhs) =>
lhs.ComposeK(rhs);
public Kleisli<M, A, C> Map<C>(Func<B, C> f)
{
var ma = this;
return new Kleisli<M, A, C>(a => ma.Invoke(a).Map(f));
}
public static Kleisli<M, A, B> Pure(B value) => new Kleisli<M, A, B>(_ => M.Pure(value));
public Kleisli<M, A, C> Apply<C>(Kleisli<M, A, Func<B, C>> mf)
{
var ma = this;
return new Kleisli<M, A, C>(a => mf.Invoke(a).Apply(ma.Invoke(a)));
}
public Kleisli<M, A, C> Bind<C>(Func<B, Kleisli<M, A, C>> f)
{
var ma = this;
return new Kleisli<M, A, C>(a => ma.Invoke(a).Bind(a1 => f(a1).Invoke(a)));
}
public Kleisli<M, C, B> Comap<C>(Func<C, A> f)
{
var ma = this;
return new Kleisli<M, C, B>(c => ma.Invoke(f(c)));
}
}
public static class KleisliExt
{
public static Kleisli<M, A, B> As<M, A, B>(this K<Kleisli<M, A>, B> ma)
where M : Monad<M>
=> (Kleisli<M, A, B>)ma;
public static Kleisli<M, A, B> As<M, A, B>(this K<CoKleisli<M, B>, A> ma)
where M : Monad<M>
=> (Kleisli<M, A, B>)ma;
public static K<Kleisli<M, A>, B> Kind<M, A, B>(this Kleisli<M, A, B> ma) where M : Monad<M> => ma;
public static K<CoKleisli<M, B>, A> Co<M, A, B>(this Kleisli<M, A, B> ma) where M : Monad<M> => ma;
public static K<CoKleisli<M, B>, A> Co<M, A, B>(this K<Kleisli<M, A>, B> ma) where M : Monad<M> => ma.As();
public static Kleisli<M, A, C> ComposeK<M, A, B, C>(this Func<A, K<M, B>> fb, Func<B, K<M, C>> fc)
where M : Monad<M> =>
new Kleisli<M, A, B>(fb).ComposeK(fc);
public static Kleisli<M, A, C> ComposeK<M, A, B, C>(this Func<A, K<M, B>> fb, Kleisli<M, B, C> fc)
where M : Monad<M> =>
new Kleisli<M, A, B>(fb).ComposeK(fc);
}
public class Kleisli<M, A> : Monad<Kleisli<M, A>>
where M : Monad<M>
{
public static K<Kleisli<M, A>, C> Map<B, C>(Func<B, C> f, K<Kleisli<M, A>, B> ma) => ma.As().Map(f);
public static K<Kleisli<M, A>, B> Pure<B>(B value) => Kleisli<M, A, B>.Pure(value);
public static K<Kleisli<M, A>, C> Apply<B, C>(K<Kleisli<M, A>, Func<B, C>> mf, K<Kleisli<M, A>, B> ma) =>
ma.As().Apply(mf.As());
public static K<Kleisli<M, A>, C> Bind<B, C>(K<Kleisli<M, A>, B> ma, Func<B, K<Kleisli<M, A>, C>> f) =>
ma.As().Bind(a => f(a).As());
}
public class CoKleisli<M, B> : Cofunctor<CoKleisli<M, B>>
where M : Monad<M>
{
public static K<CoKleisli<M, B>, A> Comap<A, C>(Func<A, C> f, K<CoKleisli<M, B>, C> fb) => fb.As().Comap(f);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment