Lens library for C# (demonstration)
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
/* | |
A basic lens library for the purpose of demonstration. | |
Implements a lens as the costate comonad coalgebra. | |
This library is not complete. | |
A more complete lens library would take from | |
Edward Kmett's to support polymorphic updates and traversal: | |
http://hackage.haskell.org/package/lens | |
*/ | |
namespace Lens { | |
public struct And<A, B> { | |
public readonly A a; | |
public readonly B b; | |
private And(A a, B b) { | |
this.a = a; | |
this.b = b; | |
} | |
public And<X, B> First<X>(Func<A, X> f) { | |
return And<X, B>.and(f(a), b); | |
} | |
public And<A, X> Second<X>(Func<B, X> f) { | |
return And<A, X>.and(a, f(b)); | |
} | |
public And<A, X> Select<X>(Func<B, X> f) { | |
return Second(f); | |
} | |
public And<X, Y> BinarySelect<X, Y>(Func<A, X> f, Func<B, Y> g) { | |
return And<X, Y>.and(f(a), g(b)); | |
} | |
public C Apply<C>(Func<A, B, C> f) { | |
return f(a, b); | |
} | |
public C ApplyFirst<C>(Func<A, C> f) { | |
return f(a); | |
} | |
public C ApplySecond<C>(Func<B, C> f) { | |
return f(b); | |
} | |
public X Fold<X>(Func<A, B, X> f) { | |
return f(a, b); | |
} | |
public Store<B, A> ConstStore { | |
get { | |
var t = this; | |
return Store<B, A>.store(_ => t.a, t.b); | |
} | |
} | |
public static And<A, B> and(A a, B b) { | |
return new And<A, B>(a, b); | |
} | |
} | |
public struct Or<A, B> { | |
private readonly bool l; | |
private readonly A a; | |
private readonly B b; | |
private Or(bool l, A a, B b) { | |
this.l = l; | |
this.a = a; | |
this.b = b; | |
} | |
public bool IsLeft { | |
get { | |
return l; | |
} | |
} | |
public bool IsRight { | |
get { | |
return !l; | |
} | |
} | |
public Or<B, A> Swap { | |
get { | |
return l ? Or<B, A>.Right(a) : Or<B, A>.Left(b); | |
} | |
} | |
public X Fold<X>(Func<A, X> left, Func<B, X> right) { | |
return l ? left(a) : right(b); | |
} | |
public Or<A, X> Select<X>(Func<B, X> f) { | |
return Fold(a => Or<A, X>.Left(a), b => Or<A, X>.Right(f(b))); | |
} | |
public Or<A, X> SelectMany<X>(Func<B, Or<A, X>> f) { | |
return Fold(a => Or<A, X>.Left(a), f); | |
} | |
public Or<A, Y> SelectMany<X, Y>(Func<B, Or<A, X>> f, Func<B, X, Y> g) { | |
return SelectMany<Y>(b => f(b).Select<Y>(x => g(b, x))); | |
} | |
public Or<X, Y> BinarySelect<X, Y>(Func<A, X> f, Func<B, Y> g) { | |
return Fold(a => Or<X, Y>.Left(f(a)), b => Or<X, Y>.Right(g(b))); | |
} | |
public B RightValue(Func<A, B> f) { | |
return Fold(f, b => b); | |
} | |
public A LeftValue(Func<B, A> f) { | |
return Fold(a => a, f); | |
} | |
public B RightOr(Func<B> d) { | |
return RightValue(_ => d()); | |
} | |
public A LeftOr(Func<A> d) { | |
return LeftValue(_ => d()); | |
} | |
public void ForEachRight(Action<B> q) { | |
if(IsLeft) | |
q(b); | |
} | |
public void ForEachLeft(Action<A> q) { | |
if(IsRight) | |
q(a); | |
} | |
public static Or<A, B> Left(A a) { | |
return new Or<A, B>(true, a, default(B)); | |
} | |
public static Or<A, B> Right(B b) { | |
return new Or<A, B>(false, default(A), b); | |
} | |
} | |
public class Store<A, B> { | |
public readonly Func<A, B> Put; | |
public readonly A Pos; | |
private Store(Func<A, B> put, A pos) { | |
this.Put = put; | |
this.Pos = pos; | |
} | |
public Store<A, C> Select<C>(Func<B, C> f) { | |
return Store<A, C>.store(a => f(Put(a)), Pos); | |
} | |
/* | |
Store is a comonad. | |
*/ | |
public Store<A, C> CoSelectMany<C>(Func<Store<A, B>, C> f) { | |
return Store<A, C>.store(a => f(Store<A, B>.store(Put, a)), Pos); | |
} | |
public B CoPoint { | |
get { | |
return Put(Pos); | |
} | |
} | |
public Store<A, Store<A, B>> Duplicate { | |
get { | |
return Store<A, Store<A, B>>.store(a => Store<A, B>.store(Put, a), Pos); | |
} | |
} | |
public Store<And<A, C>, And<B, D>> Product<C, D>(Store<C, D> s) { | |
return Store<And<A, C>, And<B, D>>.store( | |
x => And<B, D>.and(Put(x.a), s.Put(x.b)) | |
, And<A, C>.and(Pos, s.Pos) | |
); | |
} | |
public static Store<A, B> store(Func<A, B> put, A pos) { | |
return new Store<A, B>(put, pos); | |
} | |
} | |
public struct Lens<A, B> { | |
private readonly Func<A, Store<B, A>> q; | |
private Lens(Func<A, Store<B, A>> q) { | |
this.q = q; | |
} | |
public Store<B, A> Run(A a) { | |
return q(a); | |
} | |
public B Get(A a) { | |
return Run(a).Pos; | |
} | |
public A Set(A a, B b) { | |
return Run(a).Put(b); | |
} | |
public Func<A, A> Modify(Func<B, B> f) { | |
var t = this; | |
return a => { | |
var x = t.Run(a); | |
return x.Put(f(x.Pos)); | |
}; | |
} | |
/* | |
Lenses compose. | |
*/ | |
public Lens<A, C> Then<C>(Lens<B, C> w) { | |
var t = this; | |
return Lens<A, C>.lens(a => { | |
Store<B, A> y = t.Run(a); | |
Store<C, B> z = w.Run(y.Pos); | |
return Store<C, A>.store(c => y.Put(z.Put(c)), z.Pos); | |
}); | |
} | |
/* | |
Lenses split on choice. | |
*/ | |
public Lens<Or<A, X>, B> Sum<X>(Lens<X, B> l) { | |
var t = this; | |
return Lens<Or<A, X>, B>.lens(e => | |
e.Fold<Store<B, Or<A, X>>>(a => t.Run(a).Select(j => Or<A, X>.Left(j)), x => l.Run(x).Select(j => Or<A, X>.Right(j))) | |
); | |
} | |
/* | |
Lenses are a tensor product. | |
*/ | |
public Lens<And<A, C>, And<B, D>> Product<C, D>(Lens<C, D> l) { | |
var t = this; | |
return Lens<And<A, C>, And<B, D>>.lens(v => | |
t.Run(v.a).Product(l.Run(v.b))); | |
} | |
public static Lens<A, B> lens(Func<A, Store<B, A>> f) { | |
return new Lens<A, B>(f); | |
} | |
public static Lens<A, B> lens(Func<A, B, A> s, Func<A, B> g) { | |
return new Lens<A, B>(a => | |
Store<B, A>.store(b => s(a, b), g(a))); | |
} | |
/* | |
The lens identity for lens composition (Then method). | |
*/ | |
public static Lens<A, A> LensIdentity() { | |
return Lens<A, A>.lens(a => Store<A, A>.store(v => v, a)); | |
} | |
/* | |
A predicate is a lens. | |
*/ | |
public static Lens<Store<A, bool>, Or<A, A>> PredicateLens() { | |
return Lens<Store<A, bool>, Or<A, A>>.lens(s => { | |
var g = s.Pos; | |
return Store<Or<A, A>, Store<A, bool>>.store(o => | |
o.Fold<Store<A, bool>>(l => And<bool, A>.and(true, l).ConstStore, r => And<bool, A>.and(false, r).ConstStore) | |
, s.Put(g) ? Or<A, A>.Left(g) : Or<A, A>.Right(g)); | |
}); | |
} | |
/* | |
Lens unzips. | |
*/ | |
public static And<Lens<X, A>, Lens<X, B>> UnzipLens<X>(Lens<X, And<A, B>> l) { | |
return And<Lens<X, A>, Lens<X, B>>.and( | |
Lens<X, A>.lens(x => { | |
var c = l.Run(x); | |
var i = c.Pos; | |
return Store<A, X>.store(a => c.Put(And<A, B>.and(a, i.b)), i.a); | |
}) | |
, Lens<X, B>.lens(x => { | |
var c = l.Run(x); | |
var i = c.Pos; | |
return Store<B, X>.store(b => c.Put(And<A, B>.and(i.a, b)), i.b); | |
}) | |
); | |
} | |
/* | |
Lens factors. | |
*/ | |
public static Lens<Or<And<A, B>, And<A, C>>, And<A, Or<B, C>>> FactorLens<C>() { | |
return Lens<Or<And<A, B>, And<A, C>>, And<A, Or<B, C>>>.lens(e => | |
Store<And<A, Or<B, C>>, Or<And<A, B>, And<A, C>>>.store(y => | |
y.b.BinarySelect(b => And<A, B>.and(y.a, b), c => And<A, C>.and(y.a, c)) | |
, e.Fold<And<A, Or<B, C>>>( | |
b => And<A, Or<B, C>>.and(b.a, Or<B, C>.Left(b.b)) | |
, c => And<A, Or<B, C>>.and(c.a, Or<B, C>.Right(c.b))))); | |
} | |
/* | |
Lens distributes. | |
*/ | |
public static Lens<And<A, Or<B, C>>, Or<And<A, B>, And<A, C>>> DistributLens<C>() { | |
return Lens<And<A, Or<B, C>>, Or<And<A, B>, And<A, C>>>.lens(e => | |
Store<Or<And<A, B>, And<A, C>>, And<A, Or<B, C>>>.store(y => | |
y.Fold<And<A, Or<B, C>>>( | |
l => And<A, Or<B, C>>.and(l.a, Or<B, C>.Left(l.b)) | |
, r => And<A, Or<B, C>>.and(r.a, Or<B, C>.Right(r.b))) | |
, e.b.BinarySelect(b => And<A, B>.and(e.a, b), c => And<A, C>.and(e.a, c)) | |
)); | |
} | |
/* | |
The lens for the first element of a pair. | |
*/ | |
public static Lens<And<A, B>, A> FirstLens() { | |
return Lens<And<A, B>, A>.lens(v => | |
Store<A, And<A, B>>.store(a => And<A, B>.and(a, v.b), v.a)); | |
} | |
/* | |
The lens for the second element of a pair. | |
*/ | |
public static Lens<And<A, B>, B> SecondLens() { | |
return Lens<And<A, B>, B>.lens(v => | |
Store<B, And<A, B>>.store(b => And<A, B>.and(v.a, b), v.b)); | |
} | |
/* | |
The three lens laws; identity, retention and double-set. | |
All lenses must satisfy these laws. | |
i.e. It must not be possible to have these laws return false. | |
The two coalgebra laws given below follow from these three laws. | |
*/ | |
public bool IdentityLaw(A a) { | |
var c = Run(a); | |
return c.Put(c.Pos).Equals(a); | |
} | |
public bool RetentionLaw(A a, B b) { | |
return Run(Run(a).Put(b)).Pos.Equals(b); | |
} | |
public bool DoubleSetLaw(A a, B b1, B b2) { | |
var c = Run(a); | |
return Run(c.Put(b1)).Put(b2).Equals(c.Put(b2)); | |
} | |
/* | |
The coalgebra laws. | |
All lenses must satisfy these laws. | |
i.e. It must not be possible to have these laws return false. | |
The three lens laws above follow from these two laws. | |
*/ | |
public bool CoalgebraLaw1(A a) { | |
return Run(a).CoPoint.Equals(a); | |
} | |
public bool CoalgebraLaw2(A a) { | |
var t = this; | |
return Run(a).Select(r => t.Run(r)).Equals(t.Run(a).Duplicate); | |
} | |
} | |
} |
This comment has been minimized.
This comment has been minimized.
@tonymorris, |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
Any samples?