我试图更深入地了解lens
库,因此我会使用它提供的类型。我已经有了一些镜片经验,并且知道它们有多么强大和方便。所以我转向棱镜,我有点迷失。似乎棱镜允许两件事:
第一点似乎很有用,但通常一个人不需要来自实体的所有数据,^?
使用普通镜头可以获得Nothing
如果所涉及的字段没有。 t属于实体所代表的分支,就像它与棱镜一样。
第二点......我不知道,可能有用吗?
所以问题是:我能用其他光学器件做一个棱镜怎么办?
修改:感谢大家提供优秀的答案和进一步阅读的链接!我希望我能接受所有这些。
答案 0 :(得分:27)
镜头表征 has-a 关系;棱镜表征是-a 的关系。
Lens s a
表示" s
有 a
&#34 ;;它有方法从a
中只获取一个s
并在a
中仅覆盖一个s
。 Prism s a
表示" a
是 s
&#34 ;;它有方法可以将a
转换为s
并将s
向下转换为a
。
将这种直觉放入代码中,可以让您熟悉" get-set" (或" costate comonad coalgebra")镜片配方,
data Lens s a = Lens {
get :: s -> a,
set :: a -> s -> s
}
和" upcast-downcast"棱镜的代表,
data Prism s a = Prism {
up :: a -> s,
down :: s -> Maybe a
}
up
将a
注入s
(不添加任何信息),down
测试s
是否为a
。
在lens
中,up
拼写为review
,down
为preview
。没有Prism
构造函数;你使用the prism'
smart constructor。
你可以用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
。实际上,您可以撰写Traversal (Either a b) a
,但这不允许您从Either a b
创建a
- 它&#39 ; ll只允许你覆盖已经存在的a
。
旁白:我认为关于
Traversal
s的这一微妙之处是您对部分记录字段产生混淆的根源。带有普通镜头的{p>^?
如果相关字段不属于实体代表的分支,则可以Nothing
将
^?
与真实Lens
一起使用将永远不会返回Nothing
,因为Lens s a
只能在a
中识别出一个s
。 遇到部分记录字段时,data Wibble = Wobble { _wobble :: Int } | Wubble { _wubble :: Bool }
makeLenses
将生成Traversal
,而不是Lens
。wobble :: Traversal' Wibble Int wubble :: Traversal' Wibble Bool
有关如何在实践中应用Prism
的示例,请查看Control.Exception.Lens
,它将Prism
的集合提供给Haskell的可扩展{{1}层次结构。这使您可以在Exception
上执行运行时类型测试,并将特定异常注入SomeException
。
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
}
es和Lens
共同编码面向对象编程,组合和子类型的两个核心设计工具。 Prism
是Java的Lens
和.
运算符的一等版本,而=
是Java的一等版本Prism
和隐式上传。
考虑instanceof
es的一种富有成效的方法是,它们为您提供了一种将复合Lens
拆分为聚焦值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
/ {{ 1}}对于这些类型!)
从这个意义上说,up
是 co - down
。 Prism
是Lens
的绝对对偶; Either
是(,)
的绝对对偶。
您还可以在"profunctor optics"表述中观察到这种二元性 - Strong
和Choice
是双重的。
Prism
这或多或少是Lens
使用的表示,因为这些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
es和lens
是非常可组合的。您可以撰写Lens
以获得更大的Prism
s(&#34; Prism
是 Prism
,其中是< / em> a
&#34;)使用s
;使用p
撰写(.)
会为您提供Prism
。
答案 1 :(得分:12)
我刚写了一篇博文,可能有助于建立一些关于棱镜的直觉:棱镜是构造函数(透镜是字段)。 http://oleg.fi/gists/posts/2018-06-19-prisms-are-constructors.html
Prism可以作为一流模式匹配引入,但这是一个 片面的观点。我说他们是广义的构造函数,尽管可能 更常用于模式匹配而不是实际构造。
构造函数(以及合法棱镜)的重要属性是它们 注入。虽然通常的棱镜法律没有直接说明, 可以推断出注入性属性。
引用lens
- 图书馆文档,棱镜法则是:
首先,如果我review
的值为Prism
,然后是preview
,我会将其取回:
preview l (review l b) ≡ Just b
其次,如果您可以使用值Prism
中的l
s
提取值a,那么
值s
完全由l
和a
描述:
preview l s ≡ Just a ⇒ review l a ≡ s
事实上,仅凭第一定律就足以证明建筑的注入性
通过Prism
:
review l x ≡ review l y ⇒ x ≡ y
证据很简单:
review l x ≡ review l y
-- x ≡ y -> f x ≡ f y
preview l (review l x) ≡ preview l (review l y)
-- rewrite both sides with the first law
Just x ≡ Just y
-- injectivity of Just
x ≡ y
我们可以使用injectivity属性作为等式中的附加工具
推理工具箱。或者我们可以将它用作检查决定的简单属性
是否合法Prism
。检查很容易,因为我们只有
review
的{{1}}面。例如,许多智能构造函数
规范化输入数据,不是合法的棱镜。
使用case-insensitive
的示例:
Prism
第一项法律也被违反:
-- Bad!
_CI :: FoldCase s => Prism' (CI s) s
_CI = prism' ci (Just . foldedCase)
λ> review _CI "FOO" == review _CI "foo"
True
λ> "FOO" == "foo"
False
答案 2 :(得分:8)
除了其他优秀的答案外,我觉得Iso
为考虑此问题提供了一个很好的有利位置。
有一些i :: Iso' s a
表示如果您有s
值,您(实际上)也会有a
值,反之亦然。 Iso'
为您提供了两个转换函数view i :: s -> a
和review i :: a -> s
,它们都保证成功且无损。
有一些l :: Lens' s a
表示如果您有s
,您还有a
,但反之亦然。 view l :: s -> a
可能会在此过程中删除信息,因为转换不是无损的,因此如果您拥有的只是a
,则无法采取其他方式(cf 。set l :: a -> s -> s
,除了s
值之外还需要a
才能提供缺失的信息。
p :: Prism' s a
表示如果您有s
值,可能也有a
,但无法保证。转换preview p :: s -> Maybe a
无法保证成功。不过,你确实有另一个方向,review p :: a -> s
。换句话说,Iso
是可逆的并且总是成功的。如果你放弃了可逆性要求,你得到Lens
;如果你放弃成功保证,你会获得Prism
。如果你放弃两者,你会得到一个affine traversal(不是镜头作为一个单独的类型),如果你更进一步,放弃最多只有一个目标,你就结束了Traversal
。这反映在the lens subtype hierarchy的一颗钻石中:
Traversal
/ \
/ \
/ \
Lens Prism
\ /
\ /
\ /
Iso