访问Haskell中的“默认展示”?

9

假设你有一个数据结构(来自于这个问题):

data Greek = Alpha | Beta | Gamma | Delta | Eta | Number Int

现在,我们可以通过在指令末尾添加“deriving Show”将其实例化为Show

然而,如果我们希望将Number Int显示为:

instance Show Greek where
    show (Number x) = show x
    -- ...

问题在于必须同时指定所有其他部分的希腊数据,例如:Greek
    show Alpha = "Alpha"
    show Beta = "Beta"

对于这个小例子,当然可以实现。但是如果选项数很多,就需要大量的工作。

我想知道是否可以访问“默认显示”实现,并使用通配符调用它。例如:

instance Show Greek where
    show (Number x) = show x
    show x = defaultShow x

你因此“实现”了与默认方法不同的特定模式,其余模式由“回退机制”解决。
这有点类似于面向对象编程中使用super.method引用进行方法覆盖。

6
正确的方式是使用 .. deriving Show,然后编写一个漂亮打印的类(或者只是一个函数),以便按照您喜欢的格式显示数据。此外,在Haskell中不存在方法覆盖或super.method,因为Haskell中没有子类型化。 - user2407038
5
show 函数应该生成一个字符串,其中包含一个 Haskell 表达式,一旦执行该表达式即可还原出原始值。如果需要其他功能,应声明一个不同的函数(可能在自己的类中)。 - chi
1
那么,为什么它可以被覆盖(可以在对象上定义一个函数),以及为什么无限列表会被折叠成数组文字? - Willem Van Onsem
1
(a) 因为有时构造函数并没有被导出,所以你必须重写它 (例如见Data.Set) (b) 我不完全理解你的观点,但你说无限列表在合同上是被打破了是对的,例如 read(show[[1..],[2]]) 不能得到原始列表。话虽如此,应用程序代码通常会违反合同,所以你想做什么并不罕见。有时候只需重写 show 来使其更易读,忘记Haskell语法也是很方便的。 - chi
1
但即使show不产生有效的Haskell语法,它仍然被普遍认为是面向程序员的。它的含义是“显示数据的文本表示,以便我可以知道它是什么”,而不是“将此值作为文本显示给最终用户”。用Python术语来说,它是repr,而不是str。因此,以一种与整数无法区分的方式显示一些希腊值是有点代码异味的。 - Ben
显示剩余2条评论
3个回答

11

正如评论中@phg所述,这也可以借助generic-deriving完成:

{-# LANGUAGE DeriveGeneric #-}
module Main where

import           Generics.Deriving.Base (Generic)
import           Generics.Deriving.Show (GShow, gshow)

data Greek = Alpha | Beta | Gamma | Delta | Eta | Number Int
  deriving (Generic)

instance GShow Greek
instance Show Greek where
  show (Number n) = "n:" ++ show n
  show l = gshow l

main :: IO ()
main = do
  print (Number 8)
  print Alpha

7
不,据我所知这是不可能的。
此外,要自定义 Show 实例需要仔细考虑,因为 ShowRead 实例应该是相互兼容的。
如果只是想将其转换为可读字符串,则使用自己的函数或自己的类型类。这也可以实现您想要的功能:
假设您有一个名为 Presentable 的类型类,并具有一个 present 方法,以及默认的 Show 实例,则可以编写如下内容:
instance Presentable Greek where
    present (Number x) = show x
    present x = show x

7
你可以使用Data和Typeable来实现这个目标。当然,这是一种hack的方法,而且这个例子只适用于你的示例中的“枚举”类型。
我相信我们可以更详细地介绍如何做到这一点,但为了涵盖您给出的示例,请按照以下方式操作:
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data
import Data.Typeable

data Greek = Alpha | Beta | Gamma | Delta | Eta | Number Int 
             deriving (Data,Typeable)

instance Show Greek where
    show Number n = show n
    show x = show $ toConstr x

这种方法的实现无法处理嵌套数据结构或其他稍微复杂的情况,但是再次强调,这只是一种丑陋的hack。如果你真的必须使用这种方法,你可以在Data.Data包中寻找,我相信你可以拼凑出一些东西......
这里有一篇博客文章,快速介绍了这些包:http://chrisdone.com/posts/data-typeable
正确的做法是使用newtype包装器。我知道这不是最方便的解决方案,特别是在使用GHCi时,但它不会产生额外的开销,并且随着程序的增长,不太可能以意想不到的方式出错。
data Greek = Alpha | Beta | Gamma | Delta | Eta | Number Int 
         deriving (Show)

newtype SpecialPrint = SpecialPrint Greek

instance Show SpecialPrint where
    show (SpecialPrint (Number x)) = "Number: " ++ show x
    show (SpecialPrint x) = show x

main = do
    print (SpecialPrint Alpha)
    print (SpecialPrint $ Number 1)

3
我认为你可以使用generic-deriving来实现与第一种方法类似的功能,这可能还可以处理嵌套的东西(只要所有内容都是“Generic”)。 - phipsgabler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接