为什么在函数定义中首选模式匹配?
Why is pattern matching preferred in function definitions?
我正在阅读 learnyouahaskell 的 "learnyouahaskell" 教程。上面写着:
Pattern matching can also be used on tuples. What if we wanted to make
a function that takes two vectors in a 2D space (that are in the form
of pairs) and adds them together? To add together two vectors, we add
their x
components separately and then their y
components
separately. Here's how we would have done it if we didn't know about
pattern matching:
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors a b = (fst a + fst b, snd a + snd b)
Well, that works, but there's a better way to do it. Let's modify the
function so that it uses pattern matching.
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
There we go! Much better. Note that this is already a catch-all
pattern. The type of addVectors
(in both cases) is addVectors :: (Num a) => (a, a) -> (a, a) - > (a, a)
, so we are guaranteed to get
two pairs as parameters.
我的问题是:如果两个定义产生相同的签名,为什么模式匹配是首选方式?
正如 Carsten 在评论中提到的,这是一个基于意见的问题,但无论如何让我详细说明。
对 2 元组使用模式匹配并没有太大优势,但让我们考虑一些更大的数据结构,例如 4 元组。
addVectors :: (Num a) => (a, a, a, a) -> (a, a, a, a) -> (a, a, a, a)
addVectors a b = -- some code that adds vectors
addVectors :: (Num a) => (a, a, a, a) -> (a, a, a, a) -> (a, a, a, a)
addVectors (w1, x1, y1, z1) (w2, x2, y2, z2) = (w1 + w2, x1 + x2, y1 + y2, z1 + z2)
如果没有模式匹配,您将不得不编写从 4 元组中提取第一、第二、第三和第四个元素的函数,并在 addVectors
中使用它。有了模式匹配,写 addVectors
的实现就很容易了。
我相信在书中使用这样的例子可以更有效地传达信息。
简而言之,需要构造和破坏值。
值是通过采用数据构造函数(可能是 null-ary)函数并应用所需的参数来构造的。到目前为止,还不错。
随机示例(滥用GADTSyntax
)
data T where
A :: Int -> T
B :: T
C :: String -> Bool -> T
销毁更复杂,因为需要获取类型 T
的值并获取以下信息:1) 使用哪个构造函数来制作此类值,以及 2) 所述构造函数的参数是什么。
第 1 部分可以通过函数完成:
whichConsT :: T -> Int -- returns 0,1,2 for A,B,C
第 2 部分更棘手。一个可能的选择是使用 projections
projA :: T -> Int
-- projB not needed
projC1 :: T -> String
projC2 :: T -> Bool
例如他们满足
projA (A n) = n
projC1 (C x y) = x
projC2 (C x y) = y
但是等等!投影的类型采用 T -> ...
形式,它承诺此类函数适用于类型 T
的所有值。所以我们可以
projA B = ??
projA (C x y) = ??
projC1 (A n) = ??
如何实现以上?无法产生合理的结果,因此最好的选择是触发运行时错误。
projA B = error "not an A!"
projA (C x y) = error "not an A!"
projC1 (A n) = error "not a C!"
但是,这给程序员带来了负担!现在程序员有责任检查传递给投影的值是否具有正确的构造函数。这可以使用 whichConsT
来完成。许多命令式程序员习惯于这种接口(测试和访问,例如迭代器中的 Java 的 hasNext(), next()
),但这是因为大多数命令式语言没有更好的选择。
FP 语言(以及现在的一些命令式语言)也允许模式匹配。与投影相比,使用它具有以下优势:
- 无需拆分信息:我们同时得到 1) 和 2)
- 无法使程序崩溃:我们从不使用可能导致崩溃的部分投影函数
- 程序员没有负担:上述推论
- 如果穷举检查器打开,我们一定会处理所有可能的情况
现在,在具有恰好 一个 构造函数(元组、()
、newtype
s)的类型上,可以定义总投影,这非常好(例如 fst,snd
)。尽管如此,许多人还是喜欢坚持使用模式匹配,它也可以处理一般情况。
我认为在这种情况下,模式匹配更直接地表达了您的意思。
在函数应用案例中,需要知道fst
和snd
是做什么的,从中推导出a
和b
是元组,其元素得到已添加。
addVectors a b = (fst a + fst b, snd a + snd b)
我们有 snd
和 fst
函数来分解元组的事实在这里让人分心。
在模式匹配的情况下,可以立即清楚输入是什么(一个元组,其元素我们称为 x1
和 y1
以及一个元组...等等)以及它是如何被解构的。而且还可以立即清楚发生了什么,它们的元素是如何添加的。
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
这几乎就像数学定义:
(x1, y1) + (x2, y2) := (x1 + x2, y1 + y2)
开门见山,不要分心:-)
你完全可以在 Haskell 中这样写:
(x₁, y₁) `addVector` (x₂, y₂) = (x₁ + x₂, y₁ + y₂)
我正在阅读 learnyouahaskell 的 "learnyouahaskell" 教程。上面写着:
Pattern matching can also be used on tuples. What if we wanted to make a function that takes two vectors in a 2D space (that are in the form of pairs) and adds them together? To add together two vectors, we add their
x
components separately and then theiry
components separately. Here's how we would have done it if we didn't know about pattern matching:addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a) addVectors a b = (fst a + fst b, snd a + snd b)
Well, that works, but there's a better way to do it. Let's modify the function so that it uses pattern matching.
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a) addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
There we go! Much better. Note that this is already a catch-all pattern. The type of
addVectors
(in both cases) isaddVectors :: (Num a) => (a, a) -> (a, a) - > (a, a)
, so we are guaranteed to get two pairs as parameters.
我的问题是:如果两个定义产生相同的签名,为什么模式匹配是首选方式?
正如 Carsten 在评论中提到的,这是一个基于意见的问题,但无论如何让我详细说明。
对 2 元组使用模式匹配并没有太大优势,但让我们考虑一些更大的数据结构,例如 4 元组。
addVectors :: (Num a) => (a, a, a, a) -> (a, a, a, a) -> (a, a, a, a)
addVectors a b = -- some code that adds vectors
addVectors :: (Num a) => (a, a, a, a) -> (a, a, a, a) -> (a, a, a, a)
addVectors (w1, x1, y1, z1) (w2, x2, y2, z2) = (w1 + w2, x1 + x2, y1 + y2, z1 + z2)
如果没有模式匹配,您将不得不编写从 4 元组中提取第一、第二、第三和第四个元素的函数,并在 addVectors
中使用它。有了模式匹配,写 addVectors
的实现就很容易了。
我相信在书中使用这样的例子可以更有效地传达信息。
简而言之,需要构造和破坏值。
值是通过采用数据构造函数(可能是 null-ary)函数并应用所需的参数来构造的。到目前为止,还不错。
随机示例(滥用GADTSyntax
)
data T where
A :: Int -> T
B :: T
C :: String -> Bool -> T
销毁更复杂,因为需要获取类型 T
的值并获取以下信息:1) 使用哪个构造函数来制作此类值,以及 2) 所述构造函数的参数是什么。
第 1 部分可以通过函数完成:
whichConsT :: T -> Int -- returns 0,1,2 for A,B,C
第 2 部分更棘手。一个可能的选择是使用 projections
projA :: T -> Int
-- projB not needed
projC1 :: T -> String
projC2 :: T -> Bool
例如他们满足
projA (A n) = n
projC1 (C x y) = x
projC2 (C x y) = y
但是等等!投影的类型采用 T -> ...
形式,它承诺此类函数适用于类型 T
的所有值。所以我们可以
projA B = ??
projA (C x y) = ??
projC1 (A n) = ??
如何实现以上?无法产生合理的结果,因此最好的选择是触发运行时错误。
projA B = error "not an A!"
projA (C x y) = error "not an A!"
projC1 (A n) = error "not a C!"
但是,这给程序员带来了负担!现在程序员有责任检查传递给投影的值是否具有正确的构造函数。这可以使用 whichConsT
来完成。许多命令式程序员习惯于这种接口(测试和访问,例如迭代器中的 Java 的 hasNext(), next()
),但这是因为大多数命令式语言没有更好的选择。
FP 语言(以及现在的一些命令式语言)也允许模式匹配。与投影相比,使用它具有以下优势:
- 无需拆分信息:我们同时得到 1) 和 2)
- 无法使程序崩溃:我们从不使用可能导致崩溃的部分投影函数
- 程序员没有负担:上述推论
- 如果穷举检查器打开,我们一定会处理所有可能的情况
现在,在具有恰好 一个 构造函数(元组、()
、newtype
s)的类型上,可以定义总投影,这非常好(例如 fst,snd
)。尽管如此,许多人还是喜欢坚持使用模式匹配,它也可以处理一般情况。
我认为在这种情况下,模式匹配更直接地表达了您的意思。
在函数应用案例中,需要知道fst
和snd
是做什么的,从中推导出a
和b
是元组,其元素得到已添加。
addVectors a b = (fst a + fst b, snd a + snd b)
我们有 snd
和 fst
函数来分解元组的事实在这里让人分心。
在模式匹配的情况下,可以立即清楚输入是什么(一个元组,其元素我们称为 x1
和 y1
以及一个元组...等等)以及它是如何被解构的。而且还可以立即清楚发生了什么,它们的元素是如何添加的。
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
这几乎就像数学定义:
(x1, y1) + (x2, y2) := (x1 + x2, y1 + y2)
开门见山,不要分心:-)
你完全可以在 Haskell 中这样写:
(x₁, y₁) `addVector` (x₂, y₂) = (x₁ + x₂, y₁ + y₂)