与记录语法匹配的模式

Ben*_*esh 0 haskell pattern-matching

请考虑以下数据定义:

data Foo = A{field::Int}
         | B{field::Int}
         | C
         | D
Run Code Online (Sandbox Code Playgroud)

现在让我们假设我们想编写一个函数,如果它存在则需要Foo增加并增加field,否则保持不变:

incFoo :: Foo -> Foo
incFoo A{field=n} = A{field=n+1}
incFoo B{field=n} = B{field=n+1}
incFoo x = x
Run Code Online (Sandbox Code Playgroud)

这种天真的方法会导致一些代码重复.但是这两个AB共享field允许我们重写它的事实:

incFoo :: Foo -> Foo
incFoo x | hasField x, n <- field x = x{field=n+1}
incFoo x = x

hasField A{} = True
hasField B{} = True
hasField _ = False
Run Code Online (Sandbox Code Playgroud)

不那么优雅,但是当实际操作很复杂时,它更容易维护.这里的关键特征是x{field=n+1}- 记录语法允许我们"更新" field而不指定x类型.考虑到这一点,我期望类似于以下语法(不支持):

incFoo :: Foo -> Foo
incFoo x{field=n} = x{field=n+1}
incFoo x = x
Run Code Online (Sandbox Code Playgroud)

我已经考虑过使用View Patterns,但由于它field是一个部分函数(field C引发错误),它需要将它包装在更多的样板代码中.

所以我的问题是:为什么不支持上面的语法,是否有任何优雅的方法来实现类似的行为?

谢谢!

Dan*_*zer 5

你不能这样做的原因是因为在Haskell中,记录本质上是第二类的.它们总是必须包含在构造函数中.因此,为了使这项工作按预期进行,您必须为每个构造函数使用单独的大小写,或者使用记录替换.

一种可能的解决方案是使用镜头.我将使用镜头的实现,lens因为lens-family-th似乎没有处理重复的字段名称.

import Control.Lens

data Foo = A {_f :: Int}
         | B {_f :: Int}
         deriving Show
makeLenses ''Foo

foo :: Foo -> Foo
foo = f %~ (+1)
Run Code Online (Sandbox Code Playgroud)

然后我们可以像这样使用它

> foo (A 1)
  A{_f = 1}
Run Code Online (Sandbox Code Playgroud)