Skip to content

Instantly share code, notes, and snippets.

@takoeight0821
Created November 28, 2018 03:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takoeight0821/acfbd5274b970e55ed8c7ed86a3b07b5 to your computer and use it in GitHub Desktop.
Save takoeight0821/acfbd5274b970e55ed8c7ed86a3b07b5 to your computer and use it in GitHub Desktop.
ASTを構築する言語内DSLについて色々考えたメモ
module AstDsl where
import Control.Monad.State
import Control.Monad.Identity
import Data.Monoid
import Control.Monad.Writer
-- こんな普通のAST定義があるとする
data Expr = Let String Expr Expr
| Var String
| Val Int
| Add Expr Expr
deriving (Eq, Show)
-- 普通にASTを構築するとこんな感じ
buildSimple :: Expr
buildSimple =
Let "a" (Val 42)
$ Let "b" (Val 21)
$ Add (Add (Var "a") (Var "b")) (Val 1)
-- Stateモナドを使ってユニークな変数を生成する
-- この後の拡張を考えてモナド変換子を使う
newVar :: Monad m => String -> StateT Int m String
newVar x = do
a <- get
modify (+1)
return $ x <> show a
-- これで同名の変数を作ってしまうヒューマンエラーが防げる
buildState :: Expr
buildState = runIdentity $ flip evalStateT 0 $ do
a <- newVar "a"
b <- newVar "a"
return
$ Let a (Val 42)
$ Let b (Val 21)
$ Add (Add (Var a) (Var b)) (Val 1)
-- Letを自動生成する
let_ :: Monad m => Expr -> StateT Int m (Expr, Expr -> Expr)
let_ expr = do
x <- newVar "x"
return (Var x, Let x expr)
-- これでVarを省略できる
buildGenLet :: Expr
buildGenLet = runIdentity $ flip evalStateT 0 $ do
(a, letA) <- let_ $ Val 42
(b, letB) <- let_ $ Val 21
return $ letA $ letB $ Add (Add a b) (Val 1)
-- WriterモナドとEndoを使って継続を暗黙的に処理する
def :: Expr -> StateT Int (Writer (Endo Expr)) Expr
def expr = do
x <- newVar "x"
tell $ Endo $ Let x expr
return (Var x)
-- コンストラクタを剥がす部分が若干ゴツくなるが、doの中身はスッキリ
buildDSL :: Expr
buildDSL = uncurry (flip appEndo) $ runWriter $ flip evalStateT 0 $ do
a <- def $ Val 42
b <- def $ Val 21
return $ Add (Add a b) (Val 1)
main :: IO ()
main = do
print buildSimple
print buildState
print buildGenLet
print buildDSL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment