镜头的特点是has-a关系;棱镜的特点是is-a关系。
A Lens s a
says "s
has an a
"; 它有方法可以精确地得到一个a
从一个s
并恰好覆盖一个a
in an s
. A Prism s a
says "a
is an s
"; 它有方法向上转型a
to an s
并(试图)贬低s
to an a
.
将这种直觉放入代码中,可以为您提供熟悉的“get-set”(或“costate comonad coalgebra”)镜头公式,
data Lens s a = Lens {
get :: s -> a,
set :: a -> s -> s
}
以及棱镜的“向上-向下”表示,
data Prism s a = Prism {
up :: a -> s,
down :: s -> Maybe a
}
up
注入一个a
into s
(不添加任何信息),以及down
测试是否s
is an a
.
In lens
, up
拼写为review https://hackage.haskell.org/package/lens-4.16.1/docs/Control-Lens-Review.html#v:review and down
is preview https://hackage.haskell.org/package/lens-4.16.1/docs/Control-Lens-Fold.html#v:preview。没有Prism
构造函数;你用the prism'智能构造函数 https://hackage.haskell.org/package/lens-4.16.1/docs/Control-Lens-Prism.html#v:prism-39-.
你能用a做什么Prism
?注入和项目总和类型!
_Left :: Prism (Either a b) a
_Left = Prism {
up = Left,
down = either Just (const Nothing)
}
_Right :: Prism (Either a b) b
_Right = Prism {
up = Right,
down = either (const Nothing) Just
}
镜头不支持这一点 - 你不能写Lens (Either a b) a
因为你无法实施get :: Either a b -> a
。作为一个实际问题,你can写一个Traversal (Either a b) a
,但这不允许您创建Either a b
从一个a
- 它只会让你覆盖a
它已经在那里了。
Aside:我认为这个微妙的点是关于Traversal
s 是您对部分记录字段感到困惑的根源。
^?
使用平光镜片可以得到Nothing
如果相关字段不属于实体代表的分支
Using ^?
与真实的Lens
永远不会回来Nothing
,因为一个Lens s a
准确识别一个a
里面一个s
。
当面对部分记录字段时,
data Wibble = Wobble { _wobble :: Int } | Wubble { _wubble :: Bool }
makeLenses
将生成一个Traversal
, not a Lens
.
wobble :: Traversal' Wibble Int
wubble :: Traversal' Wibble Bool
举个例子来说明如何Prism
可以应用到实践中,看看Control.Exception.Lens http://hackage.haskell.org/package/lens-4.16.1/docs/Control-Exception-Lens.html,它提供了一个集合Prism
s 进入 Haskell 的可扩展Exception
等级制度。这使您可以执行运行时类型测试SomeException
s 并将特定异常注入SomeException
.
_ArithException :: Prism' SomeException ArithException
_AsyncException :: Prism' SomeException AsyncException
-- etc.
(这些是实际类型的稍微简化的版本。实际上,这些棱镜是重载的类方法。)
从更高的层次思考,某些整个程序可以被认为是“基本上是一个Prism
”。编码和解码数据就是一个例子:您始终可以将结构化数据转换为String
,但不是每个String
可以解析回来:
showRead :: (Show a, Read a) => Prism String a
showRead = Prism {
up = show,
down = listToMaybe . fmap fst . reads
}
总而言之,Lens
es and Prism
它们一起编码了面向对象编程的两个核心设计工具:组合和子类型。Lens
es 是 Java 的一流版本.
and =
运营商,以及Prism
s 是 Java 的一流版本instanceof
和隐式向上转换。
一种富有成效的思考方式Lens
es 是它们为您提供了一种拆分复合材料的方法s
成为一个聚焦的价值a
和一些背景c
。伪代码:
type Lens s a = exists c. s <-> (a, c)
在这个框架中,一个Prism
为您提供一种查看s
作为一个a
或一些上下文c
.
type Prism s a = exists c. s <-> Either a c
(我将让你自己相信这些与我上面演示的简单表示是同构的。尝试实现get
/set
/up
/down
对于这些类型!)
从这个意义上说Prism
is a co-Lens
. Either
是以下的绝对对偶(,)
; Prism
是以下的绝对对偶Lens
.
您还可以在以下内容中观察到这种二元性:“泛函光学” http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/poptics.pdf公式 -Strong http://hackage.haskell.org/package/profunctors-5.2.2/docs/Data-Profunctor.html#t:Strong and Choice http://hackage.haskell.org/package/profunctors-5.2.2/docs/Data-Profunctor.html#t:Choice是双重的。
type Lens s t a b = forall p. Strong p => p a b -> p s t
type Prism s t a b = forall p. Choice p => p a b -> p s t
This is 或多或少的表示lens
使用,因为这些Lens
es and Prism
s 非常可组合。你可以作曲Prism
变得更大Prism
s ("a
is an s
, which is a p
“) 使用(.)
;组成一个Prism
with a Lens
给你一个Traversal
.