Вы можете сделать что-то подобное в (я думаю) достаточно чистом способе, используя семейство типов вместе с ConstraintKinds
и PolyKinds
:
type family Union (a :: [k]) (r :: k) :: Constraint where
Union (x ': xs) x = ()
Union (x ': xs) y = Union xs y
test1 :: Union [Circle', Triangle'] s => Shape s -> Int
test1 = undefined
Выше ()
является пустым ограничением(это похоже на пустой «список» ограничений класса типов).
В первом «уравнении» семейства типов используется нелинейное сопоставление с образцом, доступное в семействах типов (дважды слева используется x
)со стороны).Семейство типов также использует тот факт, что, если ни один из случаев не совпадает, оно не даст вам действительного ограничения.
Вы также сможете использовать логический тип уровня вместо ConstraintKinds
.Это было бы немного более громоздко, и я думаю, что было бы лучше избегать здесь использования логического уровня на уровне типов (если вы можете).
Примечание (я никогда не могу вспомнить это, и мне пришлось посмотреть его)на этот ответ): вы получаете Constraint
в области видимости, импортируя его из GHC.Exts
.
Редактировать: Частичное запрещение недоступных определений
Вот модификация, чтобы получить его (частично) запрещать недостижимые определения, а также недействительные вызовы.Это немного более круговое движение, но, похоже, это работает.
Измените Union
, чтобы получить *
вместо ограничения, например:
type family Union (a :: [k]) (r :: k) :: * where
Union (x ': xs) x = ()
Union (x ': xs) y = Union xs y
Это не имеет значенияслишком много, что это за тип, если у него есть обитатель, с которым вы можете сопоставить образец, поэтому я возвращаю тип ()
(тип устройства).
Вот как вы его используете:
test1 :: Shape s -> Union [Circle', Triangle'] s -> Int
test1 Circle {} () = undefined
test1 Triangle {} () = undefined
-- test1 Square {} () = undefined -- This line won't compile
Если вы забыли сопоставить его (например, если вместо конструктора ()
вместо имени сопоставить имя переменной, например, x
), возможно, будет определен недостижимый случай.Тем не менее, он все равно выдаст ошибку типа на сайте вызова, когда вы на самом деле попытаетесь достичь этого случая (поэтому, даже если вы не соответствуете аргументу Union
, вызов test1 (Square undefined) ()
не будет проверять тип).
Обратите внимание, что, по-видимому, аргумент Union
должен идти после аргумента Shape
, чтобы это работало (в любом случае полностью, как описано).