Haskell实例:这怎么可能是一些有效的代码?
在我编写 Show 实例的一个小例子时,我犯了一个缩进错误:
module Main where
data B= B0|B1
instance Show B where
show B0="0"
show B1="1"
main=print B0
显然,工作代码是:
module Main where
data B= B0|B1
instance Show B where
show B0="0"
show B1="1"
main=print B0
我原以为第一个会出现编译错误,但我可以运行它,结果是:
example.hs: stack overflow
为什么这段代码甚至可以编译?
另外,为什么这只是运行时错误(如果堆栈不受约束,则会填满您的 RAM)而不是编译错误?
回答
an 的主体instance可以是空的。您可以省略以下where条款:
instance Show B
但你也可以包括它:
instance Show B where
-- nothing here
这对于为方法提供默认实现的类型类很有用,可能基于泛型编程工具。例如,对于aeson包,定义实例与 JSON 之间的转换的常用方法是使用泛型和空实例:
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
data Person = Person {
name :: Text
, age :: Int
} deriving (Generic, Show)
-- these empty instances use Generics to provide a default implementation
instance ToJSON Person
instance FromJSON Person
在您的程序中,通过省略缩进,您定义了一个instance Show B没有方法定义的方法(并且-Wall会生成一个“缺少方法”警告,告诉您它不满足实例的最低要求)。unindentedshow为 提供了一个新的顶级定义show,与类型类show中的无关Show。
你没有show明确使用。相反,您通过 隐式使用了它print,它总是show从类型类调用,忽略您的顶级定义,因此您的崩溃程序等效于:
data B = B0 | B1
instance Show B
main = print B0
这会产生堆栈溢出,因为当没有给出特定实例时,会使用show和 的默认定义showsPrec:
show x = shows x ""
showsPrec _ x s = show x ++ s
与顶级函数shows(不是类型类的一部分)一起操作:
shows = showsPrec 0
这在实例中至少定义了一个showor时效果很好showsPrec,然后另一个得到了合理的定义,但如果两者都没有定义,这会在这三个函数之间创建一个无限递归循环。
另外,请注意,以下程序会告诉您show是模棱两可的,这将使您更清楚发生了什么。
module Main where
data B= B0|B1
instance Show B where
show B0="0"
show B1="1"
main=putStrLn (show B0) -- instead of print
回答
由于您没有缩进show,这意味着这不属于instancefor Show,因此您的程序相当于:
instance Show B where -- ← no implementations
show B0 = "0"
show B1 = "1"
因此show,您在这里构造了另一个与Show类型类无关的函数,而只是具有相同的名称。
Show定义了三种功能showsPrec :: Show a => Int -> a -> String -> String,show :: a -> String -> String和showList :: [a] -> String -> String。showsPrec经常使用,因为它具有使用括号的优先级系统。
因此,show在以下方面受到影响showsPrec并showsPrec在以下方面实施show:
class Show a where {-# MINIMAL showsPrec | show #-} -- … showsPrec :: Int -> a -> ShowS -- … show :: a -> String -- … showList :: [a] -> ShowS showsPrec _ x s = show x ++ s show x = shows x "" showList ls s = showList__ shows ls s -- … shows :: (Show a) => a -> ShowS shows = showsPrec 0
因此,实现showsPrecor就足够了show,这就是为什么{-# MINIMUM showsPrec | show #-}在类型类的顶部有一个编译指示。
如果您没有实现这两者中的任何一个,那么编译器将发出警告,并因此使用基本实现,但这将导致什么都没有,因为show将调用showPrec,然后调用show,直到最终堆栈耗尽。