我有:
class C a where
anyC :: a
data D = D String|AnyD deriving (Eq, Show, Data)
instance C D where
anyC = AnyD
我希望编写将创建:: C a => a
的函数D someString
。我会致电anyC
并获取a
AnyD
。下一步是从D someString
创建AnyD
。在大多数现代语言中,它很容易。对于Haskell,我找到this tutorial并看到fromConstrB
。所以,目标似乎可行,但是......我遇到问题,文章看起来错了:我不能用Chris的方式构造任何东西,因为构造函数参数必须是代数类型,像fromConstr (toConstr (1 :: Int))
这样的代码返回错误Exception: Data.Data.constrIndex is not supported for Prelude.Int, as it is not an algebraic data type.
。同样适用于String
,因此我无法将someString
传递给构造函数!如何解决它?
答案 0 :(得分:6)
具有类型
的定义foo :: C a => a
承诺提供任何类型a
的值,只要它在类C
中。 foo
的用户可以选择a
,foo
本身无法选择特定类型。
使用您的设置,唯一可能的(非错误)定义是
foo = anyC
如果您想返回D "hello"
,则此类型为D
。如果需要,可以使用该类型:
bar :: D
bar = D "hello"
另请注意,如果需要,您也可以更改实例:
instance C D where
anyC = D "hello"
关于
在大多数现代语言中,这很容易。
事实并非如此。例如,在Java中,模糊的等价物可能是
// the typeclass, roughly
interface C<A> {
A anyC();
}
class D implements C<D> { ... }
<A extends C<A>> A foo() {
return new D(...);
}
Java也不会接受。除了其他问题,foo()
承诺返回调用者选择的任何A
,它无法选择返回D
。
Haskell类型变量非常粗略地对应于Java的泛型类型参数。您可能会考虑使用Java和其他OOP语言中存在的子类型,但Haskell不会使用它。
如果您想要一个fromConstrB
示例,可以试试这个:
{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables, TypeOperators, GADTs #-}
module FromConstr where
import Data.Data
data D = D String | AnyD deriving (Show, Data)
foo :: D
foo = fromConstrB field ctor
where
ctor :: Constr
ctor = toConstr (D "aaa")
field :: forall a. Data a => a
field = case eqT :: Maybe (a :~: String) of
Just Refl -> "new string"
Nothing -> error "trying to fill a non-string field"
foo
评估为D "new string"
。这可以扩展,以便field
通过嵌套case eqT
来覆盖更多类型(如果构造函数需要更多),以便我们检查所有需要的类型。
另请注意fromConstrB
是有限的,因为如果我们的构造函数有两个具有相同类型的字段,我们无法用不同的值填充字段。为此,我认为我们需要诉诸更复杂的fromConstrM
。
这是一个方便的辅助功能。它需要一个Constr
,一个“无类型”参数列表(Dynamic
在运行时进行所有类型检查),并尝试构建一个将构造函数应用于给定参数的值。
applyConstr :: Data a => Constr -> [Dynamic] -> Maybe a
applyConstr ctor args = let
nextField :: forall d. Data d => StateT [Dynamic] Maybe d
nextField = do
as <- get
case as of
[] -> lift Nothing -- too few arguments
(a:rest) -> do
put rest
case fromDynamic a of
Nothing -> lift Nothing -- runtime type mismatch
Just x -> return x
in case runStateT (fromConstrM nextField ctor) args of
Just (x, []) -> Just x
_ -> Nothing -- runtime type error or too few / too many arguments
例如,您可以按如下方式使用它:
bar :: D
bar = case applyConstr (toConstr (D "aaa")) [toDyn "hello"] of
Just x -> x
Nothing -> error "runtime type mismatch"
如果构造函数有更多参数,则只需要使列表更长,如[toDyn "string", toDyn (42::Int), toDyn True]
中所示。 toDyn
函数将类型值转换为“无类型”值,以便它们可以一起存储在同一列表中,并传递给applyConstr
。稍后applyConstr
将测试(在运行时)此列表是否具有确切长度,并具有正确类型的值。