I tried to write a variation on show
that treats strings differently from other instances of Show
, by not including the " and returning the string directly. But I don't know how to do it. Pattern matching? Guards? I couldn't find anything about that in any documentation.
Here is what I tried, which doesn't compile:
show_ :: Show a => a -> String
show_ (x :: String) = x
show_ x = show x
If possible, you should wrap your values of type String
up in a newtype
as @wowofbob suggests.
However, sometimes this isn't feasible, in which case there are two general approaches to making something recognise String
specifically.
The first way, which is the natural Haskell approach, is to use a type class just like Show
to get different behaviour for different types. So you might write
class Show_ a where
show_ :: a -> String
and then
instance Show_ String where
show_ x = x
instance Show_ Int where
show_ x = show x
and so on for any other type you want to use. This has the disadvantage that you need to explicitly write out Show_
instances for all the types you want.
@AndrewC shows how you can cut each instance down to a single line, but you'll still have to list them all explicitly. You can in theory work around this, as detailed in this question, but it's not pleasant.
The second option is to get true runtime type information with the Typeable
class, which is quite short and simple in this particular situation:
import Data.Typeable
[...]
show_ :: (Typeable a, Show a) => a -> String
show_ x =
case cast x :: Maybe String of
Just s -> s
Nothing -> show x
This is not a natural Haskell-ish approach because it means callers can't tell much about what the function will do from the type.
Type classes in general give constrained polymorphism in the sense that the only variations in behaviour of a particular function must come from the variations in the relevant type class instances. The Show_
class gives some indication what it's about from its name, and it might be documented.
However Typeable
is a very general class. You are delegating everything to the specific function you are calling; a function with a Typeable
constraint might have completely different implementations for lots of different concrete types.
Finally, a further elaboration on the Typeable
solution which gets closer to your original code is to use a couple of extensions:
{-# LANGUAGE ViewPatterns, ScopedTypeVariables #-}
import Data.Typeable
[...]
show_ :: (Typeable a, Show a) => a -> String
show_ (cast -> Just (s :: String)) = s
show_ x = show x
The use of ViewPatterns
allows us to write the cast
inside a pattern, which may fit in more nicely with more complicated examples. In fact we can omit the :: String
type constraint because the body of this cases forces s
to be the result type of show_
, i.e. String
, anyway. But that's a little obscure so I think it's better to be explicit.