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和 NeX/Ung>函数,这取决于<代码> x>代码>是正还是负:

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和 NeX/Ung>函数,这取决于<代码> x>代码>是正还是负:

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
包装器模拟正和负