Haskell - 函数
函数在 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
模式匹配
模式匹配是匹配特定类型表达式的过程。它只不过是一种简化代码的技术。该技术可以实现到任何类型的 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 语句非常相似,但它们在功能上有所不同。
在下面的代码中,我们使用Guard的概念修改了阶乘程序。
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调用事实函数。在内部,编译器将以与模式匹配相同的方式工作,产生以下输出 -
The factorial of 5 is: 120
哪里子句
其中是可在运行时用于生成所需输出的关键字或内置函数。当函数计算变得复杂时,它会非常有帮助。
考虑这样一个场景:您的输入是具有多个参数的复杂表达式。在这种情况下,您可以使用“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)
递归函数
递归是函数重复调用自身的情况。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
高阶函数
到目前为止,我们所看到的是 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"
在上面的示例中,我们使用了Type 类Char的toUpper函数将输入转换为大写。在这里,“map”方法将函数作为参数并返回所需的输出。这是它的输出 -
sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts sh-4.3$ main "TUTORIALSPOINT.COM"
拉姆达表达式
有时我们必须编写一个在应用程序的整个生命周期中仅使用一次的函数。为了处理这种情况,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