(Haskell) 试图在数据上派生 Show 类程序在编译时给出警告,然后在运行时侵入所有 RAM是什么原因?

问题描述

尝试在 Haskell 中导出用户定义数据的显示时,出现内存不足错误。是什么原因?

--ghc 8.0.2

class X a where
    f::a->a
    f a = a
    
data T = AA | BB
instance X T
instance Show T

var::T
var = AA

main = print $ var

我希望 Haskell 能够制作 X 类和 Show 类的 T instacne,结果打印 AA。实例函数适用于类 X(实例 X T),但不适用于类 Show(实例 X Show)。

预期行为是以下代码的行为:

--ghc 8.0.2

class X a where
    f::a->a
    f a = a
    
data T = AA | BB deriving Show --notice I changed here
instance X T
--notice I changed here

var::T
var = AA

main = print $ var

-------------------------已编辑--------------------- ---

编译输出为:

$ ghc test.hs -o test

[1 of 1] Compiling Main             ( test.hs,test.o )

test.hs:8:10: warning: [-Wmissing-methods]
    • No explicit implementation for
        either ‘showsPrec’ or ‘show’
    • In the instance declaration for ‘Show T’
  |
8 | instance Show T
  |          ^^^^^^
Linking test ...

运行时没有输出

$./test

当编译的程序运行时,它会侵入我计算机的所有可用内存。当我尝试使用交互式 GHCi 运行它时,它会引发堆栈溢出错误

为什么可执行文件在尝试像第一个代码示例中那样实例化 T 时尝试分配无限内存?它是一个错误还是其他与函数式编程范式相关的东西?或者一些与类型多态相关的限制?

-------------------------已编辑--------------------- ---

-------------------------OLD--------------------- ---

脚本输出是: code output

GHCi,version 8.6.5: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( outofmemory.hs,interpreted )

outofmemory.hs:9:10: warning: [-Wmissing-methods]
    • No explicit implementation for
        either ‘showsPrec’ or ‘show’
    • In the instance declaration for ‘Show T’
  |
9 | instance Show T
  |          ^^^^^^
Ok,one module loaded.

为什么 ghci 在尝试像第一个代码示例中那样实例化 T 时尝试分配无限内存?它是一个错误还是其他与函数式编程范式相关的东西?或者一些与类型多态相关的限制?

-------------------------OLD--------------------- ---

解决方法

错误输出并未说明内存不足,但我认为您的源文件只是名为 outofmemory

关于您的错误:类型类 T 没有实例化必须为其提供实现的任何函数,但 Show 有。您需要实现函数 show 以使您的类型成为 Show 的实例,例如:

instance Show T where
    show AA = "AA"
    show BB = "BB"

如果您像这样省略 X 的默认实现,您将在 f 中触发相同的错误:

class X a where
    f::a->a
--    f a = a -- no default

deriving 关键字允许派生类型类 ReadShowBoundedEnumEqOrd。这是由编译器提供的。你不能在你自己的类型中使用它。

,

当你定义一个实例时,你应该总是定义相关的类方法。如果不这样做,则使用类默认值来填充所需的定义。

事实证明,在许多类中,默认值使用相互递归定义方法。例如。我们可能有以下默认值:

class Eq a where
   (==) :: a -> a -> Bool
   x == y = not (x /= y)
   (/=) :: a -> a -> Bool
   x /= y = not (x == y)

我们定义 ==/= 的地方。但是,如果两者一起使用,这些默认值将导致非终止(或内存不足),就像在实例不提供任何定义的情况下一样。事实上,在这种情况下,使用任何一种都会使程序陷入无限(相互)递归。

为了防止这个问题,如果我们没有定义至少一个,GHC 会发出警告,前提是我们启用了警告。

关于您的情况:在类 Show 中,我们还有两个方法,它们的默认值是相互递归的。实际上,在 docs 中我们读到我们必须至少定义其中之一:

最小完整定义

showsPrec | show

即使在这种情况下,如果我们没有定义这些成员中的至少一个,GHC 也会发出警告,前提是我们启用了警告。

这是可以通过启用警告来防止的几个 Haskell 陷阱之一。因此,我强烈建议您始终保持警告状态,例如使用 -Wall 编译器标志。