Haskell教程:#7 函数

By George Z. August 7, 2021

Function

函数在 Haskell 中扮演着重要角色,因为它是一种函数式编程语言。 和其他语言一样,Haskell 也有自己的函数定义和声明。函数声明由函数名称及其参数列表及其输出组成。函数定义是您实际定义函数的地方。

让我们以 add 函数的小例子来详细理解这个概念。

add :: Integer -> Integer -> Integer   --function declaration 
add x y =  x + y                       --function definition 

main = do 
   putStrLn "The addition of the two numbers is:"  
   print(add 2 5)    --calling a function 

在这里,我们在第一行和第二行中声明了我们的函数,我们编写了我们的实际函数,它将接受两个参数并产生一个整数类型的输出。像大多数其他语言一样,Haskell 从 main 方法开始编译代码。 我们的代码将生成以下输出

The addition of the two numbers is:
7

模式匹配 (Pattern Matching)

模式匹配是匹配特定类型表达式的过程。 它只不过是一种简化代码的技术。 这种技术可以实现到任何类型的 Type 类中。 If-Else 可以用作模式匹配的替代选项。模式匹配可以被认为是动态多态的一种变体,在运行时,可以根据参数列表执行不同的方法。

看看下面的代码块。 这里我们使用了模式匹配技术来计算一个数的阶乘。

fact :: Int -> Int 
fact 0 = 1 
fact n = n * fact ( n - 1 ) 

main = do 
   putStrLn "The factorial of 5 is:" 
   print (fact 5)

我们都知道如何计算一个数的阶乘。 编译器将开始搜索带有参数的名为“fact”的函数。 如果参数不等于 0,那么该数字将继续调用相同的函数,并且比实际参数的数字小 1。当参数的模式与 0 完全匹配时,它将调用我们的模式,即“fact 0 = 1”。 我们的代码将产生以下输出

The factorial of 5 is:
120

守卫(Guards)

Guards 是一个与模式匹配非常相似的概念。 在模式匹配中,我们通常匹配一个或多个表达式,但我们使用守卫来测试表达式的某些属性。虽然建议使用模式匹配而不是守卫,但从开发人员的角度来看,守卫更具可读性和简单性。 对于第一次使用的用户,守卫看起来与 If-Else 语句非常相似,但它们在功能上有所不同。

在下面的代码中,我们通过使用守卫的概念修改了我们的阶乘程序。

fact :: Integer -> Integer 
fact n | n == 0 = 1 
       | n /= 0 = n * fact (n-1) 
main = do 
   putStrLn "The factorial of 5 is:"  
   print (fact 5) 

在这里,我们声明了两个守卫,用“|”隔开 并从main调用fact函数。 在内部,编译器将以与模式匹配相同的方式工作以产生以下输出

The factorial of 5 is:
120

Where 从句

where 是可在运行时使用以生成所需输出的关键字或内置函数。 当函数计算变得复杂时,它会非常有用。考虑这样一个场景,您的输入是具有多个参数的复杂表达式。 在这种情况下,您可以使用“where”子句将整个表达式分成小部分。

在以下示例中,我们采用复杂的数学表达式。 我们将展示如何使用 Haskell 找到多项式方程 [x^2 - 8x + 6] 的根。

roots :: (Float, Float, Float) -> (Float, Float)  
roots (a,b,c) = (x1, x2) where 
   x1 = e + sqrt d / (2 * a) 
   x2 = e - sqrt d / (2 * a) 
   d = b * b - 4 * a * c  
   e = - b / (2 * a)  
main = do 
   putStrLn "The roots of our Polynomial equation are:" 
   print (roots(1,-8,6))

注意计算给定多项式函数根的表达式的复杂性。 这是相当复杂的。 因此,我们使用 where 子句来破坏表达式。 上面这段代码将生成以下输出

The roots of our Polynomial equation are:
(7.1622777,0.8377223)

递归函数 (Recursion Function)

递归是一种函数重复调用自身的情况。 Haskell 不提供任何循环多次表达式的工具。 相反,Haskell 希望您将整个功能分解为不同功能的集合,并使用递归技术来实现您的功能。让我们再次考虑我们的模式匹配示例,其中我们计算了一个数字的阶乘。 求一个数的阶乘是使用递归的经典案例。 在这里,您可能会问:“模式匹配与递归有何不同?” 这两者之间的区别在于它们的使用方式,模式匹配用于设置终端约束,而递归是一个函数调用。

在以下示例中,我们同时使用了模式匹配和递归来计算 5 的阶乘。

fact :: Int -> Int 
fact 0 = 1 
fact n = n * fact ( n - 1 ) 

main = do 
   putStrLn "The factorial of 5 is:" 
   print (fact 5) 

它将产生以下输出

The factorial of 5 is:
120

高阶函数 (Higher Order Function)

到目前为止,我们所看到的是 Haskell 函数将一种类型作为输入并产生另一种类型作为输出,这在其他命令式语言中非常相似。 高阶函数是 Haskell 的一个独特功能,您可以将函数用作输入或输出参数。

虽然是一个虚拟的概念,但是在现实世界的程序中,我们在 Haskell 中定义的每一个函数都是使用高阶机制来提供输出的。 如果您有机会查看 Haskell 的库函数,那么您会发现大多数库函数都是以更高阶的方式编写的。

让我们举个例子,我们将导入一个内置的高阶函数映射,并根据我们的选择使用它来实现另一个高阶函数。

import Data.Char  
import Prelude hiding (map) 

map :: (a -> b) -> [a] -> [b] 
map _ [] = [] 
map func (x : abc) = func x : map func abc  
main = print $ map toUpper "tutorialspoint.com" 

在上面的例子中,我们使用了类型类 Char 的 toUpper 函数将我们的输入转换为大写。 在这里,方法“map”将一个函数作为参数并返回所需的输出。 这是它的输出

sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
"TUTORIALSPOINT.COM" 

Lambda 表达式

有时我们必须编写一个在应用程序的整个生命周期中只使用一次的函数。 为了处理这种情况,Haskell 开发人员使用了另一个称为 lambda 表达式或 lambda 函数的匿名块。没有定义的函数称为 lambda 函数。 lambda 函数用“\”字符表示。 让我们以下面的例子为例,我们将在不创建任何函数的情况下将输入值增加 1。

main = do 
   putStrLn "The successor of 4 is:"  
   print ((\x -> x + 1) 4)

在这里,我们创建了一个没有名字的匿名函数。 它将整数 4 作为参数并打印输出值。 我们基本上是在操作一个函数,甚至没有正确声明它。 这就是 lambda 表达式的美妙之处。我们的 lambda 表达式将产生以下输出

sh-4.3$ main
The successor of 4 is:
5
comments powered by Disqus