15

haskellwikiで説明されている準引用は、文字列の引用をいじることなく Haskell 内に他の言語を埋め込むための便利なツールとして主に示されています。

質問: Haskell 自体にとって、トークンを置き換えて結果を ghc に渡すだけの目的で、既存の Haskell コードを quasiquoter に通すのはどれほど簡単でしょうか? おそらくテンプレート Haskell がここで鍵となるでしょうか?

コード例を探しましたが、見つかりませんでした。一部の EDSL は、結合演算子のサイズを小さくすることでこの機能の恩恵を受けることができます (たとえば、'a .|. b .>>. c' を '[myedsl|a | b >> c]' に変更します)。

4

1 に答える 1

18

たとえば、haskell-src-metaパッケージを使用して、Haskellコードを操作する準クォーターを作成できます。有効なHaskellコードをASTに解析し、それを変更できます。

この場合、ASTを変更する最も簡単な方法は、Data.Genericsを使用して、演算子を他の演算子に置き換えるAST全体にジェネリック変換を適用することです。

まず、一般的なHaskell式の変換関数を作成します。式を表すデータ型は、template-haskellパッケージのExpです。

たとえば、演算子>>をに変換するには.>>.、次のような関数を使用します

import Language.Haskell.TH (Exp(..), mkName)

replaceOp :: Exp -> Exp
replaceOp (VarE n) | n == mkName ">>" = VarE (mkName ".>>.")
replaceOp e = e

これにより、変数式(VarE)が変更されますが、他の種類の式には何もできません。

ここで、AST全体をウォークし、すべての出現箇所を置き換えるために>>、関数everywheremkTfromを使用しますData.Generic

import Data.Generics (everywhere, mkT)

replaceEveryOp :: Exp -> Exp
replaceEveryOp = everywhere (mkT replaceOp) 

いくつかの置換を行うために、置換する演算子の関連付けリストを取得するように関数を変更できます。

type Replacements = [(String, String)]

replaceOps :: Replacements -> Exp -> Exp
replaceOps reps = everywhere (mkT f) where
    f e@(VarE n) = case rep of
        Just n' -> VarE (mkName n')
        _ -> e
        where rep = lookup (show n) reps
    f e = e

ちなみに、これは、ビューパターン言語拡張機能を使用して記述した方がはるかに優れた関数の良い例です。

{-# LANGUAGE ViewPatterns #-}

replaceOps :: Replacements -> Exp -> Exp
replaceOps reps = everywhere (mkT f) where
    f (VarE (replace -> Just n')) = VarE (mkName n')
    f e = e

    replace n = lookup (show n) reps

あとは、「myedsl」準クォーターを作成するだけです。

{-# LANGUAGE ViewPatterns #-}

import Data.Generics (everywhere, mkT)
import Language.Haskell.Meta.Parse (parseExp)
import Language.Haskell.TH (Exp(..), mkName, ExpQ)
import Language.Haskell.TH.Quote (QuasiQuoter(..))

type Replacements = [(String, String)]

replacements :: Replacements
replacements =
    [ ("||", ".|.")
    , (">>", ".>>.")
    ]

myedls = QuasiQuoter
    { quoteExp  = replaceOpsQ
    , quotePat  = undefined
    , quoteType = undefined
    , quoteDec  = undefined
    }

replaceOpsQ :: String -> ExpQ
replaceOpsQ s = case parseExp s of
    Right e -> return $ replaceOps replacements e
    Left err -> fail err

replaceOps :: Replacements -> Exp -> Exp
replaceOps reps = everywhere (mkT f) where
    f (VarE (replace -> Just n')) = VarE (mkName n')
    f e = e

    replace n = lookup (show n) reps

上記を独自のモジュール(例MyEDSL.hs)に保存すると、それをインポートして準クォーターを使用できます。

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}

import MyEDSL

foo = [myedsl| a || b >> c |]

後者はHaskellでは有効な演算子ではないため(パターンガードに使用される構文要素であるため)、||代わりに使用したことに注意してください。|

于 2012-08-22T17:27:32.650 に答える