为什么要使用Lambda而不是模式匹配?

5
我正在观看有关Haskell中解析器的教程:https://www.youtube.com/watch?v=9FGThag0Fqs。讲座从定义一些非常基本的解析器开始。这些解析器将在后面用于创建更复杂的解析器。其中一个基本解析器是item。它用于从我们正在解析的字符串中提取一个字符。
所有解析器都具有以下类型:
type Parser a = String -> [(a, String)]                                         

解析器“item”的定义如下所示:
item :: Parser Char                                                             
item = \inp -> case inp of                                                      
                    [] -> []                                                
                    (x:xs) -> [(x,xs)]

我不太习惯这种语法,所以它对我来说看起来很奇怪。我会这样写:

item' :: Parser Char                                                            
item' [] = []                                                                   
item' (x:xs) = [(x,xs)]

在GHCi中的测试表明它们是相等的:

*Main> item ""
[]
*Main> item "abc"
[('a',"bc")]
*Main> item' ""
[]
*Main> item' "abc"
[('a',"bc")]

讲师对此发表了简短的评论,认为这样做看起来更清晰,但我不同意。所以我的问题是:
它们确实完全相同吗? 为什么lambda版本更清晰?

2
我认为这是一个品味问题。 - augustss
3个回答

11

我相信这源自于常见的写作惯例

f :: Type1 -> ... -> Typen -> Result
f x1 ... xn = someResult

我们在类型中恰好有 n 个函数箭头,以及在等式左侧恰好有 n 个参数。这使得将类型和形式参数联系起来变得容易。

如果 Result 是一个函数的类型别名,则我们可以写成

f :: Type1 -> ... -> Typen -> Result
f x1 ... xn y = something
或者
f :: Type1 -> ... -> Typen -> Result
f x1 ... xn = \y -> something

后者遵循上述约定:n个箭头,在左侧有n个变量。此外,在右侧,我们有一个类型为Result的东西,这使得它更容易被发现。而前者则没有,当快速阅读代码时可能会错过额外的参数。

此外,这种风格使得将Result转换为新类型而不是类型别名变得容易:

newtype Result = R (... -> ...)

f :: Type1 -> ... -> Typen -> Result
f x1 ... xn = R $ \y -> something

n=0时,发布的item :: Parser Char代码是这种风格的一个实例。


2
我完全同意关于newtype的事情。你希望你的解析器至少是一个Functor和Applicative,所以你需要这个newtype,用最少的编辑使其准备好是一件好事。 - AndrewC

4

为什么应该避免等式函数定义(罗曼·切普利亚卡):

http://ro-che.info/articles/2014-05-09-clauses

上述链接的主要观点:

  • DRY:函数和参数名称重复-->更难重构
  • 更清晰地显示函数决定的参数
  • 易于添加pragma(例如用于性能分析)
  • 在语法上更接近低级别的代码

这并没有解释 Lambda 表达式..


@bheklilr 我添加了一个列表。 - rethab

3
我认为它们是完全相等的。Lambda样式的定义将一个名称“item”赋予匿名Lambda函数,该函数在内部进行模式匹配。模式匹配样式的定义直接定义它。但最终两者都是进行模式匹配的函数。我认为这是个人口味的问题。
此外,Lambda样式的定义可以被认为是pointfree风格,即定义函数时不显式写出其参数,但实际上它并不是非常pointfree,因为参数仍然被写入(但在不同的位置),但在这种情况下你得不到任何东西。
这里有另一个可能的定义,介于这两者之间:
item :: Parser Char
item inp = case inp of
                [] -> []
                (x:xs) -> [(x, xs)]

它与lambda风格本质上相同,但不是点自由的。

这根本不是"无点风格"。在\y -> ...中,命名为y的参数是"点"。仅仅因为它不在等号左边,这并不意味着这个定义是无点风格的。 - amalloy
@amalloy 你可能是对的(我正在编辑我的答案)。但它比直接定义更加 pointfree,不是吗? :) - zegkljan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接