什么是棱镜?

时间:2018-06-18 18:34:14

标签: haskell lens

我试图更深入地了解lens库,因此我会使用它提供的类型。我已经有了一些镜片经验,并且知道它们有多么强大和方便。所以我转向棱镜,我有点迷失。似乎棱镜允许两件事:

  1. 确定实体是否属于和类型的特定分支,如果属于,则捕获元组或单个元素中的基础数据。
  2. 对实体进行解构和重构,可能正在修改它。
  3. 第一点似乎很有用,但通常一个人不需要来自实体的所有数据,^?使用普通镜头可以获得Nothing如果所涉及的字段没有。 t属于实体所代表的分支,就像它与棱镜一样。

    第二点......我不知道,可能有用吗?

    所以问题是:我能用其他光学器件做一个棱镜怎么办?

    修改:感谢大家提供优秀的答案和进一步阅读的链接!我希望我能接受所有这些。

3 个答案:

答案 0 :(得分:27)

镜头表征 has-a 关系;棱镜表征是-a 的关系。

Lens s a表示" s a&#34 ;;它有方法从a中只获取一个s并在a中仅覆盖一个sPrism 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
}

upa注入s(不添加任何信息),down测试s是否为a

lens中,up拼写为reviewdownpreview。没有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 PrismLens的绝对对偶; Either(,)的绝对对偶。

您还可以在"profunctor optics"表述中观察到这种二元性 - StrongChoice是双重的。

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完全由la描述:

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 -> areview 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