考虑以下数据类型:
data Get (statusCode :: Nat)
实际上,它是一个简化的type constructor from servant,然后在类型级API中使用,如下所示:
type API = "users" :> Verb 'GET 200 '[JSON] [User]
出于我们的目的,我们可以将API缩减为
type API = Get 200
现在,对状态代码的类型Nat
的限制太宽松,允许定义不存在的HTTP状态代码:
type API = Get 999
因此,问题:有没有办法限制可以应用于Get
类型构造函数的自然集?
为了清楚起见,我省略了代码示例中的所有编译指示和导入。
statusCode
解决这个问题的一个明显方法是为状态代码定义一个单独的ADT,并使用它代替Nat
利用数据类型促销。
data StatusCode = HTTP200 | HTTP201 | HTTP202
data Get (statusCode :: StatusCode)
然而,这是一个突破性的变化,需要突破主要版本并重写所有用户'定义。我怀疑限制代码的好处是值得的。
此扩展允许对我们的类型变量
进行直接约束data IsStatusCode statusCode => Get (statusCode :: Nat)
但它要求用户将约束添加到其所有声明中。再次,一个突破性的变化。此外,不推荐使用DatatypeContexts
。
我们可以使用类型系列从下面的示例中有条件地创建Get'
,但由于某种原因,声明类型别名可以快速编译。为了得到一个错误,我们需要构造一个这种类型的值,这也是一个突破性的变化。
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
答案 0 :(得分:1)
我将从一个解决方案开始,然后讨论其他可能性(似乎没有淘汰):
{-# LANGUAGE TypeOperators, TypeInType, GADTs #-}
import GHC.TypeLits (Nat, type (<=))
import Data.Proxy (Proxy(..))
import Data.Kind (type (*))
data Get (statusCode :: Nat) :: (statusCode <= 600) => *
type API1 = Get 900 -- compiles
type API2 = Get 200 -- compiles
api1 :: Proxy (Get 900) -- doesn't compile
api1 = Proxy
api2 :: Proxy (Get 200) -- compiles
api2 = Proxy
不需要任何类型的家庭,也不需要下降到价值水平。然而,类型同义词将编译得很好。 使用无效Get
的类型同义词将导致使用站点的编译时崩溃。我认为这是一个很好的解决方案,你可以期待。如果有任何不清楚的地方,请告诉我。
接下来,只是对其他方法的一些想法:
DatatypeContexts
这个永远不会工作:除了被弃用之外,所有这一切都是为构造函数添加约束。你明确指出你不想要构造任何类型的东西,所以这是毫无意义的。新的GADT语法修复了这种歧义 - 约束现在明确地绑定到数据或类型构造函数。
TypeError
我相信这应该有效,而且不必构造类型的值。 (所以以下情况应该没问题。)
data Get' (statusCode :: Nat)
type family Get x where
Get x = If (x <=? 600) (Get' x) (TypeError (Text "Invalid Code"))
api2 :: Proxy (Get 200) -- should compile.
api2 = Proxy
api1 :: Proxy (Get 999) -- shouldn't compile, but currently does
api1 = Proxy
我有动力相信这应该基于此trac自动收录器中的example。这里的东西显然不应该表现得如此:即使我用If
本身替换对类型函数TypeError
的调用,仍然没有触发任何东西 - 看来TypeErrors
不是'{1}}顶级仍然会导致一些问题。
另一方面,我真的不确定TypeError
同义词中type
的行为应该,但我倾向于说{{1}不应该编译,因为type API1 = Get 999
没有。