Peter Hall
Peter Hall

Reputation: 58835

Is there a polymorphic `toString` function that doesn't add quotes?

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

Answers (4)

Ben
Ben

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

Mathnerd314
Mathnerd314

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

Cetin Sert
Cetin Sert

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

Tom Crockett
Tom Crockett

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

Related Questions