Преобразование произвольного ограничения класса `C a` в` C a Bool`

Таким образом, существует много преимуществ наличия типов в форме C a Bool. В основном потому, что они позволяют вам выполнять любую логическую операцию между двумя ограничениями, когда обычный C a просто неявно И все.

Если мы рассмотрим ~ ограничение класса, это можно сделать так:

class Equal x y b | x y -> b
instance Equal x x True
instance False ~ b => Equal x y b

Но особый особенностью этого случая является то, что размещение x x в голове экземпляра эквивалентно x ~ y =>, а затем x y в голове. Это не относится к любому другому классу. Поэтому, если мы попытаемся сделать что-то подобное для класса C, получим что-то вроде

class C' x b | x -> b
instance C x => C' x True
instance False ~ Bool => C' x b

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

Я также прочитал https://www.haskell.org/haskellwiki/GHC/AdvancedOverlap, который снова не применяется для любого класса C, потому что он требует переписать все экземпляры исходного класса, В идеале я бы хотел, чтобы мой код работал с GHC.Exts.Constraint и KindSignatures, так что C может быть параметрическим.

Итак, для класса, подобного этому

class Match (c :: * -> Constraint) x b | c x -> b

Как написать экземпляры, чтобы Match c x True тогда и только тогда, когда c x, Match c x False в противном случае?

Ответ 1

Это невозможно в Haskell из-за так называемого "Углеродного мира". В нем указано, что набор экземпляров для классов типов открыт, что означает, что вы можете создавать новые экземпляры в любое время (в отличие от закрытого мира, где должен быть фиксированный набор экземпляров). Например, в то время как класс Functor typeclass определен в Prelude, я все же могу сделать экземпляры для него в моем собственном коде, который не находится в Prelude.

Чтобы реализовать то, что вы предложили, компилятору нужен способ проверить, является ли тип T экземпляром класса C. Это, однако, требует, чтобы компилятор знал все возможные экземпляры этого класса, и это невозможно из-за предположения открытого мира (при компиляции Prelude компилятор еще не знает, что вы позже сделаете YourOwnFunctor экземпляр Functor тоже).

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

Если вы хотите закрытый мир, вы можете вместо этого использовать закрытые типы семейств, которые были введены в GHC 7.8. Используя их, вы можете написать:

type family Equal a b :: Bool where
  Equal x x = True
  Equal x y = False