Сокращение удовлетворенных ограничений для обычных типов
Я понимаю, что следующее семейство типов не должно и, возможно, не может быть реализовано в 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
, Так что теперь у нас есть круговые зависимости. Это может быть осложнено другими экземплярами в других модулях, которые компилятор еще не видел.
Таким образом, все эти (очевидно) дублированные экземпляры на вики-странице представляют, что программист знает все экземпляры и как они взаимозависимы, таким образом, что компилятор не может понять.