约束数据类型

时间:2014-10-20 13:50:39

标签: haskell types representation

假设您有一个很好的归纳定义,并且您希望将其定义为Haskell中的数据类型。然而,你的归纳定义是(正如许多归纳定义所述)这样一种形式,即生成规则要求它们的“前提”具有某种结构。例如,假设我们有以下定义:

  • 如果x是偶数,则T x是武器,
  • 如果x是一个奇数,那么S x就是武器。

如果我想在Haskell中定义这个(作为单个)数据类型,我会写一些像

data Weapon =  T Int | S Int

显然,这不起作用,因为您现在可以生成T 5S 4。是否有一种自然的方式来传递对构造函数参数的限制,以便我可以编写类似于上面代码的东西来给出正确的定义?

3 个答案:

答案 0 :(得分:10)

这有点不是Haskelly,但在例如Agda:改变你的表示的解释,以便通过构造强制它是正确的。

在这种情况下,请注意n :: Inteven (2 * n)odd (2 * n + 1)。如果我们动摇了Int s太大的情况,我们可以说偶数IntInt之间存在双射;以及奇数IntInt之间的另一个。

因此,使用此选项,您可以选择此表示形式:

data Weapon = T Int | S Int

并更改其解释,以使值T n实际代表T (2 * n),值S n代表S (2 * n + 1)。因此,无论您选择n :: IntT n都有效,因为您将其视为" T - of-2 * n"值。

答案 1 :(得分:9)

最好的方法是不要明确导出TS,而是允许自定义构造函数:

module YourModule (Weapon, smartConstruct) where

data Weapon =  T Int | S Int

smartConstruct :: Int -> Weapon
smartConstruct x
    | even x     = T x
    | otherwise  = S x

现在,在导入YourModule时,用户将无法明确地创建TS,但只能使用smartConstruct功能。

答案 2 :(得分:5)

如果你愿意限制自己使用Nats,并且可以使用合理的高级魔术,你可以使用GHC.TypeLits

{-# LANGUAGE DataKinds, GADTs, TypeOperators, KindSignatures #-}

import GHC.TypeLits

data Weapon (n :: Nat) where
    Banana      :: n ~ (2 * m) => Weapon n
    PointyStick :: n ~ (2 * m + 1) => Weapon n

banana :: Weapon 2
banana = Banana

pointyStick :: Weapon 3
pointyStick = PointyStick

尝试自己,它不会用错误的(奇数/偶数)编译。

编辑:更实用的可能是仙人掌'接近但是。