makePrisms 的用例和示例

Use cases of makePrisms with examples

我不清楚 makeLense 和 makePrisms 之间的区别?

我知道当我们想要访问嵌套的 structure/data 时,请像这样使用 makeLense:

data Point = Point { _x :: Int, _y :: Int}
data Test= Test {_name :: String, _position :: Point} 

makeLenses ''Point
makeLenses ''Test

然后我们就可以访问或修改Test或Point的组件了。比如我们定义一个函数:

modify :: Test -> Test
modify = over (position . x) (*8)

所以我们可以有:

let t1 = Test {_name= "Me", _position = Point {_x = 3, _y = 8}}

然后

modify t1

将是:

Test {_name = "Me", _position = Point {_x = 24, _y = 8}}

但是,我不知道在上面的例子中何时以及如何使用 makePrisms!

为了理解这一点,您必须了解类型、类型变量、代数数据类型(求和和乘积类型),还必须了解类型类,尤其是 Functor 类型类。如果您不理解这些内容,请将此页面添加为书签,然后开始理解它们,可能使用我为此目的帮助工作的资源,以说明和解释这些基础知识:http://happylearnhaskelltutorial.com

所以在我们开始棱镜之前,您首先需要知道什么是镜头。

A Lens 通常被描述为功能性 getter/setter,但更多的是关于实现等等。

我现在想在这里和你一起做一个描述实验。

假设我有一页非常小的文字,上面有一些文字。现在,我递给你一张与那一页大小相同的硬纸板,只是上面有一个孔,可以放一个放大镜,用来聚焦某个特定的词。该词位于该页面的特定位置。

所以我们有这两样东西:一个页面和一个放大镜 "card" 没有放大镜...如果我们放一个放大镜,它会聚焦在页面上的特定位置。现在有人带来了另一页,上面有不同的文字,但布局与第一页相同。

很容易想象,您可以拿走那张卡片,将它放在新页面上,它会在同一个地方关注不同的词。

除了放大镜,您还有一个特殊的 "eraser/printer" 玻璃,当您将它放入卡片中时,您可以在该页面上擦除和键入文字。

所以现在将它应用到 Lens,你可以通过这个例子看到如果我们获取一个数据(一页文本),一个适合该数据的镜头(一张有孔的卡片匹配页面的形状)和可以 "get" 或 "set"(或其他东西)的功能(即放大镜或 eraser/printer 玻璃,或其他类型的眼镜) ,好吧,从这里我们可以 view 较大的数据(页面)中的较小的数据(单词)来提取它...或者我们可以 set 不同的匹配部分数据到该页面上的位置...

lens :: :: Functor f => (s -> a) -> (s -> b -> t) -> (a -> f b) -> s -> f t

这个函数有什么作用?它 从某些函数创建 镜头。现在我们有了上面的框架来理解镜头是什么,我们就可以理解这个函数了。我告诉你,s 变量代表 "state",它对应于纸张的类型,即镜头将聚焦的上下文。接下来我可以说 a 类型变量对应于镜头将聚焦的页面上的单词。 bt 呢?如果我们决定更改 a 的值,并且该更改会更改其类型,则它们是转换后的 as 值。

Functor是干什么用的?我们一会儿就会知道。好吧,首先让我们制作一个镜头来实现这一点。所以,回到我们的 lens 函数,它的第一个参数是 "getter" 函数,第二个参数是 "setter" 函数(因此是类型)。然后是另一种说法。嗯,因为 Haskell 函数是柯里化的,所以实际上是 return 类型:一个函数 s -> f t。现在让我们制作那个镜头吧。

假设我们有一个值列表 [(1,(5,9)), (2,(3,6))],我们想制作一个聚焦于第二个嵌套元组中的第二个值的透镜。不过,这很愚蠢,因为您可以只使用 snd . snd 对吗?是的,你可以,所以这不是一个很好的例子,但所有更好的例子都更复杂,所以请耐心等待 - 我们会找到它们,此外,你的 snd.snd 函数是否也可以设置,或者有一个作用于它?不,我不这么认为! :)(好吧好吧,我知道你可以使用 fmap (const val) 来设置,但它也可以改变类型吗?好吧,如果你像爱德华一样继续它的逻辑结论,那么这个思路实际上就是你最终得到 Lens 的结果Kmett 做到了 - 这就是 Functor 的用武之地!)

