Ответ 1
Вы можете построить квазициклы, которые манипулируют кодом Haskell, например, используя пакет haskell-src-meta. Он анализирует действительный код Haskell в AST, который вы затем можете изменить.
В этом случае самый простой способ изменить AST - это использовать Data.Generics, чтобы применить общее преобразование ко всему АСТ, которое заменяет операторов другими операторами.
Начнем с построения функции преобразования для генерических выражений Haskell. Тип данных, представляющий выражение, Exp в пакете template-haskell.
Например, чтобы преобразовать оператор >>
в .>>.
, мы использовали бы функцию типа
import Language.Haskell.TH (Exp(..), mkName)
replaceOp :: Exp -> Exp
replaceOp (VarE n) | n == mkName ">>" = VarE (mkName ".>>.")
replaceOp e = e
Это изменяет выражение переменной (VarE
), но не может ничего сделать для других выражений.
Теперь, чтобы пройти весь АСТ и заменить все вхождения >>
, мы будем использовать функции everywhere
и mkT
от 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 [email protected](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 (так как это синтаксический элемент, используемый для защиты шаблонов).