Haskell 将函数降低为嵌入式语言

Haskell 将函数降低为嵌入式语言,haskell,types,existential-type,gadt,Haskell,Types,Existential Type,Gadt,如何以尽可能类型安全的方式将Haskell函数降低为嵌入式语言。特别是,假设我有一个值类型,如 data Type t where Num :: Type Int Bool :: Type Bool data Ty = TNum | TBool deriving Eq data Tagged t = Tagged (Type t) t deriving Typeable data Dynamic = forall t . Typeable t => Dynamic (Tag

如何以尽可能类型安全的方式将Haskell函数降低为嵌入式语言。特别是,假设我有一个值类型,如

data Type t where
  Num  :: Type Int
  Bool :: Type Bool

data Ty = TNum | TBool deriving Eq

data Tagged t = Tagged (Type t) t deriving Typeable
data Dynamic  = forall t . Typeable t => Dynamic (Tagged t) deriving Typeable

forget :: Typeable t => Tagged t -> Dynamic
forget = Dynamic

remember :: Typeable b => Dynamic -> Maybe b
remember (Dynamic c) = cast c
我想把像
(isSucc::Int->Int->Bool)
这样的函数转换成它的动态形式和一些类型信息的乘积,如下所示

data SplitFun = SF { dynamic    :: [Dynamic] -> Dynamic 
                   , inputTypes :: [Ty] 
                   , outputType :: Ty
                   }
对于某些
应用
功能

(\(a:b:_) -> isSucc a b) == apply (makeDynamicFn isSucc)
对动态类型实际上不匹配时可能引发的一些异常进行模化。或者,更明确地说,我想找到
makeDynamicFn::FunType->SplitFun
。显然,这不是一个合适的Haskell类型,而且不太可能有一种从
isSucc
本身提取类型的方法,因此它可能更像

anInt . anInt . retBool $ isSucc :: SplitFun
其中
anInt
retBool
具有
printf
样式的类型


这可能吗?有没有办法模拟它?

要实现
FunType->SplitFun
类型的函数,我们将使用标准类型类机制来解构函数类型

现在,直接实现这个函数变得相当困难。要从递归案例中获取
inputtype
outputType
,必须应用函数;但是,您只能在
动态
字段中应用该函数,这使您无法填充其他字段。相反,我们将把任务分成两部分:一个函数将为我们提供
Ty
信息,另一个将构造
[Dynamic]->Dynamic
函数

data Proxy a = Proxy

class Split r where
    dynFun :: r -> [Dynamic] -> Dynamic
    tyInfo :: Proxy r -> ([Ty], Ty)

    split :: r -> SplitFun
    split f = let (i, o) = tyInfo (Proxy :: Proxy r)
              in  SF (dynFun f) i o
现在,
tyInfo
实际上并不需要该函数,我们使用
Proxy
只是传递类型信息,而不需要到处使用
undefined
。注意,我们需要
ScopedTypeVariables
才能从实例声明中引用类型变量
r
。巧妙地使用
asTypeOf
也可能奏效

我们有两种基本情况:
Bool
Int

instance Split Int where
    dynFun i _ = forget (Tagged Num i)
    tyInfo _ = ([], TNum)

instance Split Bool where
    dynFun b _ = forget (Tagged Bool b)
    tyInfo _ = ([], TBool)
没有输入类型,因为我们已经有了一个值,我们不需要要求更多的
Dynamic
值,只需返回该特定值的
Dynamic

接下来,我们有两个递归案例:
Bool->r
Int->r

instance (Split r) => Split (Int -> r) where
    dynFun f (d:ds) = case remember d :: Maybe (Tagged Int) of
        Just (Tagged _ i) -> dynFun (f i) ds
        Nothing           -> error "dynFun: wrong dynamic type"
    dynFun f []     = error "dynFun: not enough arguments"

    tyInfo _ = case tyInfo (Proxy :: Proxy r) of
         (i, o) -> (TNum:i, o)

instance (Split r) => Split (Bool -> r) where
    dynFun f (d:ds) = case remember d :: Maybe (Tagged Bool) of
        Just (Tagged _ b) -> dynFun (f b) ds
        Nothing           -> error "dynFun: wrong dynamic type"
    dynFun f []     = error "dynFun: not enough arguments"

    tyInfo _ = case tyInfo (Proxy :: Proxy r) of
         (i, o) -> (TBool:i, o)
这两个需要
FlexibleInstances
dynFun
检查第一个
Dynamic
参数,如果没有问题,我们可以安全地将函数
f
应用到它并从那里继续。我们也可以做
dynFun::r->[Dynamic]->可能是动态的,但这是相当小的改变


现在,有一些重复正在进行。我们可以引入另一类,例如:

class Concrete r where
    getTy   :: Proxy r -> Ty
    getType :: Proxy r -> Type r
然后写:

instance (Typeable r, Concrete r) => Split r where
    dynFun r _ = forget (Tagged (getType (Proxy :: Proxy r)) r)
    tyInfo _ = ([], getTy (Proxy :: Proxy r))

instance (Typeable r, Concrete r, Split s) => Split (r -> s) where
    dynFun f (d:ds) = case remember d :: Maybe (Tagged r) of
        Just (Tagged _ v) -> dynFun (f v) ds
        -- ...

    tyInfo _ = case tyInfo (Proxy :: Proxy s) of
        (i, o) -> (getTy (Proxy :: Proxy r):i, o)

但是这需要
重叠实例
不可判定实例

这两种方法来回答这个问题吗?如果是这样的话,我会写下一个实际的答案。我会很快在真实的代码中尝试,但看起来不错。绝对是一条比我所遵循的更简单的路径。这非常有效!请加上它作为答案。