Reputation: 58835
In most OO languages that I'm familiar with, the toString
method of a String
is actually just the identity function. But in Haskell show
adds double quotes.
So if I write a function something like this
f :: Show a => [a] -> String
f = concat . map show
it works as expected for numbers
f [0,1,2,3] -- "0123"
but Strings end up with extra quotes
f ["one", "two", "three"] -- "\"one\"\"two\"\"three\""
when I really want "onetwothree"
.
If I wanted to write f
polymorphically, is there a way to do it with only a Show
constraint, and without overriding the Show instance for String (if that's even possible).
The best I can come up with is to create my own type class:
class (Show a) => ToString a where
toString = show
and add an instance for everything?
instance ToString String where toString = id
instance ToString Char where toString = pure
instance ToString Int
instance ToString Maybe
...etc
Upvotes: 8
Views: 1356
Reputation: 71590
I think the root cause of your problem is that show
isn't really renderToText
. It's supposed to produce text that you could paste into Haskell code to get the same value, or convert back to the same value using read
.
For that purpose, show "foo" = "foo"
wouldn't work, because show "1" = "1"
and show 1 = "1"
, which loses information.
The operation you want to be able to apply to "foo"
to get "foo"
and to 1
to get "1"
is something other than show
. show
just isn't a Java-esque toString
.
When I've needed this before, I have indeed made my own new type class and made a bunch of things instances of it, and then used that rather than Show
. Most of the instances were implemented with show
, but String
wasn't the only one I wanted to customise so the separate type class wasn't completely wasted. In practice, I found there were only a handful of types that I actually needed the instance for, and it was pretty trivial to add them as I got compile errors.
Upvotes: 8
Reputation: 231
The Pretty
class and its corresponding type Doc
have the needed behavior for Show. Your link shows a different use case, however; maybe you could edit the question?
Upvotes: 5
Reputation: 4600
You could use newtype
with 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
output:
hello world
The double quotes in the normal case are actually useful when reading a shown string back in the context of larger expression as they clearly delimit shown string values from values of other shown types:
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
Upvotes: 2
Reputation: 31619
You could do this:
{-# 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"
Note that this is probably a terrible idea.
Upvotes: 3