我一直在尝试对数据类型进行一些抽象,我遇到了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
合并为一个,而不是另一个?
答案 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 -> ???)
形式的???是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"
的论点并不严格。所以这是捕获,你的代码都编译为常量"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,只是使每个字段都未定义,但顶层构造函数不应该是底部。
答案 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
这样,匹配实际上不会强制该值。它总是会成功,f
和fs
的使用会转换为选择器函数的应用程序。
通过此更改,您的程序可以正常运行:
ghci> gfields $ from (undefined :: Thing)
["foo","bar"]
您的其他程序有效,因为您在外部上致电selName
:
ghci> selName $ (\(l :*: r) -> l) $ unM1 $ unM1 $ from (undefined :: Thing)
"foo"
现在,即使表达式中的对上有模式匹配,也只有类型
selName
的论证与结果相关。但这个表达并不完全正确
与您的第一个测试程序相同,因为不同的结果证明和jozefg
在他的回答中进一步解释。