Выражение квазицитата для AST, где один конструктор производит монадическое вычисление?

В очень упрощенном смысле у меня есть что-то вроде следующего:

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:s Lift и просто [| цитата |] выражение, полученное в результате синтаксического анализа
  • Получение 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).


person Kben    schedule 20.07.2012    source источник
comment
Не могли бы вы подробнее рассказать о сложностях, с которыми вы столкнулись, пожалуйста.   -  person dave4420    schedule 20.07.2012
comment
Попробовал немного разъяснить сложности. Проблема в том, что я практически не знаю, с чего начать с любой из реализаций, поэтому я не могу рассказать больше - вот как далеко я продвинулся.   -  person Kben    schedule 20.07.2012


Ответы (1)


Ваш тип данных Expr не обязательно должен быть подъемным, чтобы вы могли построить для него квазицитатор, и в этом случае невозможно реализовать экземпляр Lift. Причина в том, что вы не можете «заглянуть внутрь» значения Runtime Expr, поскольку значения StateT по сути являются функциями, и нет общего способа узнать, что делает функция.

Что вам нужно сделать, так это построить AST, который создает Expr «вручную», то есть без использования [| |]-кавычки (или, чтобы быть более конкретным, вы можете использовать [| |]-кавычки, чтобы помочь вам построить AST, но вы не можете напрямую цитировать данные Expr).

Итак, по сути, вам нужен парсер типа

parseExpQ :: Parser ExpQ

который создает Exp, представляющий код Haskell, необходимый для сборки файла Expr. Для конструктора Fn вам нужно либо построить блок выполнения с помощью конструктора DoE, либо цепочку связывания с использованием InfixE и >>=.

Если это звучит слишком сложно, другой вариант — представить тело функции в виде выражения (или списка операторов, в зависимости от семантики вашего игрушечного языка). т.е.

data Expr = Num Int
          | Str T.Text
          | Fn [T.Text] [Statement]

И определите тип Statement, чтобы его можно было интерпретировать для создания тех же эффектов, для которых вы ранее использовали StateT Either.

person shang    schedule 20.07.2012
comment
Спасибо, думаю, понял. Однако я столкнулся с отдельной проблемой: в ghci запуск чего-либо вроде [toy| someExpr |] приводит к ошибке синтаксического анализа при вводе `|]', даже несмотря на то, что я настроил его для работы с TH (например, runQ [| what |] работает как положено). Есть идеи? - person Kben; 20.07.2012
comment
Вам нужно {-# LANGUAGE QuasiQuotes #-} вверху или ваш файл или -XQuasiQuotes в качестве опции компилятора. - person shang; 20.07.2012
comment
Ах, извините, я не заметил, что вы сказали GHCI. В GHCi вы можете включить языковые расширения: :set -XQuasiQuotes - person shang; 20.07.2012
comment
Понятно. У меня уже была языковая прагма, но мне все равно пришлось установить ее в ghci. - person Kben; 20.07.2012