不,你不能这样做。总而言之,您正在尝试编写依赖类型的函数,而 Haskell 不是依赖类型的语言。您无法将 TypeRep 值提升为真实类型,因此无法写下所需函数的类型。为了更详细地解释这一点,我首先要说明为什么您对randList 类型的表述方式并不真正有意义。然后,我将解释为什么你不能做你想做的事。最后,我将简要提及一些关于实际操作的想法。
存在主义
randList 的类型签名不能代表您想要的意思。记住 Haskell 中的所有类型变量都是通用量化的,它读取
randList :: forall a. Typeable a => [Dynamic] -> IO [a]
因此,我有权将其称为randList dyns :: IO [Int] 任何我想要的地方;我必须能够为 all a 提供返回值,而不仅仅是为 some a。将其视为一个游戏,调用者可以选择a,而不是函数本身。您想要说的(这不是有效的 Haskell 语法,尽管您可以通过使用存在数据类型将其转换为有效的 Haskell1)更像是 p>
randList :: [Dynamic] -> (exists a. Typeable a => IO [a])
这保证列表的元素是some 类型a,它是Typeable 的一个实例,但不一定any 这种类型。但即使这样,你也会遇到两个问题。首先,即使你可以构建这样一个列表,你能用它做什么?其次,事实证明你一开始就无法构建它。
既然您对存在列表元素的所有了解都是Typeable 的实例,那么您可以用它们做什么呢? Looking at the documentation,我们看到只有两个函数2 采用Typeable 的实例:
因此,关于列表中元素的类型,您所知道的只是您可以在它们上调用typeOf 和cast。由于我们永远无法对它们做任何有用的事情,我们的存在主义也可能是(同样,不是有效的 Haskell)
randList :: [Dynamic] -> IO [(TypeRep, forall b. Typeable b => Maybe b)]
如果我们将typeOf 和cast 应用于列表的每个元素、存储结果并丢弃现在无用的存在类型的原始值,这就是我们得到的结果。显然,此列表中的TypeRep 部分没有用。列表的后半部分也不是。由于我们回到了一个普遍量化的类型,randList 的调用者再次有权要求他们获得一个Maybe Int、一个Maybe Bool 或一个Maybe b 对于任何(可输入的)b他们的选择。 (事实上,他们比以前稍微强大了一点,因为他们可以将列表的不同元素实例化为不同的类型。)但是他们无法弄清楚他们正在转换什么类型从除非他们已经知道 - 您仍然丢失了您试图保留的类型信息。
即使抛开它们没有用的事实,您也无法在这里构造所需的存在类型。当您尝试返回存在类型列表 (return $ dynListToList dl) 时会出现错误。你打电话给dynListToList的具体类型是什么?回想一下dynListToList :: forall a. Typeable a => [Dynamic] -> [a];因此,randList 负责选择 which a dynListToList 将使用。但它不知道选择哪个a;再次,这就是问题的根源!因此,您尝试返回的类型未指定,因此不明确。3
依赖类型
好的,那么使这种存在主义有用(并且可能)?嗯,我们实际上有更多的信息:我们不仅知道一些 a,我们还有它的TypeRep。所以也许我们可以把它打包:
randList :: [Dynamic] -> (exists a. Typeable a => IO (TypeRep,[a]))
不过,这还不够好; TypeRep 和 [a] 根本没有链接。这正是您想要表达的:某种方式来链接TypeRep 和a。
基本上,你的目标是写类似的东西
toType :: TypeRep -> *
这里,*是所有类型中的那种;如果您以前没有见过种类,那么它们就是类型,什么类型是值。 * 对类型进行分类,* -> * 对单参数类型构造函数进行分类等(例如,Int :: *、Maybe :: * -> *、Either :: * -> * -> * 和 Maybe Int :: *。)
有了这个,你可以写(再一次,这段代码不是有效的 Haskell;事实上,它实际上与 Haskell 只是有一点相似之处,因为你无法在 Haskell 的类型系统中编写它或类似的东西):
randList :: [Dynamic] -> (exists (tr :: TypeRep).
Typeable (toType tr) => IO (tr, [toType tr]))
randList dl = do
tr <- randItem $ map dynTypeRep dl
return (tr, dynListToList dl :: [toType tr])
-- In fact, in an ideal world, the `:: [toType tr]` signature would be
-- inferable.
现在,您承诺的事情是正确的:不是存在某种对列表元素进行分类的类型,而是存在一些 TypeRep 以便其对应的类型对列表中的元素进行分类。如果你能做到这一点,你就会被设置。但是在 Haskell 中写 toType :: TypeRep -> * 是完全不可能的:这样做需要依赖类型的语言,因为 toType tr 是一个依赖于值的类型。
这是什么意思?在 Haskell 中,值依赖于其他值是完全可以接受的。这就是函数。例如,值head "abc" 取决于值"abc"。同样,我们有类型构造函数,因此类型依赖于其他类型是可以接受的;考虑Maybe Int,以及它如何依赖于Int。我们甚至可以拥有取决于类型的值!考虑id :: a -> a。这确实是一系列函数:id_Int :: Int -> Int、id_Bool :: Bool -> Bool 等。我们拥有哪一个取决于a 的类型。 (真的,id = \(a :: *) (x :: a) -> x;虽然我们不能用 Haskell 编写这个,但我们可以使用一些语言。)
然而,至关重要的是,我们永远不能拥有依赖于值的类型。我们可能想要这样的东西:想象Vec 7 Int,长度为7 的整数列表类型。这里,Vec :: Nat -> * -> *:一个类型,其第一个参数必须是 Nat 类型的值。但是我们不能在 Haskell 中编写这种东西。4 支持这一点的语言称为 dependently-typed(我们可以像上面那样写 id );示例包括 Coq 和 Agda。 (此类语言通常兼作证明助手,通常用于研究工作而不是编写实际代码。依赖类型很难,使它们对日常编程有用是一个活跃的研究领域。)
因此,在 Haskell 中,我们可以先检查有关类型的所有内容,丢弃所有这些信息,然后编译仅引用值的内容。事实上,这正是 GHC 所做的;因为在 Haskell 中我们永远无法在运行时检查类型,所以 GHC 在编译时擦除所有类型而不改变程序的运行时行为。这就是为什么unsafeCoerce 易于实现(操作上)并且完全不安全的原因:在运行时,它是无操作的,但它取决于类型系统。因此,像toType 这样的东西完全不可能在 Haskell 类型系统中实现。
事实上,正如您所注意到的,您甚至不能写下所需的类型并使用unsafeCoerce。对于某些问题,您可以摆脱它;我们可以写下函数的类型,但只能通过作弊来实现。这正是fromDynamic 的工作原理。但正如我们在上面看到的,在 Haskell 中甚至没有一个很好的类型可以解决这个问题。想象中的toType函数可以让你给程序一个类型,但是你甚至不能写下toType的类型!
现在怎么办?
所以,你不能这样做。你应该做什么?我的猜测是您的整体架构对于 Haskell 来说并不理想,尽管我还没有看到它; Typeable 和 Dynamic 实际上并没有在 Haskell 程序中出现那么多。 (正如他们所说,也许您“说的是带有 Python 口音的 Haskell”。)如果您只有一组有限的数据类型要处理,您也许可以将事物捆绑到一个普通的旧代数数据类型中:
data MyType = MTInt Int | MTBool Bool | MTString String
然后你可以写isMTInt,然后使用filter isMTInt,或者filter (isSameMTAs randomMT)。
虽然我不知道它是什么,但您可能有一种方法可以 unsafeCoerce 解决这个问题。但坦率地说,这不是一个好主意,除非你真的、真的、真的、真的、真的、真的知道你在做什么。即便如此,也可能不是。如果您需要unsafeCoerce,您就会知道,这不仅仅是为了方便。
我真的同意Daniel Wagner's comment:你可能会想从头开始重新考虑你的方法。不过,再一次,因为我还没有看到你的架构,我不能说这意味着什么。如果你能提炼出一个具体的困难,也许还有另一个 Stack Overflow 问题。
1 如下所示:
{-# LANGUAGE ExistentialQuantification #-}
data TypeableList = forall a. Typeable a => TypeableList [a]
randList :: [Dynamic] -> IO TypeableList
但是,由于这段代码无论如何都无法编译,我认为用exists 写出来更清晰。
2 从技术上讲,还有一些其他看起来相关的函数,例如toDyn :: Typeable a => a -> Dynamic 和fromDyn :: Typeable a => Dynamic -> a -> a。然而,Dynamic 或多或少是围绕Typeables 的存在包装,依赖typeOf 和TypeReps 知道何时到unsafeCoerce(GHC 使用一些特定于实现的类型和unsafeCoerce,但是你可以这样做,除了dynApply/dynApp)可能,所以toDyn 没有做任何新的事情。而fromDyn 并不真正期望它的类型为a 的参数;它只是cast 的包装。这些功能以及其他类似功能不提供任何仅typeOf 和cast 无法提供的额外功能。 (例如,返回到Dynamic 对您的问题不是很有用!)
3 要查看实际的错误,您可以尝试编译以下完整的 Haskell 程序:
{-# LANGUAGE ExistentialQuantification #-}
import Data.Dynamic
import Data.Typeable
import Data.Maybe
randItem :: [a] -> IO a
randItem = return . head -- Good enough for a short and non-compiling example
dynListToList :: Typeable a => [Dynamic] -> [a]
dynListToList = mapMaybe fromDynamic
data TypeableList = forall a. Typeable a => TypeableList [a]
randList :: [Dynamic] -> IO TypeableList
randList dl = do
tr <- randItem $ map dynTypeRep dl
return . TypeableList $ dynListToList dl -- Error! Ambiguous type variable.
果然,如果你尝试编译这个,你会得到错误:
SO12273982.hs:17:27:
Ambiguous type variable `a0' in the constraint:
(Typeable a0) arising from a use of `dynListToList'
Probable fix: add a type signature that fixes these type variable(s)
In the second argument of `($)', namely `dynListToList dl'
In a stmt of a 'do' block: return . TypeableList $ dynListToList dl
In the expression:
do { tr <- randItem $ map dynTypeRep dl;
return . TypeableList $ dynListToList dl }
但问题的重点是,您不能“添加修复这些类型变量的类型签名”,因为您不知道自己想要什么类型。
4 主要是。 GHC 7.4 支持将类型提升到种类和种类多态性;见section 7.8, "Kind polymorphism and promotion", in the GHC 7.4 user manual。这不会使 Haskell 依赖类型化——像 TypeRep -> * 这样的例子仍然没有5——但是你可以通过使用非常富有表现力的类型来编写 Vec,看起来 喜欢价值观。
5 从技术上讲,您现在可以写下 看起来 具有所需类型的内容:type family ToType :: TypeRep -> *。但是,这需要提升 kind TypeRep 的 type,而不是 type @ 的 value 987654435@;此外,您仍然无法实现它。 (至少我不这么认为,而且我看不出你会怎么做——但我不是这方面的专家。)但在这一点上,我们还很遥远。