8

在我熟悉的大多数 OO 语言中,toStringa 的方法String实际上只是恒等函数。但是在 Haskell 中show添加了双引号。

所以如果我写一个像这样的函数

f :: Show a => [a] -> String
f = concat . map show

它对数字按预期工作

f [0,1,2,3]  -- "0123"

但是字符串以额外的引号结尾

f ["one", "two", "three"] -- "\"one\"\"two\"\"three\""

当我真的想要"onetwothree"

如果我想f多态地编写,有没有办法只用一个Show约束来完成它,而不用覆盖 String 的 Show 实例(如果这甚至可能的话)。

我能想到的最好的方法是创建自己的类型类:

class (Show a) => ToString a where
   toString = show

并为所有内容添加一个实例?

instance ToString String where toString = id
instance ToString Char where toString = pure
instance ToString Int
instance ToString Maybe
...etc
4

4 回答 4

8

我认为你的问题的根本原因show是不是真的renderToText。它应该生成可以粘贴到 Haskell 代码中以获得相同值的文本,或者使用read.

为此目的,show "foo" = "foo"将不起作用,因为show "1" = "1"andshow 1 = "1"会丢失信息。

您希望能够应用 to "foo"get"foo"和 to 1to get"1"的操作不是show. show只是不是 Java 风格的toString

当我以前需要这个时,我确实创建了自己的新类型类并创建了一堆它的实例,然后使用它而不是Show. 大多数实例都是用 实现的show,但String不是我想要自定义的唯一一个,因此单独的类型类并没有完全浪费。在实践中,我发现我实际上需要实例的类型很少,而且当我遇到编译错误时添加它们非常简单。

于 2012-08-28T02:39:05.690 回答
5

该类Pretty及其对应的类型Doc具有 Show 所需的行为。但是,您的链接显示了不同的用例;也许您可以编辑问题?

于 2012-08-28T00:45:18.003 回答
3

你可以这样做:

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}

class Show a => ToString a where 
    toString :: a -> String

instance Show a => ToString a where 
    toString = show

instance ToString String where 
    toString = id

Prelude> toString "hello"
"hello"
Prelude> toString 3
"3"

请注意,这可能是一个糟糕的主意。

于 2012-08-28T00:21:44.187 回答
2

您可以newtype使用OverloadedStrings

{-# LANGUAGE OverloadedStrings #-}

import           Data.ByteString.Char8      (ByteString)
import qualified Data.ByteString.Char8 as B

newtype LiteralString = LS ByteString
instance IsString LiteralString where fromString  = LS . B.pack
instance Show     LiteralString where show (LS x) = B.unpack x
instance Read     LiteralString where readsPrec p s = map (\(!s, !r) -> (LS s,r)) $! readsPrec p s

hello :: LiteralString
hello = "hello world"

main :: IO ()
main = putStrLn . show $! hello

输出:

hello world

正常情况下的双引号在较大表达式的上下文中读回显示的字符串时实际上很有用,因为它们清楚地将显示的字符串值与其他显示类型的值分开:

x :: (ByteString, Int)
x =     read . show $!  ("go", 10)
--  string value starts --^^-- ends

y :: (LiteralString, Int)
y =     read . show $!  ("go", 10) 
-- string value starts --^       ^ consumes all characters; read fails
于 2012-08-28T00:31:52.687 回答