无法正确定义GADT定义的通用类型转换

时间:2013-11-20 07:32:17

标签: haskell ghc gadt

我已经定义了一个可以包含任何内容的通用数据类型(当前实现并不是完全没有任何东西)!

这是(完整代码):

{-#LANGUAGE NoMonomorphismRestriction#-}
{-#LANGUAGE GADTs#-}
{-#LANGUAGE StandaloneDeriving#-}

data AnyT where
    Any :: (Show a, Read a) => a -> AnyT

readAnyT :: (Read a, Show a) => (String -> a) -> String -> AnyT
readAnyT readFun str = Any $ readFun str

showAnyT :: AnyT -> String
showAnyT (Any thing) = show thing

deriving instance Show AnyT --Just for convinience!

a = [Any "Hahaha", Any 123]

我可以在控制台中玩它:

*Main> a
[Any "Hahaha",Any 123]
it :: [AnyT]
*Main> readAnyT (read::String->Float) "134"
Any 134.0
it :: AnyT
*Main> showAnyT $ Any 125
"125"
it :: String

嗯,我有它,但我需要以某种方式处理它。例如,让我们定义转换函数(函数定义,添加到以前的代码):

toAnyT :: (Show a, Read a) => a -> AnyT -- Rather useless
toAnyT a = Any a

fromAny :: AnyT -> a
fromAny (Any thing) = thing

有问题!以前代码中的 fromAny 定义不正确!我不知道如何纠正它。我在GHCi中收到错误:

2.hs:18:23:
    Could not deduce (a ~ a1)
    from the context (Show a1, Read a1)
      bound by a pattern with constructor
                 Any :: forall a. (Show a, Read a) => a -> AnyT,
               in an equation for `fromAny'
      at 2.hs:18:10-18
      `a' is a rigid type variable bound by
          the type signature for fromAny :: AnyT -> a at 2.hs:17:12
      `a1' is a rigid type variable bound by
           a pattern with constructor
             Any :: forall a. (Show a, Read a) => a -> AnyT,
           in an equation for `fromAny'
           at 2.hs:18:10
    In the expression: thing
    In an equation for `fromAny': fromAny (Any thing) = thing
Failed, modules loaded: none.

我尝试了其他一些方法来解决错误。


我对此有一个糟糕解决方案:通过 showAnyT 读取(替换以前的函数定义)定义必要的函数:

toAnyT :: (Show a, Read a) => a -> AnyT -- Rather useless
toAnyT a = Any a

fromAny :: Read a => AnyT -> a
fromAny thing = read (showAnyT thing)

是的,这是有效的。我可以玩它:

*Main> fromAny $ Any 1352 ::Float
1352.0
it :: Float
*Main> fromAny $ Any 1352 ::Int
1352
it :: Int
*Main> fromAny $ Any "Haha" ::String
"Haha"
it :: String

但我认为这很糟糕,因为它使用字符串进行转换。

你能帮我找到整洁有效的解决方案吗?

3 个答案:

答案 0 :(得分:1)

你所拥有的是Existential type。如果您遵循该链接,则会发现在此模式中,使用容器类型中的“数据”的唯一方法是使用类型类。

在您当前的示例中,您提到a应该有ReadShow个实例,这意味着只能在a上使用这些类型类中的函数否则如果你想在a上支持更多的操作,那么应该用所需的类型类约束它。

这样想:你可以把任何东西放在一个盒子里。现在,当您从该框中提取某些内容时,您无法指定从中获取的内容,因为您可以在其中放置任何内容。现在,如果你说你可以在这个盒子里放任何可食用的东西,那么你可以肯定,当你从这个盒子里挑选东西时它就可以食用了。

答案 1 :(得分:1)

您正在使用GADT创建存在数据类型。构造函数中的类型a已存在,但无法恢复它。您可以使用的唯一信息是它有ShowRead个实例。确切的类型被遗忘,因为这是构造函数的类型指示类型系统要做的事情。 “确保这种类型具有适当的实例,然后忘记它是什么。”

顺便说一下,你错过了一个功能:

readLike :: String -> AnyT -> AnyT
readLike s (Any a) = Any $ read s `asTypeOf` a

在模式匹配的上下文中,编译器知道a具有的任何类型,都有Read实例,并且它可以应用该实例。即使它不确定a是什么类型。但它能用它做的就是显示它,或者读取与它相同类型的字符串。

答案 2 :(得分:1)

首先是免责声明:我不知道您要解决的问题的整个背景,但我得到的第一印象是,这种使用存在感是错误的工具,你可能会尝试实现一些在面向对象语言中常见的代码模式,但不适合Haskell。

也就是说,像你这里存在的存在类型通常就像黑洞一样,一旦你把东西放入其中,类型信息就会永远丢失,你不能把它的价值转回原来的类型。但是,您可以通过类型类操作存在性值(正如您使用ShowRead所做的那样),因此您可以使用类型类Typeable来保留原始类型信息:

import Data.Typeable

data AnyT where
    Any :: (Show a, Read a, Typeable a) => a -> AnyT

现在你可以实现你拥有的所有功能,只要你也为它们添加新约束:

readAnyT :: (Read a, Show a, Typeable a) => (String -> a) -> String -> AnyT
readAnyT readFun str = Any $ readFun str

showAnyT :: AnyT -> String
showAnyT (Any thing) = show thing

toAnyT :: (Show a, Read a, Typeable a) => a -> AnyT -- Rather useless
toAnyT a = Any a

fromAny可以实现为返回Maybe a(因为您无法确定您获得的值是否属于您期望的类型)。

fromAny :: Typeable a => AnyT -> Maybe a
fromAny (Any thing) = cast thing