Reputation: 6200
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?
Upvotes: 6
Views: 655
Reputation: 116139
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
Upvotes: 5
Reputation: 48580
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
Upvotes: 5
Reputation: 32309
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).
Upvotes: 5