定义函数a - >字符串,适用于没有Show的类型?

时间:2011-08-10 03:41:16

标签: class haskell

我想定义一个可以“显示”任何类型值的函数,对于实际定义Show实例的类型具有特殊行为:

magicShowCast :: ?

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

当出现问题时,这将用于向错误消息添加更详细的信息:

-- 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

有没有办法做到这一点?我尝试使用GADT,存在类型和模板haskell的各种组合,但这些都不够,或者我无法弄清楚如何正确应用它们。

3 个答案:

答案 0 :(得分:14)

真正的答案:你做不到。 Haskell故意没有定义通用的“序列化到字符串”函数,并且能够在没有某些类型类约束的情况下这样做会违反整个城镇的参数化。可怕,只是可怕。

如果您不明白为什么会出现问题,请考虑以下类型签名:

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

你将如何实现这个功能?泛型类型意味着它必须是const . fstconst . snd,对吧? HMM。

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

Oooooooops!能够以任何理智的方式推理您的计划。就是这样,show结束了,回家了,它一直很有趣。


可怕的,不好的,不明智的回答:当然,如果你不介意无耻地作弊。我是否提到上面的例子是一个真正的GHCi会话?哈哈哈。

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",[])]

喔。好。这看起来像一个梦幻般的想法,不是吗。

无论如何,这并没有给你你想要的东西,因为没有明显的方法可以回到合适的Show实例。另一方面,this lets you do a lot more than show can do,所以也许是洗漱。

但是,说真的。不要在实际的工作代码中执行此操作。好的,错误报告,调试,记录......这是有道理的。但除此之外,这可能是非常不明智的。

答案 1 :(得分:3)

我刚才在haskell-cafe列表上问过这个问题,专家说没有。以下是一些很好的回应,

第二个提到GHC高级重叠,但我的经验是它没有真正起作用。

针对您的特定问题,我将介绍类型类

class MaybeShow a where mshow :: a -> String

使任何可展示的东西都符合逻辑

instance Show a => MaybeShow a where mshow = show

然后,如果您有固定数量的类型无法显示,请说

instance MaybeShow NotShowableA where mshow _ = "<unprintable>"

当然你可以抽象一点,

class NotShowable a
instance NotShowable a => MaybeShow a where mshow _ = "<unprintable>"
instance NotShowable NotShowableA -- etc.

答案 2 :(得分:1)

你应该不能。实现类型类的最简单方法是将它们编译成额外的参数

 foo :: Show s => a -> s

变成

 foo :: show -> a -> s

程序只是将类型类实例(如C ++中的v-tables)作为普通数据。这就是为什么你可以简单地使用看起来不像OO语言中的多个调度的东西,但可以调出返回类型。

问题在于签名

 foo :: a -> String
如果Showa,则

无法获得 class MyShow a where myShow :: a -> String instance (Show a) => MyShow a where myShow = show instance MyShow a where myShow = ... 的实施。

你可能能够在特定的实现中使用正确的语言扩展(重叠实例等),但我还没有尝试过它

 instance (a' ~ a, Show a') => MyShow a

可能有用的一个技巧是启用类型系列。它可以让你编写像

这样的代码
{{1}}

有时可以帮助您通过编译器获取代码,它认为看起来没问题。