我想我理解PHOAS(参数高阶抽象语法), 我看到我们如何打印表达式(参见http://www.reddit.com/r/haskell/comments/1mo59h/phoas_for_free_by_edward_kmett/ccbxzoo)。
但是 - 我没有看到如何为这样的表达式构建解析器,例如,需要"(lambda (a) a)"
和构建(Haskell值对应于)lam $ \ x -> x
。 (它应该使用Text.Parsec或类似的。)
我可以使用de-Bruijn索引构建一个生成lambda术语的解析器,但它会有什么帮助呢?
答案 0 :(得分:22)
正如jozefg所说,您可以轻松地在操作之间进行转换。我将展示如何在命名,de-Bruijn和lambda术语的PHOAS表示之间进行转换。如果你绝对想要将它融合到解析器中相对容易,但最好先解析一个命名表示然后转换。
我们假设
import Data.Map (Map)
import qualified Data.Map as M
以及lambda术语的以下三种表示形式:
String
- 基于名称data LamN = VarN Name | AppN LamN LamN | AbsN Name LamN
deriving (Eq, Show)
type Name = String
data LamB = VarB Int | AppB LamB LamB | AbsB LamB
deriving (Eq, Show)
data LamP a = VarP a | AppP (LamP a) (LamP a) | AbsP (a -> LamP a)
现在LamP与其他人之间的转换(双向)。请注意,这些是部分功能。如果您将它们应用于包含自由变量的lambda术语,则您需要负责传递合适的环境。
LamN
转到LamP
采用将名称映射到的环境 PHOAS变量。关闭条款的环境可能是空的。
lamNtoP :: LamN -> Map Name a -> LamP a
lamNtoP (VarN n) env = VarP (env M.! n)
lamNtoP (AppN e1 e2) env = AppP (lamNtoP e1 env) (lamNtoP e2 env)
lamNtoP (AbsN n e) env = AbsP (\ x -> lamNtoP e (M.insert n x env))
LamB
转到LamP
采取一个列表的环境 PHOAS变量。可以是关闭术语的空列表。
lamBtoP :: LamB -> [a] -> LamP a
lamBtoP (VarB n) env = VarP (env !! n)
lamBtoP (AppB e1 e2) env = AppP (lamBtoP e1 env) (lamBtoP e2 env)
lamBtoP (AbsB e) env = AbsP (\ x -> lamBtoP e (x : env))
需要潜在的自由变量 被实例化为他们的名字。为生成提供名称 粘合剂的名称。应该实例化为无限的相互列表 不同的名字。
lamPtoN :: LamP Name -> [Name] -> LamN
lamPtoN (VarP n) _sup = VarN n
lamPtoN (AppP e1 e2) sup = AppN (lamPtoN e1 sup) (lamPtoN e2 sup)
lamPtoN (AbsP f) (n : sup) = AbsN n (lamPtoN (f n) sup)
需要潜在的自由变量
被实例化为数字。采用表示数量的偏移量
我们目前正在使用的粘合剂。应该被关闭的0
实例化
术语
lamPtoB :: LamP Int -> Int -> LamB
lamPtoB (VarP n) off = VarB (off - n)
lamPtoB (AppP e1 e2) off = AppB (lamPtoB e1 off) (lamPtoB e2 off)
lamPtoB (AbsP f) off = AbsB (lamPtoB (f (off + 1)) (off + 1))
-- \ x y -> x (\ z -> z x y) y
sample :: LamN
sample = AbsN "x" (AbsN "y"
(VarN "x" `AppN` (AbsN "z" (VarN "z" `AppN` VarN "x" `AppN` VarN "y"))
`AppN` (VarN "y")))
通过PHOAS去de-Bruijn:
ghci> lamPtoB (lamNtoP sample M.empty) 0
AbsB (AbsB (AppB (AppB (VarB 1) (AbsB (AppB (AppB (VarB 0) (VarB 2)) (VarB 1)))) (VarB 0)))
通过PHOAS返回姓名:
ghci> lamPtoN (lamNtoP sample M.empty) [ "x" ++ show n | n <- [1..] ]
AbsN "x1" (AbsN "x2" (AppN (AppN (VarN "x1") (AbsN "x3" (AppN (AppN (VarN "x3") (VarN "x1")) (VarN "x2")))) (VarN "x2")))
答案 1 :(得分:6)
data Named = NLam String Named | NVar String | NApp Named Named
convert :: (String -> a) -> Named -> Exp a a
convert f (NVar n) = var $ f n
convert f (NApp e1 e2) = app (convert f e1) (convert f e2)
convert f (NLam s e) = lam $ \a -> convert (nf a) e where
nf a s' = if s' == s then a else f s'
当然,您可以使用函数String -> a
以外的其他内容作为地图。例如,Data.Map
将摆脱线性时间查找。
PHOAS相对于其他HOAS计划的一个很酷的事情是,你可以轻松地转换回#34;
addNames :: ExpF Int (State Int Named) -> State Int Named
addNames (App a b) = liftM2 NApp a b
addNames (Lam f) = do
i <- get
put (i + 1)
r <- f i
return $ NLam ('x':show i) r
convert' :: Exp Int Int -> Named
convert' = fst
. flip runState 0
. cata addNames
. liftM (return . NVar . ('x':) . show)
甚至按预期工作
λ: convert' $ convert undefined $ NLam "x" $ NApp (NVar "x") (NLam "y" (NVar "y"))
> NLam "x0" (NApp (NVar "x0") (NLam "x1" (NVar "x1")))
答案 2 :(得分:3)
我将再次使用其他答案的主题运行,并建议您解析,就像您只是使用命名变量创建天真表示一样。如果你想避免使用中间表示,你可以将它内联到解析器中而不会让它更难理解:
data Lam a = Var a | Lam a `App` Lam a | Lam (a -> Lam a)
type MkLam a = (String -> a) -> Lam a
var :: String -> MkLam a
var x = Var . ($ x)
app :: MkLam a -> MkLam a -> MkLam a
app = liftA2 App
lam :: String -> MkLam a -> MkLam a
lam v e env = Lam $ \x -> e $ \v' -> if v == v' then x else env v'
我们的想法是,不要在解析器中使用中间表示的构造函数,而是直接使用这些函数。它们具有构造函数所具有的相同类型,因此它实际上只是替代品。它也有点短,因为现在我们不必单独写出ADT和解释器。