相互递归类型的最终无标记编码

时间:2015-08-09 15:05:00

标签: haskell typeclass functional-dependencies

我试图用final-tagless编码表达一对相互递归的数据类型。

我能写:

{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE ExplicitForAll #-}
module Test where

  class ExprSYM repr where
    expr :: forall proc. (ProcSYM proc) => proc Int -> repr

  class ProcSYM repr where
    varProc :: forall a. String -> repr a
    intProc :: String -> repr Int
    subjectOf :: forall expr. (ExprSYM expr) => expr -> repr Int

  myProc = intProc "proc A."

然而,当我写:

myExpr = expr myProc

我收到以下错误:

Could not deduce (Test.ProcSYM proc0)
  arising from a use of ‘Test.expr’
from the context (Test.ExprSYM repr)
  bound by the inferred type of
           Test.myExpr :: Test.ExprSYM repr => repr
  at src/Test.hs:16:3-22
The type variable ‘proc0’ is ambiguous
In the expression: Test.expr Test.myProc
In an equation for ‘Test.myExpr’:
    Test.myExpr = Test.expr Test.myProc

是否有任何此类编码需要使用函数依赖(或等效)来解决两个representation类型之间的约束?

如果是这样,我该怎么写呢?

1 个答案:

答案 0 :(得分:4)

让我们先看一下myProc

的类型
myProc :: ProcSYM repr => repr Int
myProc = intProc "proc A."

这表示,forall类型repr其中ProcSYM repr,我的类型为repr Int。如果我们有ProcSYM的多个实现,则这是一个在所有实现中具有多态性的值。例如,如果我们有一个带有GADT实例的相应标记ProcSYM' ProcSYM,则myProc可以用作ProcSYM'的值。

{-# LANGUAGE GADTs #-}

data ProcSYM' a where
    VarProc :: String -> ProcSYM' a
    IntProc :: String -> ProcSYM' a

instance ProcSYM ProcSYM' where
    varProc = VarProc
    intProc = IntProc

myProc' :: ProcSYM' Int
myProc' = myProc

ProcSym repr中的myProc :: ProcSYM repr => repr Int约束提供了构建 repr的方法,这正是myProc所做的。无论您想要哪个ProcSym repr,都可以构建repr Int

ProcSYM proc类型中的expr :: forall proc. (ProcSYM proc) => proc Int -> repr约束是没有意义的。约束ProcSYM proc再一次提供了构建proc的方法。它无法帮助我们查看内部或解构 proc Int。如果没有办法查看proc Int内部,我们也可能没有proc Int参数,而是阅读expr :: repr

另一方面,类型forall proc. ProcSYM proc => proc IntmyProc的类型)承诺,无论你如何构造proc,我都可以提供该类型的值。您希望将myProc作为第一个参数传递给expr,如

所示
myExpr = expr myProc

在不选择具体proc的情况下传递此类型的多态值需要RankNTypes

class ExprSYM repr where
    expr :: (forall proc. ProcSYM proc => proc Int) -> repr

ExprSYM的实例可以选择ProcSYM字典传递给第一个参数。这允许expr的实现解构proc Int。我们将通过使用GADTs完成示例来证明这一点,以了解这是做什么的。我们还会对subjectOf的类型进行相同的更改。

{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GADTs #-}
module Test where

class ExprSYM repr where
    expr :: (forall proc. ProcSYM proc => proc Int) -> repr

class ProcSYM repr where
    varProc :: forall a. String -> repr a
    intProc :: String -> repr Int
    subjectOf :: (forall expr. ExprSYM expr => expr) -> repr Int

-- Tagged representation for ExprSYM
data ExprSYM' where
    Expr :: ProcSYM' Int -> ExprSYM'
deriving instance Show ExprSYM'

instance ExprSYM ExprSYM' where
    expr x = Expr x  -- chooses that the ProcSYM proc => proc Int must be ProcSYM' Int

-- Tagged representation for ProcSYM
data ProcSYM' a where
    VarProc :: String -> ProcSYM' a
    IntProc :: String -> ProcSYM' a
    SubjectOf :: ExprSYM' -> ProcSYM' Int

deriving instance Show (ProcSYM' a)

instance ProcSYM ProcSYM' where
    varProc = VarProc
    intProc = IntProc
    subjectOf x = SubjectOf x  -- chooses that the ExprSYM repr => repr must be ExprSYM'

-- myProc and myExpr with explicit type signatures
myProc :: ProcSYM repr => repr Int
myProc = intProc "proc A."

myExpr :: ExprSYM repr => repr
myExpr = expr myProc

main = print (myExpr :: ExprSYM')

这将为myExpr输出一个抽象语法树。我们可以看到,如果expr的实现想要解构ProcSYM proc => proc Int值,它可以(在这种情况下确实)提供一个ProcSYM字典来构建它知道如何解构的值。我们可以在显示值的IntProc构造函数中看到这一点。

Expr (IntProc "proc A.")