Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining a function a -> String, which works for types without Show?

Tags:

class

haskell

I'd like to define a function which can "show" values of any type, with special behavior for types which actually do define a Show instance:

magicShowCast :: ?

debugShow :: a -> String
debugShow x = case magicShowCast x of
    Just x' -> show x'
    Nothing -> "<unprintable>"

This would be used to add more detailed information to error messages when something goes wrong:

-- needs to work on non-Showable types
assertEq :: Eq a => a -> a -> IO ()
assertEq x y = when (x /= y)
    (throwIO (AssertionFailed (debugShow x) (debugShow y)))

data CanShow = CanShow1
             | CanShow 2
    deriving (Eq, Show)

data NoShow = NoShow1
            | NoShow2
    deriving (Eq)

-- AssertionFailed "CanShow1" "CanShow2"
assertEq CanShow1 CanShow2

-- AssertionFailed "<unprintable>" "<unprintable>"
assertEq NoShow1 NoShow2

Is there any way to do this? I tried using various combinations of GADTs, existential types, and template haskell, but either these aren't enough or I can't figure out how to apply them properly.

like image 539
John Millikin Avatar asked Aug 10 '11 03:08

John Millikin


People also ask

What does () => void mean TypeScript?

The syntax (a: string) => void means “a function with one parameter, named a , of type string, that doesn't have a return value”. Just like with function declarations, if a parameter type isn't specified, it's implicitly any .

What is def Myfunc () in Python?

There are many built-in functions in python but we can create our own functions. In python, a function is defined using the def keyword. >>> def myfunc(): print("Python functions are easy to learn.")>>> myfunc() #calling function. 'Python functions are easy to learn.

What does type () return Python?

Python type() The type() function either returns the type of the object or returns a new type object based on the arguments passed.


1 Answers

The real answer: You can't. Haskell intentionally doesn't define a generic "serialize to string" function, and being able to do so without some type class constraint would violate parametricity all over town. Dreadful, just dreadful.

If you don't see why this poses a problem, consider the following type signature:

something :: (a, a) -> b -> a

How would you implement this function? The generic type means it has to be either const . fst or const . snd, right? Hmm.

something (x,y) z = if debugShow z == debugShow y then y else x
> something ('a', 'b') ()
'a'
> something ('a', 'b') 'b'
'b'

Oooooooops! So much for being able to reason about your program in any sane way. That's it, show's over, go home, it was fun while it lasted.


The terrible, no good, unwise answer: Sure, if you don't mind shamelessly cheating. Did I mention that example above was an actual GHCi session? Ha, ha.

import Control.Exception
import Control.Monad
import GHC.Vacuum

debugShow :: a -> String
debugShow = show . nameGraph . vacuumLazy

assertEq :: Eq a => a -> a -> IO ()
assertEq x y = when (x /= y) . throwIO . AssertionFailed $ 
    unlines ["assertEq failed:", '\t':debugShow x, "=/=", '\t':debugShow y]

data NoShow = NoShow1
            | NoShow2
    deriving (Eq)
> assertEq NoShow1 NoShow2
*** Exception: assertEq failed:
    [("|0",["NoShow1|1"]),("NoShow1|1",[])]
=/=
    [("|0",["NoShow2|1"]),("NoShow2|1",[])]

Oh. Ok. That looks like a fantastic idea, doesn't it.

Anyway, this doesn't quite give you what you want, since there's no obvious way to fall back to a proper Show instance when available. On the other hand, this lets you do a lot more than show can do, so perhaps it's a wash.

Seriously, though. Don't do this in actual, working code. Ok, error reporting, debugging, logging... that makes sense. But otherwise, it's probably very ill-advised.

like image 148
C. A. McCann Avatar answered Nov 12 '22 19:11

C. A. McCann