Haskell Dynamic / TypeRep:在不知道完整类型的情况下提取类型类的实例?

时间:2016-07-22 08:16:21

标签: haskell dynamic casting

如果我有类型的TypeRep或包含该类型实例的Dynamic,有什么方法可以找到已知类型类的相应实例,以便我可以调用函数在该类型类上,无需知道有问题的完整类型?如果没有这样的功能,是否有理由不可能,或者它没有被实现?

或者,是否有一种方法(可能使用模板haskell)运行带有类型类实例的所有类型的生成列表,以便我可以对每个类型执行动态转换并检查结果?

我要做的是实现EqShow的版本,它实际显示Dynamic的数据(只要有可能),以便更简单,更一般地编写有用的单元测试;我并不真的需要高性能,所以使用贯穿所有可能性的生成代码是可以接受的。

1 个答案:

答案 0 :(得分:3)

无法检查类型是否是运行时的某个实例。其中一个原因是导入模块可能会带来新的实例,这会改变函数的结果(这对Haskell来说非常糟糕)。

您可以查看已知类型的列表。我们的想法是将类型与该类型的实例一起存储在GADT中,并匹配它以获取所需的实例。我不确定你想要什么,但我认为它是这样的:

data EqShow where
  JustEq :: (Typeable a, Eq a)         => Proxy a -> EqShow
  EqShow :: (Typeable a, Eq a, Show a) => Proxy a -> EqShow

只有Eq的类型以及同时包含EqShow的类型的版本。我们的想法是,如果类型匹配,我们可以匹配这些,并使用Eq实例来检查它们是否相等。如果Show实例可用,我们也可以显示结果。为了存储多个EqShows我已经使用了哈希映射,因此我们可以查找类型。这是完整的代码:

{-# LANGUAGE GADTs #-}
import           Data.Dynamic
import           Data.Typeable
import           Data.HashMap.Lazy (HashMap)
import qualified Data.HashMap.Lazy as HM

data EqShow where
  JustEq :: (Typeable a, Eq a)         => Proxy a -> EqShow
  EqShow :: (Typeable a, Eq a, Show a) => Proxy a -> EqShow

justEq :: (Typeable a, Eq a) => Proxy a -> (TypeRep, EqShow)
justEq p = (typeRep p, JustEq p)

eqShow :: (Typeable a, Eq a, Show a) => Proxy a -> (TypeRep, EqShow)
eqShow p = (typeRep p, EqShow p)

-- | Different outcomes of testing equality.
data Result
  = DifferentType TypeRep TypeRep
  | NotEq TypeRep (Maybe (String, String))
  | IsEq TypeRep (Maybe String)
  | UnknownType TypeRep
  deriving Show

-- | Check if two Dynamics are equal. Show them if possible
dynEq :: HashMap TypeRep EqShow -> Dynamic -> Dynamic -> Result
dynEq hm da db
  | ta /= tb  = DifferentType ta tb
  | otherwise =
      case HM.lookup ta hm of
        Just (EqShow p) -> checkEqShow p (fromDynamic da) (fromDynamic db)
        Just (JustEq p) -> checkJustEq p (fromDynamic da) (fromDynamic db)
        Nothing         -> UnknownType ta
  where
    ta = dynTypeRep da
    tb = dynTypeRep db

    -- Check if two results are equal and display them.
    checkEqShow :: (Typeable a, Eq a, Show a) => Proxy a -> Maybe a -> Maybe a -> Result
    checkEqShow _ (Just a) (Just b)
      | a == b    = IsEq ta (Just (show a))
      | otherwise = NotEq ta (Just (show a, show b))
    checkEqShow _ _ _ = UnknownType ta

    -- Check if two results are equal without displaying them.
    checkJustEq :: (Typeable a, Eq a) => Proxy a -> Maybe a -> Maybe a -> Result
    checkJustEq p (Just a) (Just b)
      | a == b    = IsEq ta Nothing
      | otherwise = NotEq ta Nothing
    checkJustEq p _ _ = UnknownType ta

您可以列出已知类型:

knownEqShows :: HashMap TypeRep EqShow
knownEqShows = HM.fromList
  [ eqShow (Proxy :: Proxy Int)
  , eqShow (Proxy :: Proxy Char)
  ]

并检查它们:

> let a = toDyn 'a'
> let b = toDyn 'b'
> let c = toDyn (1 :: Int)
> dynEq knownEqShows a a
IsEq Char (Just "'a'")
> dynEq knownEqShows a b
NotEq Char (Just ("'a'","'b'"))
> dynEq knownEqShows a c
DifferentType Char Int

使用模板haskell生成已知的EqShow将很困难。您可以为没有变量的类型制作版本(DoubleChar等)但是如果您有变量(例如Maybe a),那么您将无法要将它存储在EqShow中,您必须编写它的所有版本(Maybe IntMaybe (Maybe Double)等),但其中包含无限数量。

当然,更容易使用Dynamic而不是使用另一个包装器(可能):

data EqShowDynamic where
  JustEqD :: (Typeable a, Eq a)         => a -> EqShowDynamic
  EqShowD :: (Typeable a, Eq a, Show a) => a -> EqShowDynamic

所以EqShow实例已经存在。