【问题标题】:Why is pattern matching preferred in function definitions?为什么在函数定义中首选模式匹配?
【发布时间】:2016-02-05 19:29:38
【问题描述】:

我正在阅读来自learnyouahaskell 的“learnyouahaskell”教程。上面写着:

模式匹配也可以用于元组。如果我们想做 在 2D 空间中采用两个向量的函数(形式为 对)并将它们加在一起?要将两个向量相加,我们添加 他们的x 组件分别然后他们的y 组件 分别地。如果我们不知道,我们会这样做 模式匹配:

addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  
addVectors a b = (fst a + fst b, snd a + snd b)  

嗯,这行得通,但有更好的方法来做到这一点。让我们修改 函数,以便它使用模式匹配。

addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)  
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)  

我们去吧!好多了。请注意,这已经是一个包罗万象的 图案。 addVectors的类型(两种情况下)都是addVectors :: (Num a) => (a, a) -> (a, a) - > (a, a),所以我们保证得到 两对作为参数。

我的问题是:如果两个定义产生相同的签名,为什么模式匹配是首选方式?

【问题讨论】:

  • 比较一下它们 - 什么更容易阅读?它不是更好更接近实际定义吗?您不必解析/理解 fstsnd 也可以理解函数...
  • 并且无意冒犯但这只是一个征求意见的问题 - 永远不会有答案(或任何正确的答案) ) - 这就是我投票关闭它的原因
  • @Carsten 嗯,您可能对可读性有所了解。我读了它并在想:但在更多代码中也是如此。既然您提到不必知道fstsnd,我可以看到这可能具有可读性优势。
  • 如果您进行一些细微的布局修改,模式匹配版本看起来会更好,例如addVectors (x₁,y₁) (x₂,y₂) = (x₁+x₂, y₁+y₂)

标签: function haskell pattern-matching signature


【解决方案1】:

我认为在这种情况下,模式匹配更直接地表达了你的意思。

在函数应用案例中,需要知道fstsnd做了什么,并由此推导出ab是添加元素的元组。

addVectors a b = (fst a + fst b, snd a + snd b)

我们有 sndfst 函数来分解元组的事实在这里分散了注意力。

在模式匹配的情况下,输入是什么(一个元组,我们称之为x1y1 以及一个元组......等)以及它是如何被解构的,一目了然。而且还可以立即清楚发生了什么,它们的元素是如何添加的。

addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

这几乎就像数学定义:

(x1, y1) + (x2 sub>, y2) := (x1 + x2, y1 + y2)

直截了当,没有分心:-)

你可以用 Haskell 写这个:

(x₁, y₁) `addVector` (x₂, y₂) = (x₁ + x₂, y₁ + y₂)

【讨论】:

    【解决方案2】:

    简而言之,我们需要构造和破坏值。

    通过采用数据构造函数(可能是空元)函数并应用所需的参数来构造值。到目前为止,一切顺利。

    随机示例(滥用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 部分)更棘手。一个可能的选择是使用投影

    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)
    • 没有办法让程序崩溃:我们从不使用会崩溃的部分投影函数
    • 程序员没有负担:上述推论
    • 如果详尽检查器已打开,我们一定会处理所有可能的情况

    现在,对于具有完全 one 构造函数的类型(元组、()newtypes),可以定义完全投影(例如 fst,snd)。尽管如此,许多人更喜欢坚持模式匹配,它也可以处理一般情况。

    【讨论】:

      【解决方案3】:

      正如 Carsten 在 cmets 中提到的,这是一个基于意见的问题,但还是让我详细说明一下。

      对 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 的实现非常容易。

      我相信在书中使用这样的例子可以更有效地传达信息。

      【讨论】:

      猜你喜欢
      • 2020-06-21
      • 2014-03-18
      • 2011-01-30
      • 2021-07-09
      • 1970-01-01
      • 2014-03-16
      • 1970-01-01
      • 2016-01-14
      • 2020-04-02
      相关资源
      最近更新 更多