fromConstrB或其他有用的东西

时间:2017-12-02 08:57:59

标签: haskell

我有:

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传递给构造函数!如何解决它?

1 个答案:

答案 0 :(得分:6)

具有类型

的定义
foo :: C a => a

承诺提供任何类型a的值,只要它在类C中。 foo用户可以选择afoo本身无法选择特定类型。

使用您的设置,唯一可能的(非错误)定义是

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将测试(在运行时)此列表是否具有确切长度,并具有正确类型的值。