Haskell 模式匹配成语
Haskell pattern-matching idiom
我正在制作一个抽象整数计算器,并且我正在做大量的模式匹配。我会写
add Zero x = x
add (P x) y = next $ add (prev $ P x) y
add (N x) y = prev $ add (next $ N x) y
或
add Zero x = x
add x y = case x of
P _ -> next $ add (prev x) y
_ -> prev $ add (next x) y
虽然第一种方式更短,但第二种方式更吸引我。
执行此操作的首选方法是什么?
使用as-patterns.
add Zero y = y
add x@(P _) y = next $ add (prev x) y
add x@(N _) y = prev $ add (next x) y
我还考虑抽象出你的两个递归分支的共同结构,注意你只是根据是否 x
交换 prev
和 next
函数的角色是正数还是负数:
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
从积极的方面来说,它避免了一些重复,这很好。
从消极方面看,case
乍一看似乎并不详尽。事实上,为了说服自己模式匹配真的很详尽,我们必须推理 case x of
中 x
的可能值,并在运行时看到不能 Zero
,因为那是上面处理的。这比第一个片段需要更多的脑力劳动,显然详尽无遗。
更糟糕的是,当打开警告时,正如我们应该经常做的那样,GHC 会抱怨,因为它不相信 case
是详尽无遗的。
就我个人而言,我希望 Haskell 的设计者完全禁止非穷举匹配。如果有的话,我会使用 -Werror-on-non-exhaustive-matches
。我想被迫写例如
case something of
A -> ...
B -> ...
_ -> error "the impossible happened"
而不是编译器为我悄悄插入最后一个分支。
考虑使用 math-style definition of integers 作为等价关系下自然数对的全等 类:
{((a,b), (c,d)) | b+c == d+a}
直觉是一对自然 (a,b)
代表 b-a
。正如维基百科文章中提到的,与“0/positive/negative”定义相比,这通常会减少特殊情况的数量。特别是,您要求实现的加法操作变成了一行:
-- 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
我正在制作一个抽象整数计算器,并且我正在做大量的模式匹配。我会写
add Zero x = x
add (P x) y = next $ add (prev $ P x) y
add (N x) y = prev $ add (next $ N x) y
或
add Zero x = x
add x y = case x of
P _ -> next $ add (prev x) y
_ -> prev $ add (next x) y
虽然第一种方式更短,但第二种方式更吸引我。
执行此操作的首选方法是什么?
使用as-patterns.
add Zero y = y
add x@(P _) y = next $ add (prev x) y
add x@(N _) y = prev $ add (next x) y
我还考虑抽象出你的两个递归分支的共同结构,注意你只是根据是否 x
交换 prev
和 next
函数的角色是正数还是负数:
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
从积极的方面来说,它避免了一些重复,这很好。
从消极方面看,case
乍一看似乎并不详尽。事实上,为了说服自己模式匹配真的很详尽,我们必须推理 case x of
中 x
的可能值,并在运行时看到不能 Zero
,因为那是上面处理的。这比第一个片段需要更多的脑力劳动,显然详尽无遗。
更糟糕的是,当打开警告时,正如我们应该经常做的那样,GHC 会抱怨,因为它不相信 case
是详尽无遗的。
就我个人而言,我希望 Haskell 的设计者完全禁止非穷举匹配。如果有的话,我会使用 -Werror-on-non-exhaustive-matches
。我想被迫写例如
case something of
A -> ...
B -> ...
_ -> error "the impossible happened"
而不是编译器为我悄悄插入最后一个分支。
考虑使用 math-style definition of integers 作为等价关系下自然数对的全等 类:
{((a,b), (c,d)) | b+c == d+a}
直觉是一对自然 (a,b)
代表 b-a
。正如维基百科文章中提到的,与“0/positive/negative”定义相比,这通常会减少特殊情况的数量。特别是,您要求实现的加法操作变成了一行:
-- 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