[英]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中的l
和r
做任何计算,那么当我们使用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
这样,匹配实际上不会强制该值。 它总是会成功, f
和fs
的使用转化为选择器功能的应用。
通过此更改,您的程序可以运行:
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.