如何使用Cofree注释使用AST?

ais*_*ais 4 haskell abstract-syntax-tree catamorphism comonad recursion-schemes

我有这个简单的ExprAST,我可以轻松地将其转换为String.

import Prelude hiding (Foldable)
import qualified Prelude
import Data.Foldable as F
import Data.Functor.Foldable
import Data.Monoid
import Control.Comonad.Cofree

data ExprF r = Const Int
              | Add   r r
                deriving ( Show, Eq, Ord, Functor, Prelude.Foldable )

type Expr = Fix ExprF

testExpr = Fix $ Add (Fix (Const 1)) (Fix (Const 2))

convertToString :: Expr -> String
convertToString = cata $ \case
  e@(Const x) -> show x
  e@(Add x y) -> unwords [x, "+", y]
Run Code Online (Sandbox Code Playgroud)

现在我想添加一个额外的数据.所以我想尝试使用Cofree

type LineNumber = Int
type Expr2 = Cofree ExprF LineNumber
Run Code Online (Sandbox Code Playgroud)

我可以转换ExprExpr2

addLineNumbers :: Expr -> Expr2
addLineNumbers = cata $ \case
  e@(Const _) -> 1 :< e
  e -> 2 :< e
Run Code Online (Sandbox Code Playgroud)

但我无法弄清楚如何转换Expr2String

convertToString2 :: Expr2 -> String
convertToString2 = cata $ \case
  e@(_ :< (Const x)) -> show x
  e@(_ :< (Add x y)) -> unwords [x, "+", y]
Run Code Online (Sandbox Code Playgroud)

此外,Cofree是解决此注释问题的最佳方法吗?

Ben*_*son 10

注释语法树的另一种方法是将注释组合到基本仿函数中.

-- constant functor
newtype K c a = K c
    deriving (Eq, Ord, Show, Read, Functor, Foldable, Traversable)

-- functor product
data (f :*: g) a = (:*:) { left :: f a, right :: g a }
    deriving (Eq, Ord, Show, Read, Functor, Foldable, Traversable)
Run Code Online (Sandbox Code Playgroud)

我们将使用仿函数产品将注释(在a内K)附加到树的每一层.

type AnnExpr = Fix (K LineNumber :*: ExprF)
Run Code Online (Sandbox Code Playgroud)

如果您只能在检查树的单个图层时生成注释(也就是说,您的注释生成代码可以表示为自然变换),那么您可以使用以下一些机制来修改仿函数,同时保持修复点结构地点:

hoistFix :: Functor f => (forall a. f a -> g a) -> Fix f -> Fix g
hoistFix f = Fix . f . fmap (hoistFix f) . unFix
Run Code Online (Sandbox Code Playgroud)

然而,这是有限的用途,因为大多数有趣的注释(例如类型检查)需要遍历语法树.

您可以Expr通过简单地忽略注释来重用代码来拆除它.给定代数ExprF...

-- instructions for a stack machine
data Inst = PUSH Int | ADD
type Prog = [Inst]

compile_ :: ExprF Prog -> Prog
compile_ (Const x) = [PUSH x]
compile_ (Add x y) = x ++ y ++ [ADD]
Run Code Online (Sandbox Code Playgroud)

...你可以用它来拆掉一个Expr或一个AnnExpr:

compileE :: Expr -> Prog 
compileE = cata compile_

compileA :: AnnExpr -> Prog
compileA = cata (compile_ . right)
Run Code Online (Sandbox Code Playgroud)

  • @ user2407038我更喜欢重用较小的位,如`K`和`:*:`,并为doman特定的用途定义类型/模式同义词.`type(x:&g)= K x:*:g`和`pattern x:&y = K x:*:y` (2认同)