Рассмотрим следующий тип данных:
data Get (statusCode :: Nat)
На самом деле это упрощённый конструктор типа из servant, который затем используется в API-интерфейсе уровня типа:
type API = "users" :> Verb 'GET 200 '[JSON] [User]
В наших целях мы можем сократить API до
type API = Get 200
Теперь ограничение кода статуса вида Nat
слишком слабое, что позволяет определить несуществующий код состояния HTTP:
type API = Get 999
Следовательно, вопрос: Есть ли способ ограничить набор naturals, которые могут быть применены к конструктору типа Get
?
Что было пробовано
Я пропущу все прагмы и импорт в образцах кода для ясности.
Другой тип для statusCode
Один очевидный способ исправить это - это определить отдельный код ADT для кодов состояния и использовать его вместо Nat
, используя продвижение типа данных.
data StatusCode = HTTP200 | HTTP201 | HTTP202
data Get (statusCode :: StatusCode)
Тем не менее, это перерыв в изменении, который требует, чтобы удар основной версии и переписать все определения пользователей. Я сомневаюсь, что преимущество ограниченных кодов стоит того.
DatatypeContexts
Это расширение позволяет иметь прямое ограничение для нашей переменной типа
data IsStatusCode statusCode => Get (statusCode :: Nat)
но для этого требуется, чтобы пользователи добавили ограничение во все свои объявления. Опять же, нарушение. Кроме того, DatatypeContexts
устарел.
Семейство типов
Мы могли условно создать Get'
из приведенного ниже примера с использованием семейств типов, но по какой-то причине объявление типа alias с удовольствием компилируется. Чтобы получить ошибку, нам нужно построить значение этого типа, что также является нарушением изменений.
data Get' (statusCode :: Nat) = Get
type family Get x where
Get x = If (x <=? 600) (Get' x) (TypeError (Text "Invalid Code"))
type API1 = Get 200
type API2 = Get 999 -- compiles.
api :: Get 999 -- doesn't compile.
api = Get