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)
这种天真的方法会导致一些代码重复.但是这两个A和B共享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引发错误),它需要将它包装在更多的样板代码中.
所以我的问题是:为什么不支持上面的语法,是否有任何优雅的方法来实现类似的行为?
谢谢!
你不能这样做的原因是因为在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)