当我想定义整数时,我尝试了这个
data Int = Pos Nat | Neg Nat
data Nat = Zero | Succ Nat
但在写下上述代码后,我发现可以构建Pos Zero
和Neg Zero
,这是不可取的。
当我想定义 Rational 时,再次出现同样的问题
data Rational = Rational Int Int
我期待任何Rational n m
,它应该满足m > 0 && gcd n m == 1
但我知道无法确保我的ADT数据满足这些属性。因此,当我编写一些将其作为输入处理的函数时,我必须考虑这些非法情况。 我希望有一些方法可以在定义ADT时定义一次属性,而不是每次使用时都检查属性。
答案 0 :(得分:4)
对于整数,这很容易:
data Nat = Zero | Positive Pos
data Pos = One | Succ Pos
data Int = Pos Nat | Neg Pos
或者您可以继续使用Int = Pos Nat | Neg Nat
,但要解释Neg n
代表-(n+1)
而不是-n
,因此“-0
”将无法实现。
对于理性而言,它更棘手。我认为通常最好的做法是将这些非规范化值保留在实现中,但语义上只考虑这些值的等价类,或者使用智能构造函数对它们进行规范化。这肯定是Rational
采取的方法
可以限制定义,因此只存在每个等价类的单个代表。这是how Agda defines the rationals:
record ℚ : Set where
field
numerator : ℤ
denominator-1 : ℕ
isCoprime : True (C.coprime? ∣ numerator ∣ (suc denominator-1))
denominator : ℤ
denominator = + suc denominator-1
coprime : Coprime numerator denominator
coprime = toWitness isCoprime
因此,它基本上只是在其依赖类型系统中编码gcd≡1条件。幸运的是那些拥有这种类型系统的人......
我不知道如何将这样的定义翻译成Haskell。应该可以以某种方式,但我怀疑这是可行的。
一个绝对可行但可能非常低效的解决方案是enumerate all rational numbers,并且只将该枚举的索引存储为单个Nat
。或者更确切地说,实施所有积极理性的Calkin-Wilf树:
data PosRational = UnitRatio
| RatSucc PosRational
| RecipSucc PosRational
有趣的是如何为此定义Num
等实例。我们看看......
instance Fractional PosRational where
recip UnitRatio = UnitRatio
recip (RecipSucc x) = RatSucc $ recip x
recip (RatSucc x) = RecipSucc $ recip x
instance Num PosRational where
UnitRatio + x = RatSucc x
x + UnitRatio = RatSucc x
RatSucc x + y = RatSucc $ x + y
x + RatSucc y = RatSucc $ x + y
RecipSucc (RecipSucc x) + RecipSucc (RecipSucc y)
-- = recip (1 + 1 + x) + recip (1 + 1 + y)
-- = (2+y + 2+x) / ((2+x)*(2+y))
-- = (4 + x + y) / (4 + 2*x + 2*y + x*y)
-- = 1 / (1 + (x+y+x*y)/(4+x+y))
= RecipSucc $ (4+x+y)/(x+y+x*y)
RecipSucc (RatSucc x) + RecipSucc (RatSucc y)
-- = recip (1 + recip (1+x)) + recip (1 + recip (1+y))
-- = (1+x) / (1+x + 1) + (1+y) / (1+y + 1)
-- = ((1+x)*(2+y)+(1+y)*(2+x)) / ((2+x)*(2+y))
-- = (2+2*x+y+x*y + 2+x+2*y+x*y) / (4+2*x+2*y+x*y)
-- = (4 + 3*x + 3*y + 2*x*y) / (4+2*x+2*y+x*y)
-- = (4+2*x+2*y+x*y + x+y+x*y) / (4+2*x+2*y+x*y)
-- = 1 + (x+y+x*y) / (4+2*x+2*y+x*y)
-- = 1 + 1 / (1 + (4+x+y)/(x+y+x*y))
= RatSucc . RecipSucc $ (4+x+y)/(x+y+x*y)
...
答案 1 :(得分:2)
在数学上,整数将从自然数中定义,方法与从整数定义的有理数相同:作为一对自然数,但差是定义因素。
data MyInt = I Nat Nat
-- I (Succ Zero) Zero is 1
-- I Zero (Succ Zero) is -1
现在,我们都遇到了同样的问题,即I
和Rational
都可以创建多个不同但等效的值。您可以忽略它并定义所有操作来处理它。以下是每个的简单Eq
个实例:
-- for reference
instance Eq Nat where
Zero == Zero = True
(Succ n1) == (Succ n2) = n1 == n2
_ == _ = False
-- No Num instance for Nat, since you can't negate a Nat
natAdd (Succ n) m = Succ (natAdd n m)
natAdd Zero m = m
instance Eq MyInt where
(I a b) == (I c d) = a `natAdd` d == b `natAdd` c
-- Assuming an appropriate Num MyInt instance for (*)
instance Eq Rational where
(Rational n1 d1) == (Rational n2 d2) = n1 * d2 == n2 * d1
对于其他操作,用于表示整数的实际值并不重要。以下是Num
的{{1}}实例的一部分,我们使用这两个实例之间的相对差异,而不是关心对子本身。
MyInt
或者,您可以避免直接使用构造函数,只允许通过智能构造函数创建值。这样,只能为每个整数/有理值创建一个规范表示。
instance Num MyInt where
negate (I a b) = I b a
signum (I a b) | a == b = I Zero
| a > b = I (Succ Zero) Zero
| otherwise = I Zero (Succ Zero)
abs (I a b) | a >= b = I a b
| otherwise = I b a
(I a b) + (I c d) == I (a `natAdd` c) (b `natAdd` d)
(I a b) * (I c d) == ... -- you get the idea