В очень упрощенном смысле у меня есть что-то вроде следующего:
type Runtime a = {- More or less a StateT on top of an Either monad -}
-- The list of strings in Fn is a bunch of parameter names, the values of
-- which are pushed into the state of the runtime before executing the actual
-- function expr
data Expr = Num Int
| Str T.Text
| Fn [T.Text] (Runtime Expr)
| {- Bunch of other constructors -}
eval :: Expr -> Runtime Expr
parseExp :: Parser Expr
Так вот, я никогда ни для чего не использовал Template Haskell, пока не решил, что было бы удобно иметь квазицитатор для моего игрушечного языка, так что я допускаю, что могу упустить что-то очевидное.
Но в любом случае, я начал немного возиться с ним, просмотрел несколько руководств и т. д. и в основном обнаружил, что все, кроме того, как работать с конструктором Fn
, было легко.
Во время моих поисков информации в Интернете я обнаружил два основных способа, которыми люди пишут выражение «цитата»:
- Сделать их тип данных
Expr
экземпляром TH:sLift
и просто [| цитата |] выражение, полученное в результате синтаксического анализа - Получение
Data
иTypeable
для их эквивалентаExpr
, а затем применениеdataToExpQ
к тому же результату парсера
В обоих случаях у меня возникли сложности с Runtime Expr
. В первом случае проблема заключалась в том, что я не мог понять, как реализовать:
instance Lift Expr where
lift (Fn ps e) = [| Fn ps ...? |]
(Хотя мне удалось реализовать экземпляр для Data.Text самостоятельно).
Я полагаю, что настоящая проблема заключается в том, что я просто еще недостаточно хорошо знаю TH, но никакие учебники или примеры пока не помогли мне добиться чего-либо в этом.
Во втором случае проблема заключалась в том, что для того, чтобы Expr
был экземпляром Data
, также должен быть
instance Data (StateT (...) (Either ...) Expr) where
-- Something
Тогда мой вопрос: есть ли простой способ сделать это? Или, возможно, мне следует переосмыслить, как работают функции моего игрушечного языка?
Если последнее, какие-либо рекомендации о том, как получить эквивалентную функциональность, не запуская их внутри монады? В конце концов, это кажется интуитивно понятным решением, поскольку среда выполнения языка требует обработки состояния и ошибок (именно для этого я использую Either
).