我正在尝试在 Haskell 中实现 EDSL。我想用绑定的变量名漂亮地打印 AST(如果我不能得到真实的名字,那么一些生成的名字就可以了)。
这是我通过一个简单的例子已经走了多远:
import Control.Monad.State
data Free f a = Roll (f (Free f a))
              | Pure a
instance Functor f => Monad (Free f) where
  return         = Pure
  (Pure a) >>= f = f a
  (Roll f) >>= g = Roll $ fmap (>>= g) f
data Expr a = I a
            | Plus (Expr a) (Expr a)
            deriving (Show)
data StackProgram a next = Pop  (a -> next)
                         | Push a next
instance Functor (StackProgram a) where
  fmap f (Pop    k) = Pop (f.k)
  fmap f (Push i x) = Push i (f x)
liftF :: Functor f => f a -> Free f a
liftF l = Roll $ fmap return l
push :: a -> Free (StackProgram a) ()
push i = liftF $ Push i ()
pop :: Free (StackProgram a) a
pop = liftF $ Pop id
prog3 :: Free (StackProgram (Expr Int)) (Expr Int)
prog3 = do
  push (I 3)
  push (I 4)
  a <- pop
  b <- pop
  return (Plus a b)
showSP' :: (Show a, Show b) => Free (StackProgram a) b -> [a] -> State Int String
showSP' (Pure a)           _        = return $ "return " ++ show a
showSP' (Roll (Pop f))    (a:stack) = do 
  i <- get
  put (i+1)
  rest <- showSP' (f a) stack
  return $ "var" ++ show i ++ " <- pop " ++ show (a:stack) ++ "\n" ++ rest
showSP' (Roll (Push i n))  stack    = do
  rest <- showSP' n (i:stack) 
  return $ "push " ++ show i ++ " " ++ show stack ++ "\n" ++ rest
showSP :: (Show a, Show b) => Free (StackProgram a) b -> [a] -> String
showSP prg stk = fst $ runState (showSP' prg stk) 0
运行这个给出:
*Main> putStrLn $ showSP prog3 []
push I 3 []
push I 4 [I 3]
var0 <- pop [I 4,I 3]
var1 <- pop [I 3]
return Plus (I 4) (I 3)
所以我想要的是替换Plus (I 4) (I 3)为Plus var0 var1. 我曾考虑过遍历树的其余部分并用名称-值元组替换绑定变量,但我不能 100% 确定这是否/如何工作。我也更喜欢保留原始变量名,但我想不出一种简单的方法来做到这一点。我希望在 haskell 中有一个相当轻量级的语法(如上所述)。
我也很感激那些教我如何最好地做这些事情的材料的指针。我已经阅读了一些关于免费单子和 GADT 的内容,但我想我想念如何将它们放在一起。