为啥在 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 :: someDataTypeshow 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 定义中,showread 函数在 Text 类中捆绑在一起。当您导出ShowRead 时,这不是什么大问题,但是当您手动编写它们时会很痛苦。你经常想定义一个特殊的show 函数,但现在你不得不写一个read 函数,所以最好把它们分开。就我个人而言,我几乎总是派生出Show,但几乎从不派生出Read

ShowRead 类并不是真正用于序列化,而是用于简单的输入和输出。

【讨论】:

我对 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 -> StringString 之间建立同构?如果你给我ReadShow 实例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 的专利。我倾向于使用ShowRead 进行诊断,尤其是在ghci 中进行尝试。为此,Show 更有用,因为 ghci 有一个 Haskell 解析器来进行读取。

【讨论】:

我明白了,我会回来了解您的evil 功能!我也会看看Serialize

以上是关于为啥在 Haskell 中显式推导 Show/Read?的主要内容,如果未能解决你的问题,请参考以下文章

Haskell 中的参数化类型

为啥我们需要显式调用 zero_grad()? [复制]

在 terraform 模块中显式使用提供程序

在 DataFrameMapper 中显式删除列

在 mvvm 中显式保存

在 QT 中显式调用paintGL