我似乎已经陷入了一个有趣的边缘案例中。解释我想要做的事情很棘手,所以让我用代码写一下:
data Foobar x =
Foo1 {field1 :: x, field2 :: String} |
Foo2 {field1 :: x, field3 :: Int} |
Foo3 { field4 :: Bool} |
Foo4 { field2 :: String, field4 :: Bool}
正如您所看到的,某些构造函数依赖于x
,但其他构造函数则不依赖fmap
。我正在尝试编写类似于transform :: (x -> y) -> Foobar x -> Foobar y
transform fn foobar =
case foobar of
Foo1 {} -> foobar {field1 = fn (field1 foobar)}
Foo2 {} -> foobar {field1 = fn (field1 foobar)}
_ -> foobar
的函数:
fn
正如您所看到的,记录语法整齐地让我避免重建整个构造函数,仅在需要的地方应用fn
。不幸的是,当零位置需要{{1}}时,这会中断。在那种情况下(即最后的替代方案),表达式无法进行类型检查。我很清楚为什么它失败了 - 但我对如何修复这一点感到困惑。
显然我可以直接写出整个事情。这适用于这个缩减的例子,但我想写的真正的应用程序要大得多。 (大约25个构造函数,其中一些构造函数超过15个。)
有没有人对如何解决这个故障有任何干净的想法?
答案 0 :(得分:4)
一种解决方案(保存输入)是使用记录通配符:
{-# LANGUAGE RecordWildCards #-}
-- ...
case foobar of
Foo4 {..} -> Foo4 {..}
答案 1 :(得分:2)
如果您不想手动执行此操作,则必须进行模式匹配。如果你想避免这么多的工作,一个简单的解决方法是
{-# LANGUAGE DeriveFunctor #-}
data Foo a = ...
deriving(Functor)
现在我们可以写一个更安全的unsafeCoerce
coerceFoo :: Foo a -> Foo b
coerceFoo = fmap (error "This shouldn't be used on a phantom type")
在你的例子中使用它
_ -> coerceFoo foobar