sndOfSndLens = lens (snd.snd) (\(x,(y,z)) newZ -> (x,(y,newZ)))

那么我们得到了什么?我们得到了这种类型的函数 sndOfSndLens :: Functor f => (a -> f t2) -> (t, (t1, a)) -> f (t, (t1, t2)).

所以让我们得到我们的值:map (view sndOfSnd) [(1,(5,9)), (2,(3,6))] -> [9,6] 很好!行得通...让我们在以下位置设置新值:map (set sndOfSnd 2000) [(1,(5,9)), (2,(3,6))] -> [(1,(5,2000)),(2,(3,2000))] 好的...

如果它只是 getter 或 setter,那很无聊,但还有一个函数叫做 over,它需要一个镜头和一个变换函数,运行 那个变换对焦点的函数镜头的……所以让我们从每个镜头中减去 10:map (over sndOfSnd (flip (-) 10)) [(1,(5,9)), (2,(3,6))] -> [(1,(5,-1)),(2,(3,-4))] 太酷了!好吧,我会让你阅读镜头文档的其余部分以了解所有其他功能,因为它们很深,镜头组成,你也可以用它们做各种其他事情。

棱镜

我答应我们会去棱镜,看我们在这里......镜头是一个特定的 "optic"(同时,有时也令人困惑地指代整套光学器件,所以要当心),而 Prism 是另一种光学器件,谢天谢地,它完全是特定的。如果 Lens 是一种用于聚焦 productalgebraic data type 的特定部分的光学器件,那么 Prims 对 总和 类型。这就是为什么我们能够制作一个谈论成对 ((,)) 的镜头,因为它们是 product 类型……也就是说,它们将两种类型合二为一。 Lens 让您专注于一件作品,或通过这些作品的路径。顺便说一句,我们上面创建的镜头可以通过组合更通用的内置镜头来轻松定义:_2 . _2。还有我们正在谈论的所有镜头功能的操作员版本。他们看起来很疯狂,但他们有他们的逻辑。阅读有关它们的信息!

Prism 让您可以通过 总和类型 专注于一个 路径。什么是好的例子?好吧,假设我们已经考虑了 Either 数据类型。它是Either a b,它被定义为data Either a b = Left a | Right b。所以有一个相应的 prism 函数可以让我们构建一个与上述值相同的棱镜。如果我们使用集中在 Either 左侧的内置 _Left,但我们只有 Right 10 值,会发生什么情况?让我们看看......但首先,我们应该让你知道我们不能再使用 view 因为它可能不起作用,所以我们需要使用 preview 这将 return可能失败的值(抱歉,剧透!):

preview _Left (Left 10) -> Just 10 然后 Right 一个? preview _Left (Right 10) -> Nothing。好的,这很甜蜜。

set 函数工作正常,因为如果它没有意义,它可能会静默失败:set _Left 30 (Left 10) -> Left 30。当它不起作用时会发生什么? set _Right 30 (Left 10) -> Left 10 没错,没什么。

酷...希望这能解释镜头和棱镜。它们是两个非常有用的光学器件。 Lens 库全部,因此我鼓励您查看它们

原来的问题呢?

最初的问题是关于 makeLensesmakePrisms。这些是模板 haskell 表达式(也就是说,它们是元编程/类似于宏,但类型化的宏),它们可以让您根据自己的数据类型自动构建自己的透镜 and/or 棱镜。希望现在当您选择一个而不是另一个以及它们有何不同时,它会更有意义。这至少会让您对它们的不同之处有所了解。要真正理解,您应该阅读文档并查找所有可能的其他功能和光学器件。

欢迎来到炫酷的 Lenses 世界!