我正在完成了解你一个Haskell ,以便快速掌握Haskell的基础知识。我对函数式编程和模式匹配都非常满意,但后者对于 Mathematica 的做法更为如此。
与第4.1章中head
的幼稚实施一样,我继续执行last
的天真实现:
last1 :: [a] -> a
last1 (_:x:[]) = x
但是,调用last1 [1,2,3,4]
会导致错误Exception: ... Non-exhaustive patterns in function last1
。我理解这个错误意味着指定的模式并不涵盖所有可能的输入,通常,需要一个包罗万象的模式(我没有提供)。但是,我不确定为什么我的输入会出现 this 错误。
问题1:我(我的方法不正确)的理解是第一个元素由_
捕获,其余元素分配给x
,这不是正是我的意图。但是,这不应该给出类型错误,因为我指定了[a] -> a
,但x
现在是一个列表?
请注意,不关于如何编写工作last
函数 - 我知道我可以将其编写为(以及其他可能性)
last2 :: [a] -> a
last2 [x] = x
last2 (_:x) = last2 x
问题2:沿着与Haskell中更好地理解模式匹配相同的主题,我如何使用模式匹配来挑选最后一个元素,或者更一般地来说,n
元素来自给定的列表,比如[1..10]
?
This answer建议您可以使用与ViewPatterns
扩展名的模式匹配来绑定最后一个元素,但似乎很奇怪,没有像head
那样的类似“简单”模式
在 Mathematica 中,我可能会把它写成:
Range[10] /. {Repeated[_, {5}], x_, ___} :> x
(* 6 *)
选出第6个元素和
Range[10] /. {___, x_} :> x
(* 10 *)
选出非空列表的最后一个元素。
如果在本文后面有介绍,我道歉,但我正试图将每个主题和概念与我遇到的问题联系起来,以及我如何处理其他语言,以便我能够理解差异和相似性。
答案 0 :(得分:8)
要弄清楚你第一次尝试的结果,你需要看看如何 列表数据已定义。列表享有一些特殊的语法,但你会 写下这样的东西。
data List a = (:) a (List a)
| []
因此,您的列表[1 ... 10]实际上是
(1 : (2 : (3 : (4 : []))))
此外,由于(:)运算符与您的模式的正确关联性 对于last1实际上看起来像
last1 :: [a] -> a
last1 (_:(x:[])) = x
这就是为什么'x'与列表中的元素具有相同的类型;这是第一次 (:)构造函数的参数。
模式匹配允许您解构像列表这样的数据结构,但是你 需要知道他们必须做什么“形状”。这就是为什么你不能直接 指定一个模式,它将提取列表的最后一个元素,因为那里 列表可以具有无限长度。这就是为什么工作 解决方案(last2)使用递归来解决问题。你知道什么样的模式 长度为一的列表以及在哪里找到最终元素;为了一切 否则,你可以扔掉第一个元素并提取最后一个元素 得到的,更短的列表。
如果您愿意,可以添加更多模式,但不能证明这一点 很有帮助。你可以把它写成
last2 :: [a] -> a
last2 (x:[]) = x
last2 (_:x:[]) = x
last2 (_:_:x:[]) = x
...
last2 (x:xs) = last2 xs
但是如果没有无数个案例,你就永远无法完成这个功能 适用于所有长度的输入列表。当你考虑这个事实时,它更加可疑 列表实际上可以无限长;你会用什么模式来匹配呢?
答案 1 :(得分:2)
没有使用视图模式,模式匹配无法获得“last”元素。那是因为没有办法在不使用递归的情况下获取列表的最后一个元素(至少是隐式),而且,没有 decidable 方法来获取最后一个元素。
您的代码
last1 (_:x:[]) = x
应解析为
last1 (_:(x:[])) = x
可以脱糖成
last1 a = case a of
(_:b) -> case b of
(x:c) -> case c of
[] -> x
完成本练习后,我们会看到你的代码做了什么:你编写了一个匹配列表的模式,如果列表的最外层构造函数是一个缺点单元格,下一个构造函数是缺点,第三个构造函数是零。
所以在
的情况下last1 [1,2,3,4]
我们有
last1 [1,2,3,4]
= last1 (1:(2:(3:(4:[]))))
= case (1:(2:(3:(4:[])))) of
(_:b) -> case b of
(x:c) -> case c of
[] -> x
= case (2:(3:(4:[]))) of
(x:c) -> case c of
[] -> x
= let x = 2 in case (3:(4:[])) of
[] -> x
= pattern match failure
答案 2 :(得分:2)
你的例子
last1 (_:x:[]) = x
仅匹配包含两个元素的列表,即a:b:[]
形式的列表。 _
匹配列表的头部而没有绑定,x
匹配以下元素,空列表与自身匹配。
当模式匹配列表时,只有最右边的项表示一个列表 - 匹配列表的尾部。
您可以使用以下函数从列表中获取第n个元素:
getNth :: [a] -> Int -> a
getNth [] _ = error "Out of range"
getNth (h:t) 0 = h
getNth (h:t) n = getNth t (n-1)
此内置使用!!
运算符,例如[1..10] !! 5
答案 3 :(得分:2)
您确实可以使用ViewPatterns
在列表末尾进行模式匹配,所以让我们这样做:
{-# LANGUAGE ViewPatterns #-}
并在模式匹配之前通过撤消列表重新定义您的last1
和last2
。
这使它成为 O(n),但这对列表来说是不可避免的。
last1 (reverse -> (x:_)) = x
语法
mainFunction (viewFunction -> pattern) = resultExpression
是
的语法糖 mainFunction x = case viewFunction x of pattern -> resultExpression
所以你可以看到它实际上只是反转列表然后模式匹配,但它感觉更好。
viewFunction
只是你喜欢的任何功能。
(扩展的目的之一是允许人们干净利落地使用访问者功能
对于模式匹配,所以他们不必使用其数据类型的底层结构
在其上定义函数。)
如果列表为空,则此last1
会出错,就像原始last
一样。
*Main> last []
*** Exception: Prelude.last: empty list
*Main> last1 []
*** Exception: Patterns.so.lhs:7:6-33: Non-exhaustive patterns in function last1
嗯,好吧,不完全是,但我们可以通过添加
来改变它last1 _ = error "last1: empty list"
给你
*Main> last1 []
*** Exception: last1: empty list
我们当然可以对last2
使用相同的技巧:
last2 (reverse -> (_:x:_)) = x
last2 _ = error "last2: list must have at least two elements"
但定义
会更好maybeLast2 (reverse -> (_:x:_)) = Just x
maybeLast2 _ = Nothing
您可以使用例如last4
:
last4 (reverse -> (_:_:_:x:_)) = x
你可以看到使用reverse
视图模式,
我们已经改变了(_:_:_:x:_)
的语义
(ignore1st,ignore2nd,ignore3rd,get4th,ignoreTheRestOfTheList)
来
(ignoreLast,ignore2ndLast,ignore3rdLast,get4thLast,ignoreTheRestOfTheList)
。
您注意到在Mathematica中,下划线的数量用于表示被忽略的元素数量。
在Haskell中,我们只使用一个_
,但它可以用于任何忽略的值,并且存在
非对称列表构造函数:
,语义取决于您所在的一侧,因此在a:b
中,a
必须表示
元素和b
必须是一个列表(本身可能是c:d
,因为:
是正确的关联 - a:b:c
表示
a:(b:c)
)。这就是为什么任何列表模式中的最后一个下划线重新表示ignoreTheRestOfTheList
,并在
存在reverse
视图函数,这意味着忽略列表的前面元素。
在Mathematica中隐藏的递归/回溯在这里使用viewFunction reverse
(这是一个递归函数)是明确的。