在我熟悉的大多数OO语言中,toString
的{{1}}方法实际上只是身份函数。但在Haskell中,String
添加了双引号。
所以如果我写一个像这样的函数
show
它按预期的数字
工作f :: Show a => [a] -> String
f = concat . map show
但是Strings最终会有额外的引号
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
答案 0 :(得分:8)
我认为问题的根本原因是show
并非renderToText
。它应该生成可以粘贴到Haskell代码中的文本以获取相同的值,或者使用read
转换回相同的值。
为此目的,show "foo" = "foo"
无效,因为show "1" = "1"
和show 1 = "1"
会丢失信息。
您希望能够应用于"foo"
以获取"foo"
以及1
获取"1"
的操作不是show
。 show
只是不是Java风格的toString
。
当我之前需要这个时,我确实创建了自己的新类型,并制作了一堆实例,然后使用它而不是Show
。大多数实例都使用show
实现,但String
并不是我想要自定义的唯一实例,因此单独的类型类并未完全浪费。在实践中,我发现实际上只需要少量类型的实例,并且在编译错误时添加它们非常简单。
答案 1 :(得分:5)
答案 2 :(得分: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"
请注意,这可能是一个糟糕的主意。
答案 3 :(得分: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