Я не верю, что это можно сделать легко (без TH) из-за предположения открытого мира: GHC в основном никогда не разрешит отрицание ограничения класса, потому что где-то может быть больше экземпляров, которые его делают true (и не очень хорошо играть с отдельной стратегией компиляции, которую использует GHC / Haskell). Таким образом, обычно невозможно - из чистого «обычного» кода на Haskell - решить, есть ли у типа экземпляр класса, и так или нет, включать его в список или нет.
Если вы хотите немного разбить отдельную компиляцию, рассматривая только те экземпляры, которые находятся в области действия, когда модуль, над которым вы работаете, скомпилирован (то есть находятся в области действия в исходном файле этого модуля), Вы можете использовать плагины Template Haskell или GHC typechecker, чтобы получить что-то очень похожее на это. Я знаю пару реализаций, делающих нечто похожее на уровне значений, включая ifcxt и constraints-emerge . Я полагаю, что эти библиотеки, особенно ifcxt (с которыми я немного знаком), довольно просты: вы можете использовать функцию TH reify
, чтобы получить ClassI
Info
для определенного класса типов, и использовать ее [InstanceDec]
поле, чтобы получить список всех экземпляров, которые находятся в области видимости во время компиляции. Затем вы можете сделать одну ветвь для каждого конкретного экземпляра типа, которая добавит заголовок экземпляра в список, и добавьте одну ветвь для всех типов, которая не будет. Вам также может понадобиться сделать это рекурсивно, чтобы иметь дело с экземплярами, которые сами имеют ограничения.
Обратите внимание, что если вы решите использовать этот подход, это нарушит предположение об открытом мире, что может привести к путанице: если модуль импортирует модуль фильтра на уровне типов, а затем определяет тип данных / экземпляр, фильтр на уровне типов не будет знать о новом экземпляре и будет продолжать обрабатывать тип так, как будто у него нет экземпляра. Вам нужно будет убедиться, что все экземпляры, которые вас интересуют, находятся в области действия при использовании TH для создания семейства типов фильтров.
Если вы хотите несколько улучшить это, вы можете использовать подход, еще более похожий на IfCxt
, где вместо непосредственного создания экземпляров семейства типов вы можете сделать что-то вроде этого:
class IsShow (a :: Type) (b :: Bool) where
instance {-# OVERLAPPABLE #-} (b ~ 'False) => IsShow a b where
И у вас есть такие экземпляры TH, как:
instance IsShow Int 'True where
Это имеет то преимущество, что если другой модуль определяет важные типы / экземпляры, вы должны иметь возможность (примерно) использовать тот же TH для расширения экземпляров IsShow
этими новыми экземплярами и вашими семействами типов, использующими IsShow
все должно быть в порядке. Связанный выше пакет ifcxt делает в основном то же самое, но вместо того, чтобы выполнять необходимую хитрость для получения информации на уровне типов, он просто генерирует функции для получения информации на уровне значений.
В этом решении вместо семейства типов используется класс с функциональными зависимостями, поскольку OverlappingInstances
дает возможность для решения на основе классов "случай по умолчанию". Я не уверен, есть ли какой-нибудь разумный способ присвоить семейству типов open регистр по умолчанию, поэтому вы не сможете получить эту «расширяемость» при использовании семейств типов везде (вместо fundep'd экземпляров). ).
Ричард Айзенберг говорит
При отдельной компиляции отсутствие упорядоченности и проверка перекрытия необходимы для устойчивости типа.
Так что я думаю, что это возможно. Здесь также есть несколько интересных дискуссий о семействах типов и fundeps: https://typesandkinds.wordpress.com/2015/09/09/what-are-type-families/