与记录语法匹配的模式

时间:2014-05-08 13:38:00

标签: haskell pattern-matching

考虑以下数据定义:

data Foo = A{field::Int}
         | B{field::Int}
         | C
         | D

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

incFoo :: Foo -> Foo
incFoo A{field=n} = A{field=n+1}
incFoo B{field=n} = B{field=n+1}
incFoo x = x

这种天真的方法会导致一些代码重复。但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

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

incFoo :: Foo -> Foo
incFoo x{field=n} = x{field=n+1}
incFoo x = x

我考虑使用“查看模式”,但由于field是部分函数(field C引发错误),因此需要将其包装在更多的样板代码中。

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

谢谢!

1 个答案:

答案 0 :(得分: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)

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

> foo (A 1)
  A{_f = 1}