tags:

views:

198

answers:

3

I'd like a variant of show (let's call it label) that acts just like show, except that it doesn't wrap Strings in " " or Chars in ' '. Examples:

> label 5
"5"
> label "hello"
"hello"
> label 'c'
"c"

I tried implementing this manually, but I ran into some walls. Here is what I tried:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
module Label where

class (Show a) => Label a where
    label :: a -> String

instance Label [Char] where
    label str = str

instance Label Char where
    label c = [c]

-- Default case
instance Show a => Label a where
    label x = show x

However, because the default case's class overlaps instance Label [Char] and instance Label Char, those types don't work with the label function.

Is there a library function that provides this functionality? If not, is there a workaround to get the above code to work?

+4  A: 

The code above isn't going to work because instances are chosen only based on the "head", that is, the part after the class name. The "context", the stuff before the => such as `Show a' is only examined afterwards. The context can eliminate an instance and produce a compiler error, but not cause the compiler to pick a different instance. Because of this behavior, overlapping instances are a potential ambiguity.

There are compiler extensions that can let you write more complicated instances, but I suspect you're probably best off just writing individual instances of your Label class. What purpose do you have in mind for this? Depending on what you're trying to accomplish, there might be something more special-purpose already out there.

Your example code is pretty simple, though--if you want, simply adding the OverlappingInstances extension should make it work with no further modifications. Using OverlappingInstances causes GHC to tolerate some ambiguity, so long as there's an obvious "most specific" instance. In your code, the two instances with concrete types are as specific as it gets, so there shouldn't be any problems.

Might as well add TypeSynonymInstances while you're at it, for better readability:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Label where

class (Show a) => Label a where
    label :: a -> String

instance Label String where label x = x

instance Label Char where label x = [x]

instance (Show a) => Label a where label = show
camccann
Actually the way instances are chosen only based on the "head" has more to do with `UndecidableInstances` than with `OverlappingInstances` to my eye. And indeed, `UndecidableInstances` is a worrying thing to use, as it gives up some of the safety afforded by the type checker; `OverlappingInstances`, in contrast, doesn't lead to any great problems. Since the OP already uses U.I., adding O.I. is no big deal.
Michał Marczyk
@Michał Marczyk: I'd be inclined to say just the opposite, actually--overlapping can produce strange and unintuitive errors sometimes (instances that can't possibly work but no errors until you use them in a certain way, polymorphism causing ambiguity, etc.), whereas the worst undecidability can do is make GHC crash. In the latter case, it's at least *immediately* obvious that the code is incorrect; and GHC's termination checker isn't very clever, so many obviously-terminating instances require `UndecidableInstances` to compile.
camccann
In the end, both extensions are kludges for lack of expressivity in the type system, better avoided when possible, but of the two `OverlappingInstances` has the greater potential for causing subtle, unexpected errors. Hopefully, at some point new language extensions will render both obsolete.
camccann
@camccann: Thanks for the thoughtful rebuttal -- I retract my earlier comment. And of course I join you in hoping for a kludgeless future!
Michał Marczyk
@Michał Marczyk: I used to see things the way your previous comment does, but slowly changed my mind after spending some time doing type metaprogramming and noticing what caused more headaches. :) Type families look to be a step in the right direction--adding closed type families and something like Meacham's class alias proposal would handle almost everything but full-blown type-level programs, I think.
camccann
+1  A: 

There's an OverlappingInstances language extension which will make this work.

Michał Marczyk
A: 

Not really what you want, since it adds an extra constraint to the type (Typeable) but this is how you could do it generically:

Data.Generics> (show `extQ` (id :: String -> String) `extQ` ((:[]) :: Char -> String)) 1

"1"

Data.Generics> (show `extQ` (id :: String -> String) `extQ` ((:[]) :: Char -> String)) "hello"

"hello"

Data.Generics> (show `extQ` (id :: String -> String) `extQ` ((:[]) :: Char -> String)) 'c'

"c"

Data.Generics> (show `extQ` (id :: String -> String) `extQ` ((:[]) :: Char -> String)) ['f','l']

"fl"

Data.Generics> :t (show `extQ` (id :: String -> String) `extQ` ((:[]) :: Char -> String))

(show `extQ` (id :: String -> String) `extQ` ((:[]) :: Char -> String)) :: (Show a, Typeable a) => a -> String

Phyx