Haskell中Data.Dynamic函数组合的类型兼容问题求助
问题背景
需要将不同类型的函数存储在异构列表中(使用Data.Dynamic),并在运行时取出这些函数进行组合,生成新类型的函数。例如:
fromInt :: Int -> Double(将Int转为Double)addDubs :: Double -> Double -> Double(Double加法)
组合后得到(. fromInt) . addDubs . fromInt :: Int -> Int -> Double,最终结果需保存为Dynamic类型。
已实现硬编码类型的版本,但希望避免硬编码,根据已知的各函数类型TypeRep和目标函数TypeRep动态组合。
现有尝试及问题
1. 基于Data.Dynamic的combineDynams方案
代码:
combineDynams :: (TypeRep, Dynamic) -> (TypeRep, Dynamic) -> (TypeRep, Dynamic) -> TypeRep -> Maybe Dynamic combineDynams (tr1, f1) (tr2, f2) (tr3, f3) targetTypeRep = do f1' <- fromDynamic f1 :: Maybe tr1 f2' <- fromDynamic f2 :: Maybe tr2 f3' <- fromDynamic f3 :: Maybe tr3 return $ toDyn (partialApplyFns2Fn f1' f2' f3')
错误:
No instance for (Typeable tr1)
arising from a use of ‘fromDynamic’
• In a stmt of a 'do' block: f1' <- fromDynamic f1 :: Maybe tr1
问题:tr1是TypeRep值,而非Haskell类型变量,无法作为fromDynamic的类型参数使用,编译器无法推导其Typeable约束。
2. 基于GADT的anyFuncMaker方案
代码:
data AnyFunc where AnyUnary :: (Typeable a, Typeable b) => (a -> b) -> AnyFunc AnyBinary :: (Typeable c, Typeable d, Typeable e) => (c -> d -> e) -> AnyFunc anyFuncMaker :: AnyFunc -> AnyFunc -> AnyFunc -> Maybe AnyFunc anyFuncMaker (AnyUnary l) (AnyUnary r) (AnyBinary f) = case cast f of Just f' -> Just $ AnyBinary (partialApplyFns2Fn l r f') Nothing -> Nothing -- Type mismatch
初始错误:
The type variable ‘e0’ is ambiguous
• In the expression: cast f
开启AllowAmbiguousTypes后错误:
Could not deduce (Typeable e0) arising from a use of ‘cast’
问题:cast需要明确的目标类型,但当前代码中无法从上下文推导出f需要被转换为d -> b -> e(对应l :: a->b、r :: c->d),导致类型歧义或无法推导Typeable约束。
可行解决方案
方案1:改进GADT实现,显式类型匹配
利用Typeable的类型检查能力,先获取l和r的输入输出类型,构造出二元函数f需要的类型,再尝试转换并组合:
{-# LANGUAGE GADTs, ScopedTypeVariables #-} import Data.Dynamic import Data.Typeable fromInt :: Int -> Double fromInt = fromIntegral addDubs :: Double -> Double -> Double addDubs = (+) partialApplyFns2Fn :: (a -> b) -> (c -> d) -> (d -> b -> e) -> (c -> a -> e) partialApplyFns2Fn r l f = (. r) . f . l data AnyFunc where AnyUnary :: (Typeable a, Typeable b) => (a -> b) -> AnyFunc AnyBinary :: (Typeable c, Typeable d, Typeable e) => (c -> d -> e) -> AnyFunc -- 辅助函数:获取一元函数的输入输出TypeRep unaryTypeReps :: AnyFunc -> Maybe (TypeRep, TypeRep) unaryTypeReps (AnyUnary (f :: a -> b)) = Just (typeRep (Proxy :: Proxy a), typeRep (Proxy :: Proxy b)) unaryTypeReps _ = Nothing anyFuncMaker :: AnyFunc -> AnyFunc -> AnyFunc -> Maybe AnyFunc anyFuncMaker lUnary rUnary fBinary = do -- 提取两个一元函数的类型和函数本身 (aRep, bRep) <- unaryTypeReps lUnary (cRep, dRep) <- unaryTypeReps rUnary (l :: a -> b) <- case lUnary of AnyUnary f -> Just f; _ -> Nothing (r :: c -> d) <- case rUnary of AnyUnary f -> Just f; _ -> Nothing -- 检查二元函数类型是否匹配 d -> b -> e f <- case fBinary of AnyBinary (f' :: x -> y -> z) -> if typeRep (Proxy :: Proxy x) == dRep && typeRep (Proxy :: Proxy y) == bRep then cast f' :: Maybe (d -> b -> z) else Nothing _ -> Nothing -- 组合函数并包装为AnyBinary let combined = partialApplyFns2Fn r l f return $ AnyBinary combined
方案2:基于Data.Dynamic和Typeable的动态组合
如果坚持使用Dynamic列表,可以通过显式构造目标类型的Proxy,结合fromDynamic进行类型转换:
{-# LANGUAGE ScopedTypeVariables, TypeApplications #-} import Data.Dynamic import Data.Typeable fromInt :: Int -> Double fromInt = fromIntegral addDubs :: Double -> Double -> Double addDubs = (+) partialApplyFns2Fn :: (a -> b) -> (c -> d) -> (d -> b -> e) -> (c -> a -> e) partialApplyFns2Fn r l f = (. r) . f . l myfuncs :: [Dynamic] myfuncs = [toDyn fromInt, toDyn addDubs] -- 组合函数:通过TypeApplications指定各函数的类型 combineDynams :: forall a b c d e. (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e) => Dynamic -> Dynamic -> Dynamic -> Maybe Dynamic combineDynams d1 d2 d3 = do f1 <- fromDynamic @(a -> b) d1 f2 <- fromDynamic @(c -> d) d2 f3 <- fromDynamic @(d -> b -> e) d3 return $ toDyn (partialApplyFns2Fn f1 f2 f3) -- 使用示例:指定具体类型参数 exampleCombine :: Maybe Dynamic exampleCombine = combineDynams @Int @Double @Int @Double @Double (toDyn fromInt) (toDyn fromInt) (toDyn addDubs)
若需完全基于TypeRep动态判断,可结合eqTypeRep检查类型匹配,再转换组合:
{-# LANGUAGE ScopedTypeVariables #-} import Data.Dynamic import Data.Typeable import Control.Monad (guard) combineDynamsByTypeRep :: TypeRep -> TypeRep -> TypeRep -> Dynamic -> Dynamic -> Dynamic -> Maybe Dynamic combineDynamsByTypeRep trAB trCD trDBE d1 d2 d3 = do -- 先检查输入函数的类型是否匹配目标TypeRep guard $ dynTypeRep d1 == trAB && dynTypeRep d2 == trCD && dynTypeRep d3 == trDBE -- 提取函数并组合 f1 <- fromDynamic @(a -> b) d1 f2 <- fromDynamic @(c -> d) d2 f3 <- fromDynamic @(d -> b -> e) d3 return $ toDyn (partialApplyFns2Fn f1 f2 f3) where -- 从TypeRep推导类型参数的Proxy (_ :: Proxy a) = typeRepProxy trAB (_ :: Proxy b) = typeRepProxy trAB (_ :: Proxy c) = typeRepProxy trCD (_ :: Proxy d) = typeRepProxy trCD (_ :: Proxy e) = typeRepProxy $ resultType trDBE -- 辅助:从函数TypeRep中提取返回值类型 resultType :: TypeRep -> TypeRep resultType tr = case splitTyConApp tr of (tc, [_, res]) | tyConName tc == "->" -> res _ -> error "Not a valid function TypeRep" -- 辅助:从TypeRep生成对应类型的Proxy typeRepProxy :: Typeable t => TypeRep -> Proxy t typeRepProxy _ = Proxy
关键说明
- GADT方案更类型安全,编译时保留更多类型信息,运行时检查精准度更高。
Dynamic方案灵活性更强,但需显式处理类型匹配逻辑,使用unsafeCoerce时需严格确保类型一致,避免运行时错误。
内容的提问来源于stack exchange,提问作者ayaye

