Сокращение удовлетворенных ограничений для обычных типов

Я понимаю, что следующее семейство типов не должно и, возможно, не может быть реализовано в GHC:

type family MatchesConstraint c a :: Bool where
  MatchesConstraint c a is True if (c a)
  MatchesConstraint c a is False otherwise

Это проблематично, потому что классы открыты, поэтому MatchesConstraint c a может оценить True для некоторых частей программы и False в других, в зависимости от того, какие экземпляры находятся в сфере применения, что, я думаю, может быть довольно катастрофическим.

Но учтите следующее:

type family MatchesConstraint c a :: Bool where
  MatchesConstraint c a is True if (c a)
  MatchesConstraint c a doesn't reduce otherwise

Это кажется довольно безопасным. В некоторых частях нашей программы мы можем не уменьшить, если экземпляр не находится в области видимости, но у нас никогда не будет несоответствия.

Могу ли я сделать что-то подобное в GHC?

Причина, по которой я спрашиваю об этом, заключается в том, что, возможно, можно выбирать экземпляры, основываясь не только на непосредственном типе, но и на классе. Что может быть полезным в некоторых контекстах, я верю.

1 ответ

Я ответил на аналогичный вопрос от вас здесь, в кафе.

Как говорит @Carl, все экземпляры должны быть везде. Исключением является компиляция так называемых "экземпляров-сирот", что является плохой вещью и ее легко избежать.

Для записи здесь, подход состоит в том, чтобы использовать Ассоциированный тип в вашем классе с определением по умолчанию. Это работает, если вы искренне рады, что тип Associated сгенерировал ошибку "no instance", если нет соответствия ограничению:

class A t where
  type MatchesA t :: Bool           -- Associated type
  type instance MatchesA t = True   -- default instance
  ...                               -- methods for A

instance A Int where
                                    -- uses default instance for MatchesA
  ...                               -- method implementations as usual

-- undefined :: (MatchesA Int)      -- gives type True
-- undefined :: (MatchesA Bool)     -- gives 'no instance' error

Я думаю, вы можете получить ограничение - или Matches - см. Сообщение в кафе для MatchesA или MatchesB. (Я быстро это проверил, это может быть немного странно, в зависимости от того, насколько нетерпеливым является сокращение семейства типов.)

Что вы не можете сделать с этим подходом, так это выбрать одну вещь, если ограничение выполняется, и другую, если это не так. Таким образом, лучшее, что вы можете получить, это "не уменьшить". В посте кафе я ссылаюсь на (довольно старую) вики-страницу с более всесторонним подходом, который опирается на перекрывающиеся экземпляры. Связанные типы не допускают наложений:-(.

РЕДАКТИРОВАТЬ: Есть глубокая причина, почему компилятор не показывает, соответствует ли какое-либо ограничение, так, как вы хотите для Prelude классы в вашем комментарии: вы можете использовать факт соответствия, чтобы выбрать какой-то экземпляр другого класса C; и может быть еще один класс D чей выбор экземпляра зависит от C соответствия; а также A, BВыбор экземпляра зависит от D, Так что теперь у нас есть круговые зависимости. Это может быть осложнено другими экземплярами в других модулях, которые компилятор еще не видел.

Таким образом, все эти (очевидно) дублированные экземпляры на вики-странице представляют, что программист знает все экземпляры и как они взаимозависимы, таким образом, что компилятор не может понять.

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