是否有一个多态的'toString'函数不';不加引号吗?

是否有一个多态的'toString'函数不';不加引号吗?,string,haskell,polymorphism,String,Haskell,Polymorphism,在我熟悉的大多数OO语言中,String的toString方法实际上只是标识函数。但是在Haskellshow中添加了双引号 所以如果我写一个类似这样的函数 f :: Show a => [a] -> String f = concat . map show 它对数字的效果与预期一样 f [0,1,2,3] -- "0123" 但是字符串最后会加上额外的引号 f ["one", "two", "three"] -- "\"one\"\"two\"\"three\"" 当我真的

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

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

f :: Show a => [a] -> String
f = concat . map show
它对数字的效果与预期一样

f [0,1,2,3]  -- "0123"
但是字符串最后会加上额外的引号

f ["one", "two", "three"] -- "\"one\"\"two\"\"three\""
当我真的想要“一对三”的时候

如果我想以多态方式编写
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
您可以这样做:

{-# 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"

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

您可以将
newtype
重载字符串一起使用:

{-# 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

类及其对应的类型具有Show所需的行为。然而,您的链接显示了不同的用例;也许你可以编辑这个问题?

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

为此,
show“foo”=“foo”
不起作用,因为
show“1”=“1”
show 1=“1”
会丢失信息

您希望能够应用于
“foo”
以获取
“foo”
1
以获取
“1”
的操作不是
show
show
不是Java风格的
toString


当我以前需要这个时,我确实创建了自己的新类型类,并创建了一系列的东西实例,然后使用它而不是
Show
。大多数实例都是通过
show
实现的,但是
String
并不是我想要定制的唯一实例,因此单独的类型类并没有完全浪费。在实践中,我发现只有少数几种类型的实例是我真正需要的,当我遇到编译错误时,添加它们非常简单。

我最终使用
newtype
构造创建了一个带有自定义
Show
Read
实例的类型:另请注意
Show
不仅添加了双引号。它还转义字符,例如换行符。例如,一个字符的字符串
“\n”
显示为四个字符的字符串,带有字符“,\,n”“。@PeterHall如果在IO单子的上下文中打印到流,则可以使用更具体的操作,例如
putStr
putstrn
hPutStr
hPutStrLn
-这些不会以任何方式更改字符串(没有双引号、换行符转义等)@ceinsert,但是您无法以多态方式编写函数。这和我原来的问题是一样的。如果你知道它是一个字符串,你可以使用
putStr
——但是要使它具有多态性,你必须使用
print
-aka
putStr。显示
。谢谢。我特别喜欢你的最后一句话“注意,这可能是个糟糕的主意。”:)你能解释一下不同的语言扩展实际上是如何实现这一点的吗?如果没有
FlexibleInstances
,你就不允许裸类型变量作为类型类的成员。如果没有
不可判定实例
,则至少一个实例的类型参数不能是裸类型变量。如果没有这两个选项之一,将不允许上面的第一次声明。如果没有
OverlappingInstances
我们就无法同时拥有上述两个实例,因为第一个实例显然适用于所有可显示的类型,而
String
就是这样一种类型,即它们重叠。请您评论一下,这(可能)是一个糟糕的想法,是实例可能泄漏到其他模块的问题吗?我还没有真正想过它到底有多可怕,我只是本能地对那些在类型检查中引入不可判定性的扩展保持警惕:)我认为主要的问题是它仍然依赖于函数的调用者知道他们必须使用一些非标准的字符串类型。我认为
show
的主要问题是它与
read
的强耦合人们可能会反对将它们用于漂亮的打印/解析目的;
读取的默认行为。show=id
这似乎就是双引号的原因。本,你的答案不准确。
Show
的目的是将Haskell值呈现给字符串。没有义务拥有同构的读取/显示实例-请参阅Haskell报告。诚然,同构的读/显示实例是有用的,但它们不是必须的。如果需要序列化,请考虑使用<代码>二进制< /代码>。这是正确的,但是给定标准库实例(以及由<代码>派生的< /代码>产生的所有)都使用Haskell语法,很明显,对于显示< < /代码>的预期观众-生成的字符串是Haskell程序员。它不是为了“格式化此值文本以便我可以向用户显示它”。谢谢,我不知道这个类。