Haskell模式匹配习语
我正在做一个关于抽象整数的计算器,我正在做大量的模式匹配。我会写字Haskell模式匹配习语,haskell,idioms,Haskell,Idioms,我正在做一个关于抽象整数的计算器,我正在做大量的模式匹配。我会写字 add Zero x = x add (P x) y = next $ add (prev $ P x) y add (N x) y = prev $ add (next $ N x) y 或 虽然第一种方式比较短,但第二种方式更吸引我 哪种方法是首选方法?使用 我也会考虑抽象出你的两个递归分支的公共结构,注意到你只需交换代码< > PREV和 NeX/Ung>函数,这取决于 x>代码>是正还是负: add Zero x =
add Zero x = x
add (P x) y = next $ add (prev $ P x) y
add (N x) y = prev $ add (next $ N x) y
或
虽然第一种方式比较短,但第二种方式更吸引我
哪种方法是首选方法?使用
我也会考虑抽象出你的两个递归分支的公共结构,注意到你只需交换代码< > PREV和
add Zero x = x
add x y = f $ add (g x) y
where (f, g) = case x of
P _ -> (next, prev)
N _ -> (prev, next)
使用
我也会考虑抽象出你的两个递归分支的公共结构,注意到你只需交换代码< > PREV和
add Zero x = x
add x y = f $ add (g x) y
where (f, g) = case x of
P _ -> (next, prev)
N _ -> (prev, next)
关于这种风格:
add Zero x = x
add x y = case x of
P _ -> next $ add (prev x) y
_ -> prev $ add (next x) y
从积极的方面来说,它避免了一些重复,这是好的
从负面来看,案例
乍一看似乎并非详尽无遗。事实上,为了让自己相信模式匹配确实是详尽无遗的,我们必须在的案例x中对x的可能值进行推理,并看到在运行时不能是零,因为这是上面处理的。这比第一个片段需要更多的脑力劳动,而第一个片段显然是详尽无遗的
更糟糕的是,当打开警告时(我们应该一直这样做),GHC会抱怨,因为它不相信案例
是详尽无遗的
就我个人而言,我希望Haskell的设计师完全禁止非详尽的比赛。如果有,我会在非穷举匹配上使用-Werror
。我想被迫写作
case something of
A -> ...
B -> ...
_ -> error "the impossible happened"
而不是让编译器为我默默插入最后一个分支。关于此样式:
add Zero x = x
add x y = case x of
P _ -> next $ add (prev x) y
_ -> prev $ add (next x) y
从积极的方面来说,它避免了一些重复,这是好的
从负面来看,案例
乍一看似乎并非详尽无遗。事实上,为了让自己相信模式匹配确实是详尽无遗的,我们必须在的案例x中对x的可能值进行推理,并看到在运行时不能是零,因为这是上面处理的。这比第一个片段需要更多的脑力劳动,而第一个片段显然是详尽无遗的
更糟糕的是,当打开警告时(我们应该一直这样做),GHC会抱怨,因为它不相信案例
是详尽无遗的
就我个人而言,我希望Haskell的设计师完全禁止非详尽的比赛。如果有,我会在非穷举匹配上使用-Werror
。我想被迫写作
case something of
A -> ...
B -> ...
_ -> error "the impossible happened"
而不是让编译器为我默默地插入最后一个分支。考虑在等价关系下使用as同余类作为自然数对:
{((a,b), (c,d)) | b+c == d+a}
直觉是这对自然的(a,b)
代表b-a
。正如维基百科文章中提到的,与“0/正/负”定义相比,这通常减少了特殊情况的数量。特别是,您询问的关于实现的添加操作变成了一行:
-- both Int and Integer are taken
data Int' = Int Nat Nat
instance Num Int' where
-- b-a + d-c = (b+d)-(a+c)
Int a b + Int c d = Int (a + c) (b + d)
使用这种表示法完成不同的操作是一种乐趣。例如,Eq
可以用上面给出的等式实现,而Ord
类似:
instance Eq Int' where
-- b-a == d-c = b+c == d+a
Int a b == Int c d = b+c == d+a
instance Ord Int' where
-- compare (b-a) (d-c) = compare (b+c) (d+a)
compare (Int a b) (Int c d) = compare (b+c) (d+a)
有时,将这些东西正常化是很方便的。就像分数可以通过将分子和分母乘以相同的数字来减少,直到它们是相对素数一样,这些东西也可以通过将相同的数字加到或减去两个部分,直到(至少)其中一个为零来减少
normalize (Int (S a) (S b)) = normalize (Int a b)
normalize v = v
在等价关系下,考虑使用as同余类作为自然对:
{((a,b), (c,d)) | b+c == d+a}
直觉是这对自然的(a,b)
代表b-a
。正如维基百科文章中提到的,与“0/正/负”定义相比,这通常减少了特殊情况的数量。特别是,您询问的关于实现的添加操作变成了一行:
-- both Int and Integer are taken
data Int' = Int Nat Nat
instance Num Int' where
-- b-a + d-c = (b+d)-(a+c)
Int a b + Int c d = Int (a + c) (b + d)
使用这种表示法完成不同的操作是一种乐趣。例如,Eq
可以用上面给出的等式实现,而Ord
类似:
instance Eq Int' where
-- b-a == d-c = b+c == d+a
Int a b == Int c d = b+c == d+a
instance Ord Int' where
-- compare (b-a) (d-c) = compare (b+c) (d+a)
compare (Int a b) (Int c d) = compare (b+c) (d+a)
有时,将这些东西正常化是很方便的。就像分数可以通过将分子和分母乘以相同的数字来减少,直到它们是相对素数一样,这些东西也可以通过将相同的数字加到或减去两个部分,直到(至少)其中一个为零来减少
normalize (Int (S a) (S b)) = normalize (Int a b)
normalize v = v
prev
看起来像prev(nx)=x;prev x=px
,那么它不应该是add(px)y=prev$add xy
?实际上它要复杂得多;我有<代码>数据abt= 0p'nt'nNA/<代码>其中“代码>数据NAT=一个NAT< <代码>,分别用“代码> P<代码/代码>和<代码> S/<代码>包装器模拟正、负,因此<代码> PREV(Nx)=N$ SX < /代码>等等。您可能需要考虑使用“代码>数据NAT=零-S NAT < /代码>将零包含为自然数。这使得data Int'=Z Nat Nat
的定义更加不透明,但更易于使用,因为您不再需要明确区分正整数、负整数和零整数。例如,next(zab)=zab
和prev(zab)=zab
。更好的是,对于适当定义的natAdd::Nat->Nat->Nat->Nat
prev
看起来像prev(nx)=x;prev x=px
,那么它不应该是add(px)y=prev$add xy
?实际上它要复杂得多;我有data-AbInt=Zero | P-Nat | N-Nat
其中data-Nat=One | S-Nat
,用P
和S
包装器模拟正和负