Haskell 有效地解释抽象语法图

Haskell 有效地解释抽象语法图,haskell,graph,abstract-syntax-tree,data-reify,Haskell,Graph,Abstract Syntax Tree,Data Reify,以最简单的语言(显然被称为赫顿剃刀)为例: 如果我们只是在GHCi中运行tree30,类型将默认为Integer,我们将在共享tree的递归计算时立即得到答案。但是如果我们运行tree30::Expr,我们会得到一个巨大的语法树,因为Haskell的let提供的共享不会转换为嵌入式语言中的表达式 数据具体化库可用于观察表达式中可能存在的任何隐式共享。我们可以添加一些机制来实现: {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE TypeFamilies #-

以最简单的语言(显然被称为赫顿剃刀)为例:

如果我们只是在GHCi中运行
tree30
,类型将默认为
Integer
,我们将在共享
tree
的递归计算时立即得到答案。但是如果我们运行
tree30::Expr
,我们会得到一个巨大的语法树,因为Haskell的
let
提供的共享不会转换为嵌入式语言中的表达式

数据具体化
库可用于观察表达式中可能存在的任何隐式共享。我们可以添加一些机制来实现:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TypeFamilies #-}

import Control.Applicative
import Data.Reify

data ExprF e =
    LitF Int
  | AddF e e
  deriving (Eq, Show, Functor)

instance MuRef Expr where
  type DeRef Expr = ExprF
  mapDeRef f (Add e0 e1) = Add <$> f e0 <*> f e1
  mapDeRef _ (Lit d)     = pure (LitF d)
现在我感兴趣的是解释抽象语法图,而不是抽象语法树

一个简单的
evalGraph
函数通过将其解释为一棵树来消除共享:

evalGraph (Graph env r) = go r where
  go j = case lookup j env of
    Just (AddF a b) -> go a + go b
    Just (LitF d)   -> d
    Nothing         -> 0
这可以通过尝试来验证

> evalGraph <$> reifyGraph (tree 50 :: Expr)
假设您的图形是a,问题很简单:

  • 拓扑排序节点
  • 按顺序求值,边走边查找值
  • 对于您的示例,其工作原理如下:

    将节点反转为:

    [(3,LitF 1),(2,AddF 3 3),(1,AddF 2 2)]
    
    然后依次计算,查找前面的值:

    [1,2,4]
    

    你还不必放弃树方法。共享确实会发生,问题是函数调用甚至在共享子表达式上也不会被记忆

    通过在eval函数中使用基于身份的备忘录,可以很容易地解决这个问题

    eval = go
     where go = memo eval'
           eval' (Lit i) = i
           eval' (Add e1 e2) = go e1 + go e2
    

    我使用进行了测试,似乎效果不错。

    将这两个步骤融合在一起可能是很自然的;例如,将基于DFS的拓扑排序与求值步骤相融合会产生一个朴素算法的记忆版本。(实际上我不认识哈斯克尔,所以我不确定我是否正确地解释了事情,但在我看来这就是它的样子。)
    [(3,LitF 1),(2,AddF 3 3),(1,AddF 2 2)]
    
    [1,2,4]
    
    eval = go
     where go = memo eval'
           eval' (Lit i) = i
           eval' (Add e1 e2) = go e1 + go e2