6

我一直在尝试对数据类型进行一些抽象,并且遇到了 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)

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

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

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

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

为什么我Prelude.undefined进了一个,而不是另一个?

4

2 回答 2

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

于 2013-11-12T07:22:01.147 回答
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它是常量,我们不使用任何以前的计算;这是一个编译时计算。l但是,如果我们使用该 lambda和在该 lambda 中进行任何类型的计算,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,只是使每个字段未定义,但顶级构造函数不应该是底部。

于 2013-11-12T03:58:39.123 回答