繁体   English   中英

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

[英]GHC Generics behavior seems to differ in 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个,而不是另一个?

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

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

但是,将\\(l :*: r) -> selName l ++ selName r更改为您所拥有的不会崩溃。 所以区别在于这一点。 显而易见的想法是,正确的领域有一些不好的东西很快被证明是因为\\(l :*: r) -> r仍在运行。

我们可以看到唯一的非底部结果是形式(\\l :*: r -> ???)其中??? l还是r 没有其他的。

那么让我们看一下使用-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"

它的论点并不严格。 所以这里是catch,你的代码都编译成常量"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,只是使每个字段都未定义,但顶层构造函数不应该是底部。

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

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在他的回答中进一步解释了这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM