举这个例子:
module Main where
main = print (reverseWords "lol")
reverseWords :: String -> [String]
reverseWords = words
reverseWords
函数不是与此处的任何参数进行模式匹配,但该函数运行并输出"[lol]"
。
我这里有两个问题:
Haskell如何知道我是否正在针对输入调用
words
函数reverseWords
?在这种语法中,看起来我只是将函数返回words
.为什么即使我没有在模式中提供任何输入参数来
reverseWords
,它也能成功运行?
你只是说reverseWords
是函数words
。无论reverseWords
出现在何处,都可以将其替换为函数words
。所以print (reverseWords "lol")
完全等同于print (words "lol")
.基本上,你有这个新函数reverseWords
它像words
一样接受String
参数,并简单地将该参数传递给words
,返回words
为该参数返回的任何内容。您对reverseWords
的定义等效于:
reverseWords :: String -> [String]
reverseWords s = words s
考虑到所有这些,reverseWords
是一个误导性的名称,因为它与words
没有任何不同之处。所以,你根本没有做任何有用的事情,只是重命名一些东西。一个更好的例子是:
reverseWords :: String -> [String]
reverseWords = reverse . words
其中reverse
是您使用组合运算符words
编写的其他函数(.)
创建一个执行有用操作的新函数。这就是所谓的无点样式,通过组合其他函数来定义新函数,而无需引用任何参数。该定义等效于:
reverseWords :: String -> [String]
reverseWords s = reverse (words s)
您正在声明一个新函数reverseWords
。首先,声明其类型:
reverseWords :: String -> [String]
因此,它将是一个接受字符串并返回字符串列表的函数。
现在我们有两种方法可以解决这个问题。第一种是编写一个规则,说明当reverseWords
收到某个参数时,结果是一个字符串列表的表达式(可能涉及调用其他函数和使用参数)。喜欢这个:
reverseWords s = words s
这说"reverseWords s
形式的表达式被定义为等于words s
"。因此,编译器知道reverseWords "lol"
等于words "lol"
。函数reverseWords
由我们为它编写的规则隐式定义1.
但是我们还有另一种方式可以思考这个问题。我假设你对它的工作原理非常满意:
myFavouriteNumber :: Integer
myFavouriteNumber = 28
我们首先声明myFavouriteNumber
将是Integer
类型,然后通过写下整数来定义它。
嗯,函数是 Haskell 中的一类值,这意味着我们不仅必须使用专用的专用语法来定义它们。如果我们可以通过写下整数来定义类型Integer
,那么我们应该能够通过写下具有该类型的东西来定义类型String -> [String]
,而不是写下规则。这就是以这种形式发生的事情:
reverseWords = words
与其写一个规则来说明reverseWords
应用于某事时的结果会是什么,我们只是写下reverseWords
是什么。在这种情况下,我们告诉编译器reverseWords
被定义为等于words
.这仍然让编译器知道reverseWords "lol"
等于words "lol"
,但它只是通过查看reverseWords
部分来做到这一点;即使不看"lol"
也可以解决这个问题.
此外,我们还可以编写这样的定义:
two :: Integer
two = 1 + 1
在这里,我们不是将two
定义为等于某些预先存在的事物,而是计算它的值(从其他预先存在的事物:1
和+
)。因为函数是一流的,我们可以做同样的事情:
reversedWords :: String -> [String]
reversedWords = reverse . words
在这里,我们并不是说reversedWords
等于现有函数,而是计算reverseWords
的函数,即通过在预先存在的函数reverse
和words
上调用组合运算符.
得到的函数。但是我们仍在计算函数(类型String -> [String]
),而不是函数的结果(类型[String]
)。
所以回答您的问题:
Haskell如何知道我是否正在对输入调用"words"函数来反转单词?在这种语法中,看起来我只是返回函数"words">
是的,您只是将函数words
返回。但是你把它"返回"为函数本身reversedWords
(在它应用于任何东西之前),而不是作为应用它时reversedWords
的结果。这就是Haskell如何知道words
函数是接收输入到reverseWords
;reverseWords
等于words
,所以任何时候你传递一些输入给reverseWords
你实际上是在传递给words
。
为什么即使我没有在模式中提供任何输入参数来反转单词,它也能成功运行?
因为您定义了函数reverseWords
.您通过声明它等于其他一些现有函数来定义它,因此它执行该函数执行的任何操作。为函数结果编写规则(基于参数)并不是定义函数的唯一方法。
事实上,你没有在你的定义中为reverseWords
的参数提供一个名称,这正是Haskell知道这就是你正在做的事情。如果要定义类型为A -> B
的函数并为参数命名,则右侧必须是类型B
。如果你不这样做,那么右手边一定是A -> B
型的东西。阿拉伯数字
但是对于您的磁贴问题:
Haskell有隐式模式匹配吗?
我不知道如何回答这个问题,因为这个讨论都没有涉及模式匹配。您可以使用模式匹配来定义 Haskell 中的函数,但这不是这里发生的事情。
1好的,在这种情况下,规则非常明确地定义了reverseWords
,但一般来说,可以使用多个规则来定义函数,使用模式匹配和保护,以及辅助where
定义; 函数的实际值是所有规则(以及如何按自上而下顺序尝试它们的知识)和where
子句的涌现属性。
2无论A
和B
是什么,此逻辑都有效。特别是,B
可能是里面有更多箭头的东西!这正是具有多个参数的函数在 Haskell 中的工作方式。像这样的函数:
foo :: Int -> String -> (Int, String)
可以由以下任一定义:
- 编写一个采用两个参数(一个
Int
和一个String
)的规则,右侧的类型为(Int, String)
- 编写一个采用一个参数(
Int
)的规则,右侧的类型为String -> (Int, String)
- 编写没有参数的直接定义,右侧类型为
Int -> String -> (Int, String)
模式很清楚;每次向规则添加参数时,RHS 都有一个类型,该类型会再去除一个箭头(从左侧开始)。
所有 3 个选项都会生成一个具有相同类型的函数foo
,您可以以相同的方式调用该函数。函数的内部定义对外界来说并不重要。
>reverseWord
确实"返回"words
而不调用它,所以reverseWordss
变得wordss
- 由于reverseWords
返回words
,调用reverseWordss
变成了呼叫wordss
。
这就像用更传统的语法foo() { bar }
定义,然后foo()(x)
===bar(x)
.