GHC泛型行为在GHCi中似乎有所不同

时间:2013-11-11 22:32:41

标签: generics haskell ghc ghci

我一直在尝试对数据类型进行一些抽象,我遇到了GHC的泛型的情况,这看起来有点奇怪。这是我的基本声明集:

class GFields f where
    gfields :: f a -> [String]

instance (GFields c) => GFields (D1 i c) where
    gfields = gfields . unM1

instance (GFields fs) => GFields (C1 i fs) where
    gfields = gfields . unM1

instance (GFields f, GFields fs) => GFields (f :*: fs) where
    gfields (f :*: fs) = gfields f ++ gfields fs

instance (Selector s) => GFields (S1 s r) where
    gfields = (:[]) . selName

data Thing = Thing { foo :: String, bar :: Int }
    deriving (Generic)

如果我给它一个未定义的值,试图在GHCi中使用它会给我Prelude.undefined

> gfields $ from (undefined :: Thing)
*** Exception: Prelude.undefined

但是,如果我尝试手动运行一些预期的实例(只抓一个字段),我会得到我期望的结果:

> selName $ (\(l :*: r) -> l) $ unM1 $ unM1 $ from (undefined :: Thing)
"foo"

为什么我将Prelude.undefined合并为一个,而不是另一个?

2 个答案:

答案 0 :(得分:4)

所以这很有意思,你所拥有的实际上并没有完成所做的事情,内联之后的实际代码是

main = print
    . (\(l :*: r) -> selName l ++ selName r)
    . unM1
    . unM1
    . from
    $ (undefined :: Thing)

但是,将\(l :*: r) -> selName l ++ selName r更改为您所拥有的内容并不会崩溃。所以区别在于这一点。显而易见的是,由于\(l :*: r) -> r仍在运行,因此对于正确的字段有一些不好的事情很快被驳回。

我们可以看到唯一的非底部结果是(\l :*: r -> ???)形式的???是lr。没别了。

让我们看看使用-ddump-deriv的派生实例。

from (Thing g1_atM g2_atN) = M1 (M1 (M1 (K1 g1_atM) :*: M1 (K1 g2_atN)))

请注意,这在构造函数中是严格的。因此,我们必须严格遵守from undefined的结果,因为代码会崩溃。所以现在我们有点走在一个纸牌屋,因为迫使这部分任何部分都会导致我们的计划崩溃。有趣的是

-- The derived selectors
instance Selector S1_0_0Thing where
  selName _ = "foo"

instance Selector S1_0_1Thing where
  selName _ = "bar"

的论点并不严格。所以这是捕获,你的代码都编译为常量"foo",因为selName是常量,我们不使用任何先前的计算;这是一个编译时计算。但是,如果我们在lambda中使用lr进行任何计算,那么当我们使用selName或做任何事情来查看结果时,我们会强制lambda运行,但是由于l :*: r确实是最低点,我们会崩溃。

作为快速演示,这将崩溃

main = (`seq` putStrLn "Crashes")
       . (\(l :*: r) -> ())
       . unM1
       . unM1
       . from
       $ (undefined :: Thing)

但这不会

main = (const $ putStrLn "Crashes")
       . (\(l :*: r) -> ())
       . unM1
       . unM1
       . from
       $ (undefined :: Thing)

TLDR,只是使每个字段都未定义,但顶层构造函数不应该是底部。

答案 1 :(得分:4)

问题是你的实例都没有以任何方式强制论证,除了这个:

instance (GFields f, GFields fs) => GFields (f :*: fs) where
  gfields (f :*: fs) = gfields f ++ gfields fs

您的目标是将undefined传递给您的函数,因此您必须非常小心地强制参数。该论点仅用于指导类型检查器,无法查看。

修复很简单。使模式延迟(或无可辩驳的,就像Haskell报告所称的那样):

instance (GFields f, GFields fs) => GFields (f :*: fs) where
  gfields ~(f :*: fs) = gfields f ++ gfields fs

这样,匹配实际上不会强制该值。它总是会成功,ffs的使用会转换为选择器函数的应用程序。

通过此更改,您的程序可以正常运行:

ghci> gfields $ from (undefined :: Thing)
["foo","bar"]

您的其他程序有效,因为您在外部上致电selName

ghci> selName $ (\(l :*: r) -> l) $ unM1 $ unM1 $ from (undefined :: Thing)
"foo"

现在,即使表达式中的对上有模式匹配,也只有类型 selName的论证与结果相关。但这个表达并不完全正确 与您的第一个测试程序相同,因为不同的结果证明和jozefg 在他的回答中进一步解释。