如何将约束添加到类型构造函数

时间:2016-09-06 08:28:59

标签: haskell servant

考虑以下数据类型:

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)

然而,这是一个突破性的变化,需要突破主要版本并重写所有用户'定义。我怀疑限制代码的好处是值得的。

DatatypeContexts

此扩展允许对我们的类型变量

进行直接约束
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

1 个答案:

答案 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没有。