Haskell: шаблон Haskell и область действия

Этот код скомпилирован в порядке:

data None = None { _f :: Int }
type Simpl = Env

type Env = Int

Однако я получил ошибку с этим кодом:

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

data None = None { _f :: Int }

type Simpl = Env

makeLenses ''None

type Env = Int

Ошибка:

Not in scope: type constructor or class `Env'

Я просто добавил одну строку makeLenses ''None между объявлениями типа.
Это означает, что код TemplateHaskell может изменить область конструктора типов?

Кто-нибудь знает подробности об этой проблеме (или как избежать этой проблемы)?

Ответ 1

Если вы измените порядок своего кода следующим образом, он будет работать:

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

data None = None { _f :: Int }

type Simpl = Env

type Env = Int

makeLenses ''None

Когда вы используете сращивания Template Haskell для добавления новых объявлений верхнего уровня в ваш код, как это делает makeLenses, порядок объявлений в вашем коде имеет значение!

Причина в том, что обычно компиляция программы Haskell предполагает сначала собирать все объявления верхнего уровня и переупорядочивать их внутри, чтобы поместить их в порядок зависимостей, а затем компилировать их один за другим (или группировать по группам для взаимно-рекурсивных объявлений).

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

Единственная онлайн-ссылка, которую я могу найти, объясняет это в оригинальной бумаге шаблона Haskell, раздел 7.2, где говорится, что алгоритм

  • Группируйте объявления следующим образом:
[d1,...,da]
splice ea
[da+2,...,db]
splice eb
...
splice ez
[dz+2,...,dN]

где единственными объявлениями сплайсинга являются те, которые указаны явно, так что каждая группа [d1,...,da] и т.д. - все обычные объявления Haskell.

  • Выполните обычный анализ зависимостей, а затем проверку типов в первой группе. Все его свободные переменные должны быть в объеме.

Таким образом, проблема заключается в том, что первая группа объявлений перед сращиванием обрабатывается отдельно во вторую группу после сращивания и не может видеть определение Env.

Мое общее правило заключается в том, чтобы по возможности вставлять такие сращивания в нижнюю часть файла, но я не думаю, что это гарантирует, что это всегда будет работать.