Lens in Scala
函数式访问器在haskell里被叫做Lens。在面向对象语言里这个没有什么必要,不过作为练习,我们看如何在scala表示van Laarhoven lens.
先给出haskell里的lens类型:
type Lens s a = forall f. Functor f => (a -> f a) -> (s -> f s)
这里有一个全局量词forall f,所以等价的scala lens类型我们得用一个generic函数来表示:
trait Lens[S, A] { def apply[F[_] : Functor](l: A => F[A])(s: S) : F[S] }
上面在语义上,apply函数必须对所有的functor都满足,所以等价于forall f。
有了类型,我们开始加构造器。
haskell的lens构造器:
lens :: (s -> a) -> (s -> a -> s) -> Lens s a
lens getter setter l b = setter b <$> l (getter b)
其实可以证明任何一个lens构造器其实和这个都是等价的,因为((s->a), (s->a->s))和forall f. Functor f => (a -> f a) -> (s -> f s)是等价的。
对应的scala lens构造器:
object Lens { def apply[S, A](getter: S => A, setter: S => A => S) = new Lens[S, A] { def apply[F[_] : Functor](l: A => F[A])(s: S) : F[S] = { val m = implicitly[Functor[F]] m.fmap[A, S](l(getter(s)))(setter(s)) } } }
有了lens,我们现在要加get和set函数了。
在加这两个函数前,我们需要两个functor: Const和Identity。
这里只给出scala版本的:(haskell版本的可以看这里)
trait Functor[F[_]] { def fmap[A, B](a: F[A])(f: A => B) : F[B] } case class Const[A, B](a: A, b: B) object ConstFunctor { implicit def constFunctor[C] = new Functor[({type c[a] = Const[C, a]})#c] { def fmap[A, B](a: Const[C, A])(f: A => B) = new Const(a.a, f(a.b)) } } case class Identity[A](a: A) object IdentityFunctor { implicit def identityFunctor = new Functor[Identity] { def fmap[A, B](a: Identity[A])(f: A => B) = new Identity(f(a.a)) } }
使用Const和Identity,我们可以得到get和set,这里也只给出scala版本的:
trait Lens[S, A] { import ConstFunctor._ import IdentityFunctor._ def apply[F[_] : Functor](l: A => F[A])(s: S) : F[S] def get(s: S) : A = apply[({type C[a] = Const[A, a]})#C]((a: A) => new Const(a, a))(s).a def set(s: S, a: A) = apply[Identity]((_: A) => new Identity(a))(s).a }
好了,最后给个用例:
case class A(a: Int, b: Int) object Main { def main(args: Array[String]) { val l = Lens1((a: A) => a.a, (a: A) => {(b: Int) => new A(b, a.b)}) println(l.get(l.set(A(2, 6), 3))) } }