使用makePrisms的例子和例子

时间:2017-07-03 15:04:07

标签: haskell lens

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

我知道当我们想要访问嵌套结构/数据时,请使用如下的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!

1 个答案:

答案 0 :(得分:3)

为了理解这一点,您必须了解类型,类型变量,代数数据类型(总和和产品类型),还必须特别了解类型类和Functor类型类。如果您不理解这些内容,请将此页面加入书签,然后关闭并理解它们,可能使用我为此目的帮助处理的资源,以说明和解释这些基础知识:http://happylearnhaskelltutorial.com

所以在我们进入棱镜之前,你首先需要知道镜头是什么。

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

我现在想和你一起尝试一下这个描述的实验。

说我有一个非常小的文字页面,上面有一些文字。现在,我递给你一块与该页面大小相同的纸板,只有它有一个用于放大镜的孔,专注于特定的单词。该词位于该页面的特定位置。

所以我们有这两样东西:一个页面,一个放大镜" card"没有放大镜...如果我们放入一个玻璃杯,它会集中在页面上的某个特定位置。现在有人带着另一个页面,上面有不同的单词,但与第一页的布局相同。

您可以轻松拍摄该卡片,将其放在新页面上,并将重点关注同一位置的不同字词。

除了放大镜,你还有一个特殊的"橡皮擦/打印机"将它放入卡中时,可以擦除并在该页面上键入文字。

所以现在将它应用于Lens,你可以看到这个例子我们如何获取一个数据(一页文本),一个适合该数据的镜头(一个带有一个与其形状匹配的孔的卡片) (页面的)和一个可以" get"或"设置" (或者其他东西)(也就是放大镜或橡皮擦/打印机玻璃,或其他类型的眼镜),从这里我们可以view更大的数据(单词)要提取它的数据(页面)......或者我们可以set将不同的匹配数据放到该页面上的位置...

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

这个功能有什么作用?它从某些功能创建镜头。现在我们有了上面的框架来理解镜头是什么,我们可以取消这个功能。我告诉你,s变量代表" state"并且它对应于纸张的类型,这是镜头将聚焦在其中的背景。接下来我可以说a类型变量对应于镜头将关注的页面上的单词。那么bt怎么样?如果我们决定更改a的值,则它们是已转换的sa值,并且该更改会更改其类型。

那时Functor是什么?我们会在一段时间内发现。好吧,首先让我们制作一个镜头来实现这一点。所以,回到我们的lens函数,它的第一个参数是" getter"函数,第二个参数是" setter"功能(因此类型)。然后是另一个论点。好吧,因为Haskell函数被认为实际上是返回类型:函数s -> f t。我们现在就制作一个镜头。

我们假设我们有一个值列表[(1,(5,9)), (2,(3,6))],我们想制作一个专注于第二个嵌套元组中第二个值的镜头。但是,这很愚蠢,因为你可以使用snd . snd吧?是的,你可以,所以它不是一个很好的例子,但所有更好的例子都比较复杂,所以忍受我 - 我们会找到他们,此外,你的snd.snd能否发挥作用SET还是应用了一个功能?不,我不这么认为! :)(好吧,我知道你可以使用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))]好吧......

如果它只是吸气者或者是安装者,那就很无聊,但是还有一个名为over的功能,它将采用一个镜头和一个转换功能,然后运行该转换功能。镜头的焦点...所以让我们从每个减去10:map (over sndOfSnd (flip (-) 10)) [(1,(5,9)), (2,(3,6))] - > [(1,(5,-1)),(2,(3,-4))]很酷!好吧,我会让你阅读其余的镜头文档,以了解所有其他功能,因为它们深入和镜头组成,你也可以用它们做各种其他的事情。

<强>棱镜

我承诺我们会去Prism,看看我们在这里...... A Lens是一个特定的&#34;光学&#34; (同时,有时也会混淆地指出整套光学器件,所以要注意这一点),Prism是另一种光学器件,幸好完全具体。如果镜头是一个适用于聚焦产品 代数数据类型的特定部分的光学元件,则Prims会为 sum 类型。这就是为什么我们能够制作一个谈论配对((,))的镜头,因为它们是产品类型......也就是说它们将两种类型组合成一。镜头可让您专注于一件作品,或通过这些作品的路径。顺便说一句,我们上面创建的镜头可以通过组合更通用的内置镜头轻松定义:_2 . _2。还有我们正在讨论的所有镜头功能的操作员版本。他们看起来很疯狂,但他们对他们有逻辑。阅读它们!

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

Just 10 - &gt; Right然后preview _Left (Right 10)一个? Nothing - &gt; set。好的,这很好。

set _Left 30 (Left 10)函数运行正常,因为如果它没有意义,它可以无声地失败:Left 30 - &gt; set _Right 30 (Left 10)。当它不起作用时会发生什么? Left 10 - &gt; makeLenses这是对的,没有。

酷......希望这能解释镜头和Prims。他们是两个非常有用的光学器件。镜头库完整,所以我鼓励你看看它们

原来的问题怎么样?

原始问题是关于makePrisms和{{1}}。这些是模板haskell表达式(也就是说,它们是元编程/类似于宏,但是类型化的宏),它们允许您从自己的数据类型自动构建自己的镜头和/或棱镜。希望现在当你选择一个而不是另一个时它更有意义。这将至少让你了解他们的不同之处。要真正理解,您应该阅读文档并查找可能的所有其他功能和光学器件。

欢迎来到令人惊叹的镜头世界!