How to convert arbitrary type to string, without adding extra quotes to strings?
Asked Answered
P

3

6

I want to define a function which converts to strings, like the following 'toString':

toString 1 = "1"
toString True = "True"
toString "1" = "1"

Note that 'show' does not do this. By contrast it does the following:

show 1 = "1"
show True = "True"
show "1" = "\"1\""

That is, it adds extra quotes around strings. In this case I don't want to add extra quotes if I already have a string.

I'm considering using something like:

import Data.Typeable

toString a :: (Show a) => a -> String
toString a
  | typeOf a == typeOf "" = a
  | otherwise = show a

Are there any pitfalls in doing such a weird type-based conditional? Is there some built-in Haskell function that would be better to use instead?

Predispose answered 12/12, 2016 at 7:10 Comment(0)
V
5

This sort of ad-hoc polymorphism is permitted through type-classes. However, they will have to be overlapping since you want a catch all case:

{-# LANGUAGE FlexibleInstances, UndecideableInstances #-}

class Print a where
  makeString :: a -> String

instance {-# OVERLAPPING #-} Print String where
  makeString s = s

instance Show a => Print a where
  makeString x = show x

Then, your function is makeString :: Print a => a -> String and it has an instance for everything that has a Show instance. To have the second instance, you need FlexibleInstances (the instance head is not of the usual form) and UndecideableInstances (since the constraint is as general as the instance head, GHC can't be sure that it won't fall into an infinite loop trying to solve these constraints).

Vestiary answered 12/12, 2016 at 7:30 Comment(0)
F
5

If you want something like Alec's approach without overlapping instances, you can get it with a type family.

{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, ScopedTypeVariables, UndecidableInstances, FlexibleInstances, DataKinds, ... whatever else GHC tells you it needs #-}

import Data.Text (Text, unpack)
import Data.Proxy

class Print a where
  makeString :: a -> String

data Name = NString | NText | NShow
type family Choose a where
  Choose [Char] = 'NString
  Choose Text = 'NText
  Choose _ = 'NShow

class Print' (n :: Name) a where
  makeString' :: proxy n -> a -> String

instance (Choose a ~ n, Print' n a) => Print a where
  makeString = makeString' (Proxy :: Proxy n)

instance a ~ String => Print' 'NString a where
  makeString' _ = id

instance a ~ Text => Print' 'NText a where
  makeString' _ = unpack

instance Show a => Print' 'NShow a where
  makeString' _ = show
Father answered 12/12, 2016 at 9:14 Comment(0)
S
5

Expanding the OP solution attempt into a working one:

import Data.Typeable

toString :: (Show a, Typeable a) => a -> String
toString x = case cast x of
   Just y  -> y
   Nothing -> show x
Strategic answered 12/12, 2016 at 14:42 Comment(1)
This only works if x is String. For example, if x is Text, then it still adds the quoteArchiplasm

© 2022 - 2024 — McMap. All rights reserved.