首页 > 解决方案 > 如何解决“无法将类型 'b' 与 ConcreteType 匹配”?

问题描述

架构如下:用户生成一个 Intent,然后由系统捕获。Resolver 会找到一个合适的函数来解决这个意图。有一个索引将意图映射到在这些意图中提到其功能的模块。基本思想是模块接受一个实体并生成另一个实体作为结果。

我尝试了 ExistentialTypes,但我认为我还没有足够的专业知识来使用它们,所以我想知道是否有更简单的方法。

一个实体看起来像这样。

data Entity a = Entity {...}

data DirectionEntity = DirectionEntity {...}

有很多实体。

处理程序如下所示:

handler :: Entity NavigationEntity -> IO (Entity DirectionEntity)

我想在类型级别获得一些信息。一切都很好,直到我想在一个地方拥有一个包含所有这些处理程序的单一数据结构。基本上,我想要一个类似的功能:

solveIntent :: Intent -> Entity a -> IO (Entity b)
solveIntent intent entity = do
  index <- mkIndex
  let m = searchModule index intent
  run m entity

这是标题中的问题:我无法匹配类型:

Expected type: Entity a -> IO (Entity b)
Actual type: Entity NavigationEntity -> IO (Entity DirectionEntity)

任何帮助,将不胜感激。谢谢。

标签: haskell

解决方案


像这样的数据类型Intent不公开类型信息。for的类型与IntentforNavigationEntity -> DirectionEntity的类型相同。因此,结果的类型不能基于 改变,你就有问题了,因为这就是重点。IntentDirectionEntity -> NavigationEntitysearchModuleIntent

退后一步,你有大量的exists a b. Entity a -> IO (Entity b)s 集合。您希望能够选择两个Types — <code>a 和b— 并在您的集合中搜索匹配的函数。这是一项工作Typeable:您需要定义一个类型来保存 each exists a b. Entity a -> IO (Entity b),加上and的Typeable证据,然后应该为您正在搜索的两种类型获取证据。你不需要一个或任何东西。您只需要您正在搜索的模块的类型。要处理的最简单的集合类型是 just 。ablookupModuleTypeableIntent[]

import Data.Type.Equality
import Type.Reflection

data DirectionEntity deriving Typeable -- needs DeriveDataTypeable extension
data NavigationEntity deriving Typeable

data SomeModule =
  -- needs ExistentialQuantification, ExplicitForAll extensions
  forall a b. (Typeable a, Typeable b) => 
  SomeModule (Entity a -> IO (Entity b))

type ModuleIndex = [SomeModule]
handler :: Entity NavigationEntity -> IO (Entity DirectionEntity)
index :: ModuleIndex
index = [SomeModule handler] -- or whatever

-- really, this is more of a mapMaybe/filter than a lookup
lookupModule ::
  forall a b. (Typeable a, Typeable b) => -- needs ScopedTypeVariables
  ModuleIndex -> [Entity a -> IO (Entity b)] -- can have any number of matches!
lookupModule [] = []
-- we have a, b, which are our search queries
-- we extract x, y from the `SomeModule`
-- we have Typeable for all four, so we check for a match
-- then decide whether or not to include the function
-- (well, *we* don't really decide; without a match, it's a type error!)
lookupModule (SomeModule (x :: Entity x -> IO (Entity y)) : xs)
  -- needs GADTs, TypeOperators extensions
  | Just Refl <- testEquality typeRep typeRep :: Maybe (a :~: x)
  , Just Refl <- testEquality typeRep typeRep :: Maybe (b :~: y)
  = x : lookupModule xs
  | otherwise = lookupModule xs

哪个应该给你类似的东西

> length (lookupModule index :: [Entity NavigationEntity -> IO (Entity DirectionEntity)]) 
1
> length (lookupModule index :: [Entity DirectionEntity -> IO (Entity NavigationEntity)])
0

如果您的收藏真的很大,您可能需要使用Map. 这变得更加棘手,因为我们使用了不安全的操作,但接口仍然是安全的。这应该有自己的模块,用于访问控制。

module ModuleIndex(ModuleIndex, SomeModule(..), fromList, lookupModule) where

import Type.Reflection
import Unsafe.Coerce

import Data.Map as M

data Key = forall (a :: Type) (b :: Type). Key (TypeRep a) (TypeRep b)
instance Eq Key where
  Key a b == Key x y =
    SomeTypeRep a == SomeTypeRep x && SomeTypeRep b == SomeTypeRep y
instance Ord Key where
  compare (Key a b) (Key x y) =
    compare (SomeTypeRep a) (SomeTypeRep x) <> compare (SomeTypeRep b) (SomeTypeRep y)
data Value = forall a b. Value (Entity a -> IO (Entity b))

newtype ModuleIndex = ModuleIndex { getModuleIndex :: Map Key Value }
data SomeModule =
  forall a b. (Typeable a, Typeable b) => 
  SomeModule (Entity a -> IO (Entity b))
fromList :: [SomeModule] -> ModuleIndex
fromList = ModuleIndex . M.fromList . fmap disentangle
  where disentangle (SomeModule (f :: Entity a -> IO (Entity b))) =
          (Key (typeRep :: TypeRep a) (typeRep :: TypeRep b), Value f)
lookupModule ::
  forall a b. (Typeable a, Typeable b) =>
  ModuleIndex -> Maybe (Entity a -> IO (Entity b))
lookupModule = fmap extract . M.lookup key . getModuleIndex
  where key = Key (typeRep :: TypeRep a) (typeRep :: TypeRep b)
        extract (Value f) = unsafeCoerce f :: Entity a -> IO (Entity b)

推荐阅读