为啥在 Haskell 中显式推导 Show/Read?
Posted
技术标签:
【中文标题】为啥在 Haskell 中显式推导 Show/Read?【英文标题】:Why explicit derivation of Show/Read in Haskell?为什么在 Haskell 中显式推导 Show/Read? 【发布时间】:2012-07-05 04:36:54 【问题描述】:我们不能对每种类型都使用read someValue :: someDataType
或show someValue
,因为deriving (Show, Read)
必须写在data
声明中。除了错误之外,是否存在我们不希望我们的类型可序列化的情况?
为什么 Show 与 Read 分开?有没有一种情况,除了错误,我们只想显示一些数据而不读取它?如果没有,为什么不使用单一数据类型Serializable
?
刚才,我使用的是the Key
datatype of the Gloss library,它派生了Show
,而不是Read
,我不明白。很遗憾,因为我想把控件的配置放在一个文件中然后读取它,这样玩家就可以更改控件并拥有自己的配置。
我必须为 Key、SpecialKey 和 MouseButton 做包装,这没什么大不了的,但没用。
data Key' = Char' Char | SpecialKey' SpecialKey | MouseButton' MouseButton
deriving (Eq, Ord, Show, Read)
convertKey x = case x of
Char' c -> Char c
SpecialKey' sk -> SpecialKey sk
MouseButton' mb -> MouseButton mb
【问题讨论】:
【参考方案1】:首先,不是所有的数据类型都可以显示,例如函数不能显示,所以不是所有的数据类型都可以显示(也不能读取)。最初的 Haskell 定义规定,如果没有给出派生子句,那么将派生尽可能多的派生类。这使得很难知道实际派生了哪些类,因此更改了 Haskell 定义以强制显式派生子句。
其次,在原始的 Haskell 定义中,show
和 read
函数在 Text
类中捆绑在一起。当您导出Show
和Read
时,这不是什么大问题,但是当您手动编写它们时会很痛苦。你经常想定义一个特殊的show
函数,但现在你不得不写一个read
函数,所以最好把它们分开。就我个人而言,我几乎总是派生出Show
,但几乎从不派生出Read
。
Show
和 Read
类并不是真正用于序列化,而是用于简单的输入和输出。
【讨论】:
我对 Python 了解的不够多,但我怀疑答案是否定的。 这是一种将print
对象作为字符串的个性化方式。【参考方案2】:
为什么 Show 与 Read 是分开的
我不知道为什么会这样,但觉得它应该持续存在,因为可以显示一些(很少)类型,或者显示占位符字符串,但不能读回。函数是典型的例子。
另一种思考方式:将事物放在单独的类中很容易,但很难在一个类中处理在相同上下文中并不总是有意义的太多函数。许多人认为Num
类就是这个问题的一个典型例子。
我怎样才能read
密钥和其他类型不在Read
中
第一步:发送一个补丁,将Read
添加到派生实例集。第二步:使用独立派生解决:
-# LANGUAGE StandaloneDeriving #-
deriving instance Show Key
第三步:使用 CPP 使您的代码与任一版本的代码库一起工作,无论是 Ben 有一天会发布的带有 Read 实例的 Gloss 库,还是没有版本。
为什么没有Serializable
类?
对于初学者来说,有a Serialize class。此外,文本是序列化事物的一种可怕方式。也许您想要一个更惰性的序列化类,在这种情况下您应该看到Binary class。如果您关心性能,那么您可能会喜欢blaze-builder,尽管老实说我从未使用过它。
【讨论】:
谢谢,我明白了。对不起,我不太了解在非Read
类型上使用read
的步骤。我应该输入什么代码?
@L01man 我粘贴了上面的代码。您将 -# LANGUAGE ...
行放在文件的顶部,并确保在文件的某处包含 deriving instance Show Key
行。
它仍然显示main: Prelude.read: no parse
。我认为没有第 3 步就可以工作……再次抱歉;您如何实现第 3 步?
你能把你有的东西粘贴到 hpaste 之类的地方吗(或者只是编辑你的问题)?此外,如果您已经走到了这一步,那么程序必须已经编译并因此进行了类型检查——所以这很好。此错误表明您的输入字符串未按照程序预期的方式格式化。
你说得对,我忘记删除字符串中的旧Key'
。现在可以了。感谢您的帮助。【参考方案3】:
并非每种类型都是可序列化的。如何在String -> String
和String
之间建立同构?如果你给我Read
和Show
实例String -> String
,我可以找到这样一个不可序列化的函数:
evil :: String -> String
evil s = map succ (read s s ++ " evil")
假设
read (show evil) = evil
我们得到
evil (show evil)
= map succ (read (show evil) (show evil) ++ " evil")
= map succ (evil (show evil) ++ " evil")
= map succ (evil (show evil)) ++ "!fwjm"
所以如果定义了evil (show evil)
,那么它的第一个字符c
满足c = succ c
,这是不可能的。
一般来说,函数不能被序列化。有时,我们编写打包函数的数据类型,因此也不是每个数据类型都是可序列化的。例如,
data Psychiatrist
= Listen (String -> Psychiatrist)
| Charge Int
有时,即使对于这些类型,您也可以选择提供Read
(缺少某些情况)和Show
(例如,带有函数的占位符或列表)的部分实现,但没有规范的方法来提供选择它们或您期望两者的原因。
正如其他人所提到的,严肃的序列化是Serialize
的专利。我倾向于使用Show
和Read
进行诊断,尤其是在ghci 中进行尝试。为此,Show
更有用,因为 ghci 有一个 Haskell 解析器来进行读取。
【讨论】:
我明白了,我会回来了解您的evil
功能!我也会看看Serialize
。以上是关于为啥在 Haskell 中显式推导 Show/Read?的主要内容,如果未能解决你的问题,请参考以下文章