Haskell 有效地解释抽象语法图
以最简单的语言(显然被称为赫顿剃刀)为例: 如果我们只是在GHCi中运行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 #-
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