如何在Haskell中覆盖一些基本类型的Show实例?

7

我正在使用Haskell编写一些程序,涉及到很多基本类型,比如Word32/Word64等等。 我经常使用ghci测试函数,并在终端中查看结果。

为了方便和快速,我总是以十六进制的形式显示数据,例如:

data Human = M Int | F Int
instance Show Human where
    show M x = printf "man, age %d" x
    show F x = printf "woman, age %d" x

但是我希望基本类型以十六进制显示(尤其是在ghci中)。 我发现实例声明不能被覆盖。 而且我不想把它们全部包起来,比如:

newtype MyInt = MyInt Int
instance Show MyInt where
    ...

看起来有点傻。

我能在ghc的包base中修改一些代码吗?我只想让所有东西变成“十六进制”。我只想让ghci显示“十六进制”。我该如何实现?

编辑

由于我们所有人都认为覆盖Show不合适和不切实际,任何一个“更好地显示ghci中的十六进制数字”的答案都受欢迎。

4个回答

7
没有newtype,就没有办法实现这个功能;实例无法被覆盖。
如果你真的想要这个功能,我建议定义自己的类型类ShowHex,与 Show 类似,但所有的实例都打印十六进制。然而,我认为你的 Show 实例是不正确的;Show 实例用于调试和序列化,应输出语法上有效的代码。1 你的代码不是这样的,所以我建议要么定义自己的类型类来显示这些值,要么只是使用一个函数。
对此进行基础代码修改是不切实际的;不仅实例语义的更改会破坏很多包,而且让 GHC 使用您修改后的版本会非常麻烦。 1 理想情况下,它们生成的代码应该是在语义上有效的 Haskell 代码,可以产生与 show 的输入相等的值,但这并非绝对必要。

我能否告诉ghci使用ShowHex来显示,而不是Show?我想让ghci以十六进制形式显示。 - wuxb
不过,你可以定义 p = putStrLn . myShow 并在每行前加上 p $。 (顺便说一下,自从你发表评论以后,我已经更新了我的答案。) - ehird
谢谢。我也这么认为。touch base package 听起来很糟糕。p 对我来说已经足够实用了。 :) - wuxb
3
你可以使用 :set -interactive-print=myPrint 来替换 ghci 中默认的打印函数。将其放在 ~/.ghci 文件中,可以始终覆盖默认设置。 - Michael Fox
关于“实例无法被覆盖”,我有点惊讶,因为我能够为ZipList定义自己的实例(比自动deriving Show给出的表示更短),尽管在hackage上列出了Show (Ziplist a)实例!这可能是因为Control.Applicative没有定义这样的实例,它必须在Text.Show中... - imz -- Ivan Zakharyaschev

5
那样做会滥用Show实例。它并不是真正意义上的格式化工具。如果你想要以十六进制形式显示某些内容,可以使用一个函数进行转换。例如,你可以使用Numeric中的showHex函数来创建一个小助手:
> import Numeric
Numeric> let hex x = showHex x ""
Numeric> hex 123456
"1e240"

谢谢。这非常接近了。使用 hex 将节省更多时间。 - wuxb

2

同意 @ehird 和 @hammar 的观点,这可能会被滥用。在希望一些数字始终显示为十六进制的情况下,我认为这是合理的,因为 "0xff" 是数字的合法表示形式。因此,这样做:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module HexNumber where

import Numeric
import Text.Read
import qualified Text.Read.Lex as L

newtype HexInt a = HexInt { int :: a }
  deriving (Eq, Ord, Num, Enum)

instance (Show a, Integral a) => Show (HexInt a) where 
  show hi = "0x" ++ showHex (int hi) ""

instance (Num a) => Read (HexInt a) where
-- Couldn't figure out how to write this instance so just copy/paste from Text.Read
  readPrec     = readNumber convertInt
  readListPrec = readListPrecDefault
  readList     = readListDefault

readNumber :: Num a => (L.Lexeme -> ReadPrec a) -> ReadPrec a
readNumber convert =
  parens
  ( do x <- lexP
      case x of
        L.Symbol "-" -> do y <- lexP
                            n <- convert y
                            return (negate n)

        _   -> convert x
  )

convertInt :: Num a => L.Lexeme -> ReadPrec a
convertInt (L.Number n)
| Just i <- L.numberToInteger n = return (fromInteger i)
convertInt _ = pfail

现在我能够:
> let x = 10 :: HexInt Int
> x
0xa
> x * 2
0x14
> let x = 10 :: HexInt Integer
> x
0xa
> x * 2
0x14
> read "0xa" :: HexInt Int
0xa
> read "10" :: HexInt Int
0xa

对于我经常处理低级别的东西来说,这似乎非常有用。也许我会把它放在Hackage上。


2

一种极端的解决方案是使用{-# LANGUAGE NoImplicitPrelude #-},并导入自己的“Prelude”。但对于您的情况来说,这可能需要更多的工作,不值得尝试。


如果目标是更改 GHCi 的默认打印行为,那么这是行不通的,因为它已经硬编码使用了标准的 Show 类。 - hammar

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