我已经定义了一个可以包含任何内容的通用数据类型(当前实现并不是完全没有任何东西)!
这是(完整代码):
{-#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
但我认为这很糟糕,因为它使用字符串进行转换。
你能帮我找到整洁有效的解决方案吗?
答案 0 :(得分:1)
你所拥有的是Existential type。如果您遵循该链接,则会发现在此模式中,使用容器类型中的“数据”的唯一方法是使用类型类。
在您当前的示例中,您提到a
应该有Read
和Show
个实例,这意味着只能在a
上使用这些类型类中的函数否则如果你想在a
上支持更多的操作,那么应该用所需的类型类约束它。
这样想:你可以把任何东西放在一个盒子里。现在,当您从该框中提取某些内容时,您无法指定从中获取的内容,因为您可以在其中放置任何内容。现在,如果你说你可以在这个盒子里放任何可食用的东西,那么你可以肯定,当你从这个盒子里挑选东西时它就可以食用了。
答案 1 :(得分:1)
您正在使用GADT创建存在数据类型。构造函数中的类型a
已存在,但无法恢复它。您可以使用的唯一信息是它有Show
和Read
个实例。确切的类型被遗忘,因为这是构造函数的类型指示类型系统要做的事情。 “确保这种类型具有适当的实例,然后忘记它是什么。”
顺便说一下,你错过了一个功能:
readLike :: String -> AnyT -> AnyT
readLike s (Any a) = Any $ read s `asTypeOf` a
在模式匹配的上下文中,编译器知道a
具有的任何类型,都有Read
实例,并且它可以应用该实例。即使它不确定a
是什么类型。但它能用它做的就是显示它,或者读取与它相同类型的字符串。
答案 2 :(得分:1)
首先是免责声明:我不知道您要解决的问题的整个背景,但我得到的第一印象是,这种使用存在感是错误的工具,你可能会尝试实现一些在面向对象语言中常见的代码模式,但不适合Haskell。
也就是说,像你这里存在的存在类型通常就像黑洞一样,一旦你把东西放入其中,类型信息就会永远丢失,你不能把它的价值转回原来的类型。但是,您可以通过类型类操作存在性值(正如您使用Show
和Read
所做的那样),因此您可以使用类型类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