Шаблон Haskell: ограничение этапа GHC и как его преодолеть

У меня есть следующий код в модуле:

{-# LANGUAGE TemplateHaskell #-}

module Alpha where

import Language.Haskell.TH
import Data.List

data Alpha = Alpha { name :: String, value :: Int } deriving (Show)
findName n = find ((== n) . name)

findx obj = sequence [valD pat bod []]
    where
        nam = name obj
        pat = varP (mkName $ "find" ++ nam)
        bod = normalB [| findName nam |]

И тогда у меня есть следующее в основном файле:

{-# LANGUAGE TemplateHaskell #-}

import Alpha

one   = Alpha "One" 1
two   = Alpha "Two" 2
three = Alpha "Three" 3
xs    = [one, two , three]

findOne = findName "One"
findTwo = findName "Two"

$(findx three) -- This Fails
$(findx (Alpha "Four" 4)) -- This Works

main = putStrLn "Done"

Я хотел бы $(findx three) создавать findThree = findName "Three" для меня. Но вместо этого я получаю эту ошибку:

GHC stage restriction: `three'
  is used in a top-level splice or annotation,
  and must be imported, not defined locally
In the first argument of `findx', namely `three'
In the expression: findx three

Как мне преодолеть это? Я бы предпочел не определять one, twoи т. д. в отдельном файле.

Второй вопрос: почему $(findx (Alpha "Four" 4)) работать без проблем?

1 ответ

Я не очень разбираюсь в Template Haskell, но, исходя из моего ограниченного понимания, проблема в том, что three в некотором смысле "все еще определяется", когда GHC пытается скомпилировать $(findx three)в то время как все составляющие части $(findx (Alpha "Four" 4)) уже полностью определены.

Основная проблема заключается в том, что все определения в одном модуле влияют на значение друг друга. Это связано с выводом типа, а также взаимной рекурсией. Определение x = [] может означать много разных вещей, в зависимости от контекста; это может быть обязательным x к списку Intили список IO ()или что-нибудь еще. GHC, возможно, придется обработать весь модуль, чтобы выяснить, что именно он означает (или что это на самом деле ошибка).

Код, который Template Haskell создает в компилируемом модуле, должен учитываться этим анализом. Это означает, что код Template Haskell должен быть запущен до того, как GHC выяснит, что означают определения в модуле, и поэтому логически вы не можете использовать ни одно из них.

Вещи, которые были импортированы из других модулей OTOH, уже были полностью проверены, когда GHC скомпилировал этот модуль. Больше нет информации, которую нужно узнать о них, скомпилировав этот модуль. Таким образом, они могут быть доступны и использованы до компиляции кода в этом модуле.

Еще один способ думать об этом: возможно three на самом деле не должно быть типа Alpha, Может быть, это была опечатка, и конструктор должен был Alphz, Обычно GHC узнает об этих видах ошибок, компилируя весь другой код в модуле, который использует three чтобы увидеть, вносит ли это несоответствие или нет. Но что, если этот код использует или используется вещами, которые испускаются только $(findx three)? Мы даже не знаем, что это будет за код, пока не запустим его, но мы не можем решить вопрос о том, three правильно набран до тех пор, пока мы не запустим его.

Конечно, в некоторых случаях можно было бы немного снять это ограничение (я понятия не имею, будет ли это легко или практично). Возможно, мы могли бы заставить GHC считать что-то "определенным раньше", если импортируется, или если он использует только другие вещи, которые "определены рано" (и, возможно, имеет явную сигнатуру типа). Возможно, он может попытаться скомпилировать модуль без запуска кода TH, и если ему удастся выполнить полную проверку типов three перед тем, как возникнут какие-либо ошибки, он может передать это в код TH, а затем перекомпилировать все. Недостатком (помимо связанной с этим работы) было бы намного сложнее указать, какие именно ограничения существуют для того, что вы можете передать в Template Haskell.

Другие вопросы по тегам