不,你不能这样做。总而言之,您正在尝试编写依赖类型的函数,而 Haskell 不是依赖类型的语言。你不能将你的TypeRep
价值提升到一个真正的类型,所以没有办法写下你想要的函数的类型。为了更详细地解释这一点,我首先要说明为什么你对类型的表述方式randList
没有真正意义。然后,我将解释为什么你不能做你想做的事。最后,我将简要提及一些关于实际操作的想法。
存在主义
你的类型签名randList
不能代表你想要的意思。记住 Haskell 中的所有类型变量都是普遍量化的,它读取
randList :: forall a. Typeable a => [Dynamic] -> IO [a]
因此,我有权称它为,比如说,randList dyns :: IO [Int]
我想要的任何地方;我必须能够为所有人 a
提供返回值,而不仅仅是为某些人 a
提供。把它想象成一个游戏,它是调用者可以选择a
的,而不是函数本身。您想说的(这不是有效的 Haskell 语法,尽管您可以使用存在数据类型1将其转换为有效的 Haskell )更像是
randList :: [Dynamic] -> (exists a. Typeable a => IO [a])
这保证列表的元素是某种类型a
的,它是 的一个实例Typeable
,但不一定是任何这种类型。但即使这样,你也会遇到两个问题。首先,即使你可以构建这样一个列表,你能用它做什么?其次,事实证明,你甚至不能一开始就构建它。
既然您对存在列表元素的所有了解都是它们是 的实例Typeable
,那么您可以用它们做什么呢? 查看文档,我们看到只有两个函数2采用以下实例Typeable
:
typeOf
因此,您对列表中元素类型的所有了解就是您可以调用cast
它们。由于我们永远无法对它们有用地做任何其他事情,我们的存在主义也可能是(同样,不是有效的 Haskell)
randList :: [Dynamic] -> IO [(TypeRep, forall b. Typeable b => Maybe b)]
typeOf
如果我们对列表的每个元素应用和cast
处理,存储结果并丢弃现在无用的存在类型的原始值,这就是我们得到的结果。显然,TypeRep
这个列表的一部分没有用。列表的后半部分也不是。由于我们回到了一个普遍量化的类型,调用者randList
再次有权请求他们为他们选择的任何(可键入的)获取 a Maybe Int
、 aMaybe Bool
或 a 。(事实上,它们比以前更强大,因为它们可以将列表中的不同元素实例化为不同的类型。)但是除非它们已经知道,否则它们无法确定要转换的类型——你仍然知道丢失了您试图保留的类型信息。Maybe b
b
即使抛开它们没有用的事实,您也根本无法在这里构造所需的存在类型。当您尝试返回存在类型列表 ( return $ dynListToList dl
) 时会出现错误。你打电话的具体类型是什么dynListToList
?回想一下dynListToList :: forall a. Typeable a => [Dynamic] -> [a]
;因此,负责randList
挑选要使用的。但它不知道该选哪个;再次,这就是问题的根源!因此,您尝试返回的类型未指定,因此不明确。3 a
dynListToList
a
依赖类型
好的,那么什么会使这种存在主义有用(并且可能)?好吧,我们实际上有更多的信息:我们不仅知道有一些 a
,我们还有它的TypeRep
。所以也许我们可以把它打包:
randList :: [Dynamic] -> (exists a. Typeable a => IO (TypeRep,[a]))
但是,这还不够好;theTypeRep
和 the[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 中,值依赖于其他值是完全可以接受的。这就是函数。例如, valuehead "abc"
取决于 value "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 -> * -> *
: 一个类型,其第一个参数必须是 type 的值Nat
。但是我们不能在 Haskell 中编写这种东西。4 支持这一点的语言被称为依赖类型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 的评论:您可能想要从头开始重新考虑您的方法。不过,再一次,因为我还没有看到你的架构,我不能说这意味着什么。如果你能提炼出一个具体的困难,也许那里还有另一个 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
它或多或少是 s 的存在包装器Typeable
,依赖于typeOf
and TypeRep
s 知道何时执行unsafeCoerce
(GHC 使用一些特定于实现的类型 and unsafeCoerce
,但你可以这样做,除了/可能例外),所以不做任何新的事情。并且并不真正期望它的参数 type ;它只是一个包装器。这些功能以及其他类似功能不提供任何额外的功能,而这些功能是 和 所不具备的。(例如,回到一个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 支持将类型提升到种类和种类多态性;请参阅GHC 7.4 用户手册中的第 7.8 节“种类多态性和提升”。这不会使 Haskell 依赖类型化——类似TypeRep -> *
example 的东西仍然没有5——但您将能够使用看起来像值Vec
的非常富有表现力的类型来编写。
5从技术上讲,您现在可以写下看起来具有所需类型的内容:type family ToType :: TypeRep -> *
. 但是,这需要提升kind的类型,而不是type的值;此外,您仍然无法实现它。(至少我不这么认为,我看不出你会怎么做——但我不是这方面的专家。)但在这一点上,我们还很遥远。 TypeRep
TypeRep