假设您有一个很好的归纳定义,并且您希望将其定义为Haskell中的数据类型。然而,你的归纳定义是(正如许多归纳定义所述)这样一种形式,即生成规则要求它们的“前提”具有某种结构。例如,假设我们有以下定义:
x
是偶数,则T x
是武器,x
是一个奇数,那么S x
就是武器。如果我想在Haskell中定义这个(作为单个)数据类型,我会写一些像
data Weapon = T Int | S Int
显然,这不起作用,因为您现在可以生成T 5
和S 4
。是否有一种自然的方式来传递对构造函数参数的限制,以便我可以编写类似于上面代码的东西来给出正确的定义?
答案 0 :(得分:10)
这有点不是Haskelly,但在例如Agda:改变你的表示的解释,以便通过构造强制它是正确的。
在这种情况下,请注意n :: Int
,even (2 * n)
和odd (2 * n + 1)
。如果我们动摇了Int
s太大的情况,我们可以说偶数Int
和Int
之间存在双射;以及奇数Int
和Int
之间的另一个。
因此,使用此选项,您可以选择此表示形式:
data Weapon = T Int | S Int
并更改其解释,以使值T n
实际代表T (2 * n)
,值S n
代表S (2 * n + 1)
。因此,无论您选择n :: Int
,T n
都有效,因为您将其视为" T
- of-2 * n"值。
答案 1 :(得分:9)
最好的方法是不要明确导出T
和S
,而是允许自定义构造函数:
module YourModule (Weapon, smartConstruct) where
data Weapon = T Int | S Int
smartConstruct :: Int -> Weapon
smartConstruct x
| even x = T x
| otherwise = S x
现在,在导入YourModule
时,用户将无法明确地创建T
和S
,但只能使用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
尝试自己,它不会用错误的(奇数/偶数)编译。
编辑:更实用的可能是仙人掌'接近但是。