首页 > 解决方案 > 什么是棱镜?

问题描述

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

  1. 确定一个实体是否属于 sum 类型的特定分支,如果是,则捕获元组或单例中的基础数据。
  2. 解构和重建实体,可能正在修改它。

第一点似乎很有用,但通常不需要来自实体的所有数据,并且^?使用普通镜头可以获取Nothing相关字段是否不属于实体所代表的分支,就像使用棱镜一样。

第二点……不知道,可能有什么用?

所以问题是:我可以用棱镜做什么,而我不能用其他光学元件?

编辑:谢谢大家的优秀答案和进一步阅读的链接!我希望我能全部接受。

标签: haskellhaskell-lens

解决方案


镜头刻画了has-a关系;棱镜表征is-a关系。

ALens s a说“s 有一个 a”;它有一些方法可以a从 an中获取一个s并覆盖a一个s. APrism s a说“a 是一个 s”;它具有将an 向上转换a为 ans和(尝试)将 an 向下转换s为 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将 ana注入s(不添加任何信息),并down测试是否sa.

lens,up是拼写reviewdownpreview。没有Prism构造函数;您使用智能prism'构造函数


你可以用 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
}

镜头不支持这一点 - 你不能写 aLens (Either a b) a因为你不能实现get :: Either a b -> a. 实际上,您可以编写 a Traversal (Either a b) a,但这不允许您Either a b从 an创建a- 它只会让您覆盖a已经存在的 an 。

旁白:我认为关于Traversals 的这个微妙点是您对部分记录字段感到困惑的根源。

^?Nothing如果有问题的字段不属于实体所代表的分支,则使用普通镜头可以获取

^?与 real 一起使用Lens将永远不会返回Nothing,因为 aLens s a准确地标识了aa中的一个s。遇到部分记录字段时,

data Wibble = Wobble { _wobble :: Int } | Wubble { _wubble :: Bool }

makeLenses将生成 a Traversal,而不是 a Lens

wobble :: Traversal' Wibble Int
wubble :: Traversal' Wibble Bool

有关如何Prism在实践中应用 s 的示例,请查看 to Control.Exception.Lens,它提供了一组Prisms 到 Haskell 的可扩展Exception层次结构中。这使您可以对 s 执行运行时类型测试SomeException并将特定异常注入SomeException.

_ArithException :: Prism' SomeException ArithException
_AsyncException :: Prism' SomeException AsyncException
-- etc.

(这些是实际类型的略微简化版本。实际上,这些棱镜是重载的类方法。)

在更高的层次上思考,某些整个程序可以被认为是“基本上是一个Prism”。编码和解码数据就是一个例子:您总是可以将结构化数据转换为 a String,但不是每个String都可以解析回来:

showRead :: (Show a, Read a) => Prism String a
showRead = Prism {
    up = show,
    down = listToMaybe . fmap fst . reads
}

总而言之,Lenses 和Prisms 一起编码了面向对象编程、组合和子类型化这两个核心设计工具。Lenses 是 Java.=运算符的一流版本,Prisms 是 Javainstanceof和隐式向上转换的一流版本。


思考Lenses 的一种卓有成效的方式是,它们为您提供了一种将组合s拆分为集中值a和某些上下文的方法c。伪代码:

type Lens s a = exists c. s <-> (a, c)

在这个框架中, aPrism为您提供了一种将 as视为 ana或 some context的方法c

type Prism s a = exists c. s <-> Either a c

(我会让你相信这些与我上面演示的简单表示是同构的。尝试为这些类型实现get/// ! )setupdown

在这个意义上 aPrism是一个co-LensEither是 的分类对偶(,)Prism是 的范畴对偶Lens

您还可以在“profunctor optics”公式中观察到这种二元性——Strong并且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

这或多或少是lens使用的表示,因为这些Lenses 和Prisms 是非常可组合的。您可以使用;组合Prisms 以获得更大Prism的 s (" a is an s , which is a p ") 用 a(.)组合a会给你一个.PrismLensTraversal


推荐阅读