我们将把这个问题分为三个部分。第一部分是使用
data-reify库来恢复
AstF
的图形。第二部分将创建一个抽象语法树,其中包含用de Bruijn索引表示的
Let
绑定。最后,我们将删除所有不必要的let绑定。
这些都是我们沿途使用的工具。只有提供
Eq
和
Show
实例的东西(如
Fix
)才需要使用
StandaloneDeriving
和
UndecidableInstances
。
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
import Data.Foldable
import Data.Reify
import Data.Traversable
import qualified Data.List as List
import Data.IntMap ((!))
import qualified Data.IntMap as IntMap
import Prelude hiding (any)
使用 data-reify
您几乎已经具备了使用 data-reify 库所需的所有要素。
data AstF f =
LitF Int
| AddF f f
deriving (Eq, Show, Functor, Foldable, Traversable)
newtype Fix f = In { out :: f (Fix f) }
deriving instance Eq (f (Fix f)) => Eq (Fix f)
deriving instance Show (f (Fix f)) => Show (Fix f)
instance Traversable a => MuRef (Fix a) where
type DeRef (Fix a) = a
mapDeRef f = traverse f . out
唯一缺少的就是调用reifyGraph
函数。让我们试一个小例子。
do
let example = In (AddF (In (AddF (In (LitF 1)) (In (LitF 2)))) example)
graph <- reifyGraph example
print graph
这将输出
let [(1,AddF 2 1),(2,AddF 3 4),(4,LitF 2),(3,LitF 1)] in 1
graph
的类型为 Graph AstF
,由构造函数 Graph [(Unique, AstF Unique)] Unique
构建。构造函数的第一个参数是带有新唯一键的节点列表。结构中的每个边缘都已被替换为边缘头部节点的新唯一键。构造函数的第二个参数是树根节点的唯一键。
将图形转换为 Let 表示
我们将把来自 data-reify 的 Graph
转换为具有 Let
绑定的 de Bruijn 索引抽象语法树。我们将使用以下类型表示 AST。此类型不需要了解 AST 的内部表示。
type Index = Int
data Indexed f
= Var Index
| Let (Indexed f) (Indexed f)
| Exp (f (Indexed f))
deriving instance Eq (f (Indexed f)) => Eq (Indexed f)
deriving instance Show (f (Indexed f)) => Show (Indexed f)
Index
表示变量在使用时与声明时之间的
Let
数。将
Let a b
解读为
let (Var 0)=a in b
。
我们将从根节点开始遍历图形,将其转换为
Indexed
AST。在每个节点上,我们会引入一个
Let
绑定。对于每条边,我们将检查它所指向的节点是否已经被引入了处于作用域内的
Let
绑定。如果是,则将该边替换为该
Let
绑定的变量。如果还没有被引入,则会继续遍历它。我们需要知道的唯一一件事就是我们操作的AST是一个
Functor
。
index :: Functor f => Graph (DeRef (Fix f)) -> Indexed f
index (Graph edges root) = go [root]
where
go keys@(key:_) =
Let (Exp (fmap lookup (map ! key))) (Var 0)
where
lookup unique =
case List.elemIndex unique keys of
Just n -> Var n
Nothing -> go (unique:keys)
map = IntMap.fromList edges
为了方便,我们将定义以下内容。
reifyLet :: Traversable f => Fix f -> IO (Indexed f)
reifyLet = fmap index . reifyGraph
我们将尝试与之前相同的示例。
do
let example = In (AddF (In (AddF (In (LitF 1)) (In (LitF 2)))) example)
lets <- reifyLet example
print lets
这将输出:
这将输出
Let (Exp (AddF (Let (Exp (AddF (Let (Exp (LitF 1)) (Var 0)) (Let (Exp (LitF 2)) (Var 0)))) (Var 0)) (Var 0))) (Var 0)
我们在
example
中只有一个
let
绑定,但是这里有四个
Let
。在下一步中,我们将删除不必要的
Let
绑定。
移除不必要的
Let
绑定
为了移除引入未使用变量的
Let
绑定,我们需要一个被使用变量的概念。我们将为任何可折叠AST定义它。
used :: (Foldable f) => Index -> Indexed f -> Bool
used x (Var y) = x == y
used x (Let a b) = used (x+1) a || used (x+1) b
used x (Exp a) = any (used x) a
当我们移除一个Let
绑定时,中间的Let
绑定数量以及变量的de Bruijn索引将会改变。我们需要能够从Indexed
AST中移除一个变量。
remove x :: (Functor f) => Index -> Indexed f -> Indexed f
remove x (Var y) =
case y `compare` x of
EQ -> error "Removed variable that's being used`
LT -> Var y
GT -> Var (y-1)
remove x (Let a b) = Let (remove (x+1) a) (remove (x+1) b)
remove x (Exp a) = Exp (fmap (remove x) a)
有两种方式可以引入未使用的变量,Let
绑定。 变量可以完全未使用,例如let a = 1 in 2
,或者它可以被无意义地使用,例如let a = 1 in a
。 第一个可以被替换为2
,第二个可以被替换为1
。 当我们移除Let
绑定时,我们还需要使用remove
调整AST中的所有剩余变量。 不是Let
的东西不会引入未使用的变量,因此没有什么可以替换的。
removeUnusedLet :: (Functor f, Foldable f) => Indexed f -> Indexed f
removeUnusedLet (Let a b) =
if (used 0 b)
then
case b of
Var 0 ->
if (used 0 a)
then (Let a b)
else remove 0 a
_ -> (Let a b)
else remove 0 b
removeUnusedLet x = x
我们希望能够在
Indexed
AST的任何地方应用
removeUnusedLet
。我们可以使用更通用的方法来实现这一点,但我们将为自己定义如何在
Indexed
AST中应用函数。请注意保留HTML标记。
mapIndexed :: (Functor f) => (Indexed f -> Indexed f) -> Indexed f -> Indexed f
mapIndexed f (Let a b) = Let (f a) (f b)
mapIndexed f (Exp a) = Exp (fmap f a)
mapIndexed f x = x
postMap :: (Functor f) => (Indexed f -> Indexed f) -> Indexed f -> Indexed f
postMap f = go
where
go = f . mapIndexed go
然后我们可以使用以下代码删除所有未使用的变量:
removeUnusedLets = postMap removeUnusedLet
我们将再次尝试我们的示例
do
let example = In (AddF (In (AddF (In (LitF 1)) (In (LitF 2)))) example)
lets <- reifyLet example
let simplified = removeUnusedLets lets
print simplified
这里只介绍了一个
Let
。
Let (Exp (AddF (Exp (AddF (Exp (LitF 1)) (Exp (LitF 2)))) (Var 0))) (Var 0)
限制
相互递归的定义不会导致相互递归的Let
绑定。例如:
do
let
left = In (AddF (In (LitF 1)) right )
right = In (AddF left (In (LitF 2)))
example = In (AddF left right )
lets <- reifyLet example
let simplified = removeUnusedLets lets
print simplified
结果在
Exp (AddF
(Let (Exp (AddF
(Exp (LitF 1))
(Exp (AddF (Var 0) (Exp (LitF 2))))
)) (Var 0))
(Let (Exp (AddF
(Exp (AddF (Exp (LitF 1)) (Var 0)))
(Exp (LitF 2))
)) (Var 0)))
我不相信在Indexed
中有一种互递归的表示方式,而不使用负Index
。
AstF
不应该直接递归 - 这就是Fix
的作用。将AddF
情况更改为AddF f f
,否则无法通过reifyGraph
恢复任何共享。另外,如果有人编写递归或相互递归的表达式,您希望发生什么? De Bruijn指数不能表示它(我想)。最后,我认为这并不是完全微不足道的,因为reifyGraph
中绑定列表没有按拓扑排序。 - GS - Apologise to MonicaAstF
的定义。 - tibbe