目录

Haskell - 快速指南

Haskell - Overview

Haskell是一种功能编程语言,专门用于处理符号计算和列表处理应用程序。 函数式编程基于数学函数。 除了Haskell之外,一些遵循功能编程范例的其他流行语言包括:Lisp,Python,Erlang,Racket,F#,Clojure等。

conventional programing ,指令被视为特定语法或格式的一组声明,但是在functional programing的情况下,所有计算被认为是单独的数学函数的组合。

与Haskell一起发挥作用

Haskell是一种广泛使用的纯函数式语言。 在这里,我们列出了一些使这种语言比其他传统编程语言(如Java,C,C ++,PHP等)更特殊的要点。

  • Functional Language - 在传统的编程语言中,我们指导编译器执行一系列任务,这些任务只是告诉您的计算机“做什么”和“怎么做?” 但在Haskell,我们会告诉我们的电脑“它是什么?”

  • Laziness - 哈斯克尔是一种懒惰的语言。 lazy ,我们的意思是Haskell不会毫无理由地评估任何表达式。 当评估引擎发现需要计算表达式时,它会创建一个thunk data structure来收集该特定评估所需的所有信息以及指向该thunk data structure的指针。 只有在需要评估特定表达式时,评估引擎才会开始工作。

  • Modularity - Haskell应用程序只是一系列功能。 我们可以说Haskell应用程序是许多小型Haskell应用程序的集合。

  • Statically Typed - 在传统的编程语言中,我们需要定义一系列变量及其类型。 相比之下,Haskell是一种严格类型的语言。 通过术语Strictly Typed语言,我们的意思是Haskell编译器足够智能,可以计算出声明的变量的类型,因此我们不需要明确提到所用变量的类型。

  • Maintainability - Haskell应用程序是模块化的,因此维护它们非常简单且经济高效。

功能程序更加并发,它们遵循执行中的并行性,以提供更准确和更好的性能。 哈斯克尔也不例外; 它是以有效处理multithreading的方式开发的。

你好,世界

这是一个简单的例子来展示Haskell的动态。 看看下面的代码。 我们需要的只是在控制台上打印“Hello Word”的一行。

main = putStrLn "Hello World"

一旦Haskell编译器遇到上面的代码,它会立即产生以下输出 -

Hello World 

我们将在本教程中提供大量示例,以展示Haskell的强大功能和简单性。

Haskell - Environment Set Up

我们已经在线建立了Haskell编程环境 - https://www.iowiki.com/compile_haskell_online.php

这个在线编辑器有很多选项来练习Haskell编程示例。 转到页面的终端部分,然后输入"ghci" 。 此命令自动加载Haskell编译器并在线启动Haskell。 使用ghci命令后,您将收到以下输出。

sh-4.3$ ghci
GHCi,version7.8.4:http://www.haskell.org/ghc/:?forhelp
Loading package ghc-prim...linking...done.
Loading packageinteger gmp...linking... done.
Loading package base...linking...done.
Prelude>

如果您仍想在本地系统中离线使用Haskell,则需要从其官方网页下载可用的Haskell设置 - https://www.haskell.org/downloads

市场上有三种不同类型的installers -

  • Minimal Installer - 它提供GHC(格拉斯哥Haskell编译器),CABAL(用于构建应用程序和库的通用体系结构)和堆栈工具。

  • Stack Installer - 在此安装程序中,GHC可以在托管收费链的跨平台下载。 它将全局安装您的应用程序,以便它可以在需要时更新其API工具。 它会自动解析所有面向Haskell的依赖项。

  • Haskell Platform - 这是安装Haskell的最佳方式,因为它将在您的机器中安装整个平台,并从一个特定位置安装。 此安装程序不像上述两个安装程序那样分发。

我们已经看到市场上有不同类型的安装程序,现在让我们看看如何在我们的机器中使用这些安装程序。 在本教程中,我们将使用Haskell平台安装程序在我们的系统中安装Haskell编译器。

在Windows中设置环境

要在Windows计算机上设置Haskell环境,请访问他们的官方网站https://www.haskell.org/platform/windows.html并根据您的可自定义架构下载安装程序。

Windows Installer

查看系统的体系结构并下载相应的安装文件并运行它。 它将像任何其他Windows应用程序一样安装。 您可能需要更新系统的CABAL配置。

MAC中的环境设置

要在MAC系统上设置Haskell环境,请访问其官方网站https://www.haskell.org/platform/mac.html并下载Mac安装程序。

MAC安装程序

Linux中的环境设置

在基于Linux的系统上安装Haskell需要运行一些不像MAC和Windows那么容易的命令。 是的,它很无聊但很可靠。

您可以按照以下步骤在Linux系统上安装Haskell -

Step 1 - 要在Linux系统上设置Haskell环境,请访问官方网站https://www.haskell.org/platform/linux.html并选择您的发行版。 您将在浏览器中找到以下屏幕。

Linux安装程序

Step 2 - 选择您的分配。 在我们的例子中,我们使用的是Ubuntu。 选择此选项后,您将在屏幕上看到以下页面,其中包含在本地系统中安装Haskell的命令。

Ubuntu安装程序

Step 3 - 按Ctrl + Alt + T打开终端。运行命令"$ sudo apt-get install haskell-platform"并按Enter键。 在使用root密码验证您之后,它将自动开始在您的系统上下载Haskell。 安装后,您将收到一条确认消息。

Step 4 - 再次转到终端并运行GHCI命令。 获得Prelude提示后,即可在本地系统上使用Haskell。

本地系统

要退出GHCI prolog,您可以使用命令“:quit exit”。

Haskell - Basic Data Models

Haskell是一种纯函数式编程语言,因此它比其他编程语言更具交互性和智能性。 在本章中,我们将了解Haskell的基本数据模型,这些模型实际上是预定义的,或者以某种方式智能地解码到计算机内存中。

在本教程中,我们将使用我们网站上提供的Haskell在线平台( https://www.iowiki.com/codingground.htm )。

Numbers

Haskell足够聪明,可以将一些数字解码为数字。 因此,您不必像其他编程语言那样在外部提及其类型。 根据示例,转到prelude命令提示符,然后运行“2 + 2”并按Enter键。

sh-4.3$ ghci 
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help 
Loading package ghc-prim ... linking ... done. 
Loading package integer-gmp ... linking ... done. 
Loading package base ... linking ... done. 
Prelude> 2+2

结果您将收到以下输出。

4

在上面的代码中,我们只是将两个数字作为参数传递给GHCI编译器而没有预定义它们的类型,但编译器可以很容易地将这两个条目解码为数字。

现在,让我们尝试一些更复杂的数学计算,看看我们的智能编译器是否给出了正确的输出。 试试“15+(5 * 5)-40”

Prelude> 15+(5*5)-40 

上述表达式根据预期输出产生“0”。

0

人物(Characters)

与数字一样,Haskell可以智能地识别作为输入的字符。 转到Haskell命令提示符并键入带双引号或单引号的任何字符。

让我们提供以下行作为输入并检查其输出。

Prelude> :t "a" 

它将产生以下输出 -

"a" :: [Char] 

请记住,在提供输入时使用(: t )。 在上面的例子中, (:t)包括与输入相关的特定类型。 我们将在接下来的章节中详细了解这种类型。

看看下面的示例,我们将一些无效输入作为char传递,这反过来导致错误。

Prelude> :t a 
<interactive>:1:1: Not in scope: 'a'  
Prelude> a 
<interactive>:4:1: Not in scope: 'a' 

通过错误消息“:4:1:不在范围内:`a'”,Haskell编译器警告我们它无法识别您的输入。 Haskell是一种语言,其中所有内容都使用数字表示。

Haskell遵循传统的ASCII编码风格。 让我们看一下下面的例子来了解更多 -

Prelude> '\97' 
'a'  
Prelude> '\67' 
'C' 

看看您的输入如何解码为ASCII格式。

String

string只是一组字符。 没有使用字符串的特定语法,但Haskell遵循使用双引号表示字符串的传统样式。

看一下我们传递字符串“iowiki.com”的以下示例。

Prelude> :t "iowiki.com" 

它将在屏幕上产生以下输出 -

"iowiki.com" :: [Char] 

查看整个字符串是如何解码为仅限Char数组的。 让我们转到其他数据类型及其语法。 一旦我们开始实际操作,我们将习惯所有数据类型及其用途。

Boolean

与其他数据类型一样,布尔数据类型也非常简单。 请看下面的示例,我们将使用一些布尔输入(例如“True”或“False”)来使用不同的布尔运算。

Prelude> True && True 
True  
Prelude> True && False 
False   
Prelude> True || True 
True  
Prelude> True || False 
True

在上面的例子中,我们不需要提到“True”和“False”是布尔值。 Haskell本身可以对其进行解码并执行相应的操作。 让我们用“true”或“false”修改输入。

Prelude> true 

它将产生以下输出 -

<interactive>:9:1: Not in scope: 'true' 

在上面的例子中,Haskell无法区分“true”和数字值,因此我们的输入“true”不是数字。 因此,Haskell编译器抛出一个错误,指出我们的输入不是它的范围。

列表和列表理解

与其他数据类型一样, List也是Haskell中使用的非常有用的数据类型。 例如,[a,b,c]是字符列表,因此,根据定义,List是由逗号分隔的相同数据类型的集合。

与其他数据类型一样,您无需将List声明为List。 Haskell足够智能,可以通过查看表达式中使用的语法来解码输入。

看一下下面的例子,它展示了Haskell如何处理List。

Prelude> [1,2,3,4,5] 

它将产生以下输出 -

[1,2,3,4,5] 

Haskell中的列表本质上是同构的,这意味着它们不允许您声明不同类型的数据类型的列表。 任何像[1,2,3,4,5,a,b,c,d,e,f]这样的列表都会产生错误。

Prelude> [1,2,3,4,5,a,b,c,d,e,f] 

此代码将产生以下错误 -

<interactive>:17:12: Not in scope: 'a' 
<interactive>:17:14: Not in scope: 'b' 
<interactive>:17:16: Not in scope: 'c' 
<interactive>:17:18: Not in scope: 'd' 
<interactive>:17:20: Not in scope: 'e' 
<interactive>:17:22: Not in scope: 'f'

列表理解

列表理解是使用数学表达式生成列表的过程。 请看下面的例子,我们使用数学表达式生成一个列表,格式为[output | 范围,条件]。

Prelude> [x*2| x<-[1..10]] 
[2,4,6,8,10,12,14,16,18,20]  
Prelude> [x*2| x<-[1..5]] 
[2,4,6,8,10]  
Prelude> [x| x<-[1..5]] 
[1,2,3,4,5]

这种使用数学表达式创建一个List的方法称为List Comprehension

Tuple

Haskell提供了另一种在单个数据类型中声明多个值的方法。 它被称为Tuple 。 元组可以被视为列表,但是元组和列表之间存在一些技术差异。

元组是一种不可变的数据类型,因为我们不能在运行时修改元素的数量,而List是一种可变的数据类型。

另一方面,List是一种同类数据类型,但是Tuple本质上是异构的,因为元组内部可能包含不同类型的数据。

元组用单括号表示。 看一下下面的例子,看看Haskell如何处理元组。

Prelude> (1,1,'a') 

它将产生以下输出 -

(1,1,'a') 

在上面的例子中,我们使用了一个带有两个number类型变量的Tuple和一个char类型变量。

Haskell - Basic Operators

在本章中,我们将了解Haskell中使用的不同运算符。 与其他编程语言一样,Haskell智能地处理一些基本操作,如加法,减法,乘法等。在接下来的章节中,我们将了解有关不同运算符及其用法的更多信息。

在本章中,我们将使用我们的在线平台( https://www.iowiki.com/codingground.htm )在Haskell中使用不同的运算符。 请记住,我们只使用integer类型数字,因为我们将在后续章节中了解有关decimal类型数字的更多信息。

加法运算符

顾名思义,加法(+)运算符用于加法函数。 以下示例代码显示了如何在Haskell中添加两个整数 -

main = do 
   let var1 = 2 
   let var2 = 3 
   putStrLn "The addition of the two numbers is:" 
   print(var1 + var2) 

在上面的文件中,我们创建了两个单独的变量var1var2 。 最后,我们使用addition运算符打印结果。 使用compileexecute按钮来运行代码。

此代码将在屏幕上生成以下输出 -

The addition of the two numbers is:
5

减法运算符

顾名思义,此运算符用于减法运算。 以下示例代码显示如何在Haskell中减去两个整数 -

main = do 
   let var1 = 10 
   let var2 = 6 
   putStrLn "The Subtraction of the two numbers is:" 
   print(var1 - var2)

在这个例子中,我们创建了两个变量var1var2 。 此后,我们使用减法( - )运算符来减去这两个值。

此代码将在屏幕上生成以下输出 -

The Subtraction of the two numbers is:
4

乘法运算符

该运算符用于乘法运算。 以下代码显示如何使用乘法运算符在Haskell中相乘两个数字 -

main = do 
   let var1 = 2 
   let var2 = 3 
   putStrLn "The Multiplication of the Two Numbers is:" 
   print(var1 * var2) 

当您在我们的在线平台上运行时,此代码将生成以下输出 -

The Multiplication of the Two Numbers is:
6 

分部运算符

看看下面的代码。 它显示了如何在Haskell中划分两个数字 -

main = do 
   let var1 = 12 
   let var2 = 3 
   putStrLn "The Division of the Two Numbers is:" 
   print(var1/var2)

它将产生以下输出 -

The Division of the Two Numbers is: 
4.0 

序列/范围操作符

Sequence或Range是Haskell中的特殊运算符。 它用“(..)”表示。 您可以在声明具有值序列的列表时使用此运算符。

如果要打印1到10之间的所有值,则可以使用“[1..10]”之类的内容。 同样,如果你想生成从“a”到“z”的所有字母表,那么你只需输入"[a..z]"

以下代码显示如何使用Sequence运算符打印1到10之间的所有值 -

main :: IO() 
main = do 
   print [1..10]

它将生成以下输出 -

[1,2,3,4,5,6,7,8,9,10] 

Haskell - Decision Making

决策是一种允许程序员在代码流中应用条件的功能。 程序员可以根据预定义的条件执行一组指令。 以下流程图显示了Haskell的决策结构 -

条件循环

Haskell提供以下类型的决策声明 -

Sr.No. 声明和说明
1 if–else statement

带有else语句的一个else语句。 只有当给定的布尔条件无法满足时, else块中的指令才会执行。

2 Nested if-else statement

多个if块后跟else

Haskell - Types and Type Class

Haskell是一种函数式语言,它是严格类型的,这意味着整个应用程序中使用的数据类型在编译时将为编译器所知。

内置类型类

在Haskell中,每个语句都被视为一个数学表达式,该表达式的类别被称为Type 。 您可以说“Type”是编译时使用的表达式的数据类型。

要了解有关Type更多信息,我们将使用“:t”命令。 通常, Type可以被视为一个值,而Type Class可以被认为是一组类似的类型。 在本章中,我们将了解不同的内置类型。

诠释

Int是表示Integer类型数据的类型类。 2147483647到-2147483647范围内的每个整数都属于Int类型。 在以下示例中,函数fType()将根据其定义的类型运行。

fType :: Int -> Int -> Int 
fType x y = x*x + y*y
main = print (fType 2 4) 

这里,我们将函数fType()的类型设置为int 。 该函数接受两个int值并返回一个int值。 如果你编译并执行这段代码,那么它将产生以下输出 -

sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts 
sh-4.3$ main
20

Integer

Integer可以被视为Int的超集。 该值不受任何数字的限制,因此整数可以是任意长度而没有任何限制。 要查看IntInteger类型之间的基本区别,让我们修改上面的代码如下 -

fType :: Int -> Int -> Int 
fType x y = x*x + y*y 
main = print (fType 212124454 44545454454554545445454544545)

如果您编译上面的代码,将抛出以下错误消息 -

main.hs:3:31: Warning:            
   Literal 44545454454554545445454544545 is out of the Int range -
   9223372036854775808..9223372036854775807 
Linking main ...

发生此错误是因为我们的函数fType()期望一个Int类型值,并且我们传递一些真正的大Int类型值。 为了避免这个错误,让我们用“整数”修改类型“Int”并观察差异。

fType :: Integer -> Integer -> Integer 
fType x y = x*x + y*y 
main = print (fType 212124454 4454545445455454545445445454544545) 

现在,它将产生以下输出 -

sh-4.3$ main
1984297512562793395882644631364297686099210302577374055141

浮动

看看下面这段代码。 它显示了Float类型在Haskell中的工作原理 -

fType :: Float -> Float -> Float 
fType x y = x*x + y*y 
main = print (fType 2.5 3.8)

该函数将两个浮点值作为输入,并产生另一个浮点值作为输出。 编译并执行此代码时,它将生成以下输出 -

sh-4.3$ main
20.689999 

Double是一个浮点数,最后是双精度。 看看下面的例子 -

fType :: Double -> Double -> Double 
fType x y = x*x + y*y 
main = print (fType 2.56 3.81)

当您执行上面的代码时,它将生成以下输出 -

sh-4.3$ main 
21.0697

Bool

Bool是一个布尔类型。 它可以是True或False。 执行以下代码以了解Bool类型在Haskell中的工作方式 -

main = do  
   let x = True 
   if x == False 
      then putStrLn "X matches with Bool Type" 
   else putStrLn "X is not a Bool Type" 

在这里,我们将变量“x”定义为Bool,并将其与另一个布尔值进行比较以检查其原始性。 它将产生以下输出 -

sh-4.3$ main
X is not a Bool Type 

Char

字符代表字符。 单引号内的任何内容都被视为字符。 在下面的代码中,我们修改了之前的fType()函数以接受Char值并返回Char值作为输出。

fType :: Char-> Char 
fType x = 'K' 
main = do  
   let x = 'v' 
   print (fType x) 

上面的代码将使用char值'v'调用fType()函数,但它返回另一个char值,即'K'。 这是它的输出 -

sh-4.3$ main 
'K'

请注意,我们不会显式使用这些类型,因为Haskell足够智能,可以在声明之前捕获类型。 在本教程的后续章节中,我们将看到不同类型和类型类如何使Haskell成为强类型语言。

EQ类型

EQ类型类是一个接口,它提供测试表达式相等性的功能。 任何想要检查表达式相等性的Type类都应该是此EQ Type类的一部分。

上面提到的所有标准类型类都是此EQ类的一部分。 每当我们使用上面提到的任何类型检查任何相等时,我们实际上是在调用EQ类型类。

在以下示例中,我们使用“==”或“/ =”操作在内部使用EQ类型。

main = do 
   if 8 /= 8 
      then putStrLn "The values are Equal" 
   else putStrLn "The values are not Equal"

它将产生以下输出 -

sh-4.3$ main 
The values are not Equal 

Ord类型

Ord是另一个接口类,它为我们提供了排序功能。 到目前为止我们使用的所有types都是这个Ord接口的一部分。 与EQ接口一样,Ord接口可以使用“”“,”“”,“”=“,”“=”,“比较”来调用。

请在下面的示例中找到我们使用此类型类的“比较”功能的示例。

main = print (4 <= 2) 

这里,Haskell编译器将检查4是否小于或等于2.由于它不是,代码将产生以下输出 -

sh-4.3$ main 
False

Show

Show具有将其参数作为String打印的功能。 无论它的论点是什么,它总是将结果打印为String。 在以下示例中,我们将使用此界面打印整个列表。 “show”可用于调用此接口。

main = print (show [1..10]) 

它将在控制台上生成以下输出。 这里,双引号表示它是String类型值。

sh-4.3$ main 
"[1,2,3,4,5,6,7,8,9,10]" 

Read

Read接口与Show相同,但不会以String格式打印结果。 在下面的代码中,我们使用read接口读取字符串值并将其转换为Int值。

main = print (readInt "12") 
readInt :: String -> Int 
readInt = read 

这里,我们将一个String变量(“12”)传递给readInt方法,该方法在转换后返回12(一个Int值)。 这是它的输出 -

sh-4.3$ main 
12

Enum

Enum是另一种Type类,它在Haskell中启用顺序或有序功能。 可以通过Succ, Pred, Bool, Char等命令访问此Type类。

以下代码显示了如何查找12的后继值。

main = print (succ 12) 

它将产生以下输出 -

sh-4.3$ main
13

Bounded

所有具有上限和下限的类型都属于此类类。 例如, Int类型数据的最大界限为“9223372036854775807”,最小界限为“-9223372036854775808”。

以下代码显示了Haskell如何确定Int类型的最大和最小边界。

main = do 
   print (maxBound :: Int) 
   print (minBound :: Int) 

它将产生以下输出 -

sh-4.3$ main
9223372036854775807
-9223372036854775808

现在,尝试查找Char,Float和Bool类型的最大和最小边界。

Num

此类型类用于数值运算。 Int,Integer,Float和Double等类型属于此Type类。 看看下面的代码 -

main = do 
   print(2 :: Int)  
   print(2 :: Float) 

它将产生以下输出 -

sh-4.3$ main
2
2.0

Integral

Integral可以被认为是Num类型的子类。 Num Type类包含所有类型的数字,而Integral类类仅用于整数。 Int和Integer是此Type类下的类型。

Floating

与Integral一样,Floating也是Num Type类的一部分,但它只保存浮点数。 因此, FloatDouble属于这种类型。

自定义类型

与任何其他编程语言一样,Haskell允许开发人员定义用户定义的类型。 在以下示例中,我们将创建用户定义的类型并使用它。

data Area = Circle Float Float Float  
surface :: Area -> Float   
surface (Circle _ _ r) = pi * r ^ 2   
main = print (surface $ Circle 10 20 10 ) 

在这里,我们创建了一个名为Area的新类型。 接下来,我们使用此类型来计算圆的面积。 在上面的例子中,“surface”是一个函数,它将Area作为输入并产生Float作为输出。

请记住,“data”是一个关键字,Haskell中的所有用户定义类型始终以大写字母开头。

它将产生以下输出 -

sh-4.3$ main
314.15927

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比实际参数的函数少1。

当参数的模式与0完全匹配时,它将调用我们的模式,即“事实0 = 1”。 我们的代码将产生以下输出 -

The factorial of 5 is:
120

Guards

Guards是一个与模式匹配非常相似的概念。 在模式匹配中,我们通常匹配一个或多个表达式,但我们使用guards来测试表达式的某些属性。

虽然建议在guards上使用模式匹配,但从开发人员的角度来看, guards装置更具可读性和简单性。 对于初次使用的用户, guards看起来与If-Else语句非常相似,但它们在功能上是不同的。

在下面的代码中,我们使用guards的概念修改了我们的factorial程序。

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) 

在这里,我们宣布两个guards ,用“|”分隔 并从main调用fact函数。 在内部,编译器将以与模式匹配的情况相同的方式工作,以产生以下输出 -

The factorial of 5 is:
120

Where子句 (Where Clause)

关键字或内置函数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)

递归函数

递归是函数重复调用自身的情况。 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函数将一种type作为输入并生成另一种type作为输出,这在其他命令式语言中非常相似。 高阶函数是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 "iowiki.com" 

在上面的例子中,我们使用Type Class ChartoUpper函数将输入转换为大写。 这里,方法“map”将函数作为参数并返回所需的输出。 这是它的输出 -

sh-4.3$ ghc -O2 --make *.hs -o main -threaded -rtsopts
sh-4.3$ main
"iowiki.com" 

Lambda表达

在应用程序的整个生命周期中,我们有时必须编写一个仅使用一次的函数。 为了处理这种情况,Haskell开发人员使用另一个称为lambda expressionlambda function匿名块。

没有定义的函数称为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

Haskell - More On Functions

到目前为止,我们已经讨论了许多类型的Haskell函数,并使用不同的方法来调用这些函数。 在本章中,我们将学习一些可以在Haskell中轻松使用的基本函数,而无需导入任何特殊的Type类。 大多数这些函数都是其他高阶函数的一部分。

头部功能

Head功能适用于List。 它返回输入参数的第一个,它基本上是一个列表。 在下面的示例中,我们传递一个包含10个值的列表,并使用head函数生成该列表的第一个元素。

main = do 
   let x = [1..10]   
   putStrLn "Our list is:"  
   print (x) 
   putStrLn "The first element of the list is:" 
   print (head x)

它将产生以下输出 -

Our list is: 
[1,2,3,4,5,6,7,8,9,10]
The first element of the list is:
1

尾部功能

Tail是补充head功能的功能。 它需要一个list作为输入,并产生没有头部的整个列表。 这意味着, tail函数返回整个列表而没有第一个元素。 看看下面的例子 -

main = do 
   let x = [1..10]   
   putStrLn "Our list is:"  
   print (x) 
   putStrLn "The tail of our list is:" 
   print (tail x) 

它将产生以下输出 -

Our list is:
[1,2,3,4,5,6,7,8,9,10]
The tail of our list is:
[2,3,4,5,6,7,8,9,10]

最后的功能

顾名思义,它产生列表的最后一个元素作为输入提供。 请检查以下示例。

main = do 
   let x = [1..10]   
   putStrLn "Our list is:"  
   print (x) 
   putStrLn "The last element of our list is:" 
   print (last x)

它将产生以下输出 -

Our list is:
[1,2,3,4,5,6,7,8,9,10]
The last element of our list is:
10

初始化功能

Init完全与tail函数相反。 它将列表作为参数并返回整个列表而不包含最后一个条目。

main = do 
   let x = [1..10]   
   putStrLn "Our list is:"  
   print (x) 
   putStrLn "Our list without the last entry:"  
   print (init x) 

现在,观察它的输出 -

Our list is:
[1,2,3,4,5,6,7,8,9,10]
Our list without the last entry:
[1,2,3,4,5,6,7,8,9]

空函数

Null是一个布尔检查函数,它对String起作用,只有当给定列表为空时才返回True ,否则返回False 。 以下代码检查提供的列表是否为空。

main = do 
   let x = [1..10]   
   putStrLn "Our list is:"  
   print (x) 
   putStrLn "Is our list empty?"  
   print (null x)

它将产生以下输出 -

Our list is:
[1,2,3,4,5,6,7,8,9,10]
Is our list empty?
False

反向功能

它适用于String输入,并将整个输入转换为相反的顺序,并作为结果输出一个输出。 以下是此功能的代码库。

main = do 
   let x = [1..10]  
   putStrLn "Our list is:" 
   print (x) 
   putStrLn "The list in Reverse Order is:" 
   print (reverse x)

它将产生以下输出 -

Our list is:
[1,2,3,4,5,6,7,8,9,10]
The list in Reverse Order is:
[10,9,8,7,6,5,4,3,2,1]

长度函数

此函数用于计算作为参数给出的list的长度。 看看下面的例子 -

main = do 
   let x = [1..10]   
   putStrLn "Our list is:" 
   print (x) 
   putStrLn "The length of this list is:" 
   print (length x)

我们的列表中有10个元素,因此我们的代码将产生10作为输出。

Our list is:
[1,2,3,4,5,6,7,8,9,10]
The length of this list is:
10

采取功能

Take函数用于从另一个String创建子字符串。 以下代码显示了如何在Haskell中使用take函数 -

main = print(take 5 ([1 .. 10])) 

该代码生成一个子字符串,其中包含来自提供列表的5个元素 -

[1,2,3,4,5]

掉落功能

此函数还用于生成子字符串。 它的作用与take函数相反。 看下面这段代码 -

main = print(drop 5 ([1 .. 10])) 

代码从提供的列表中删除前5个元素,并打印剩余的5个元素。 它将产生以下输出 -

[6,7,8,9,10]

最大功能

此函数用于从提供的列表中查找具有最大值的元素。 让我们看看如何在实践中使用它 -

main = do 
   let x = [1,45,565,1245,02,2]   
   putStrLn "The maximum value element of the list is:"  
   print (maximum x)

上面的代码将生成以下输出 -

The maximum value element of the list is:
1245

最小功能

此函数用于查找提供列表中具有最小值的元素。 它与maximum功能正好相反。

main = do 
   let x = [1,45,565,1245,02,2]   
   putStrLn "The minimum value element of the list is:"  
   print (minimum x)

上面代码的输出是 -

The minimum value element of the list is:
1

求和函数

顾名思义,此函数返回提供列表中存在的所有元素的总和。 以下代码采用5个元素的列表,并将它们的总和作为输出返回。

main = do 
   let x = [1..5] 
   putStrLn "Our list is:" 
   print (x) 
   putStrLn "The summation of the list elements is:" 
   print (sum x)

它将产生以下输出 -

Our list is:
[1,2,3,4,5]
The summation of the list elements is:
15

产品功能

您可以使用此函数乘以列表中的所有元素并打印其值。

main = do 
   let x = [1..5] 
   putStrLn "Our list is:" 
   print (x) 
   putStrLn "The multiplication of the list elements is:" 
   print (product x) 

我们的代码将产生以下输出 -

Our list is:
[1,2,3,4,5]
The multiplication of the list elements is: 
120

Elem功能

此函数用于检查提供的列表是否包含特定元素。 因此,它返回truefalse

以下代码检查提供的元素列表是否包含值786。

main = do 
   let x = [1,45,155,1785] 
   putStrLn "Our list is:" 
   print (x) 
   putStrLn "Does it contain 786?" 
   print (elem 786 (x))

它将产生以下输出 -

Our list is:
[1,45,155,1785]
Does it contain 786?
False

使用相同的代码检查提供的列表是否包含值1785。

Haskell - Function Composition

Function Composition是使用一个函数的输出作为另一个函数的输入的过程。 如果我们学习composition背后的数学会更好。 在数学中, compositionf{g(x)}表示,其中g()是一个函数,其输出用作另一个函数的输入,即f()

如果一个函数的输出类型与第二个函数的输入类型匹配,则可以使用任何两个函数实现函数组合。 我们使用点运算符(。)在Haskell中实现函数组合。

看一下下面的示例代码。 在这里,我们使用函数组合来计算输入数是偶数还是奇数。

eveno :: Int -> Bool 
noto  :: Bool -> String 
eveno x = if x `rem` 2 == 0 
   then True 
else False 
noto x = if x == True 
   then "This is an even Number" 
else "This is an ODD number" 
main = do 
   putStrLn "Example of Haskell Function composition" 
   print ((noto.eveno)(16))

这里,在main函数中,我们同时调用两个函数evenoeveno 。 编译器将首先使用16作为参数调用函数"eveno()" 。 此后,编译器将使用eveno方法的输出作为eveno noto()方法的输入。

其产出如下 -

Example of Haskell Function composition                
"This is an even Number"

由于我们提供数字16作为输入(这是偶数), eveno()函数返回true ,它成为eveno()函数的输入并返回输出:“这是偶数”。

Haskell - Modules

如果您已经使用过Java,那么您就会知道如何将所有类绑定到名为package的文件夹中。 同样,Haskell可以被视为modules的集合。

Haskell是一种函数式语言,所有东西都表示为表达式,因此可以将模块称为类似或相关类型的函数的集合。

您可以import功能从一个模块导入另一个模块。 在开始定义其他函数之前,应首先使用所有“import”语句。 在本章中,我们将学习Haskell模块的不同功能。

列表模块

List提供了一些很棒的函数来处理list类型数据。 导入List模块后,您可以使用各种功能。

在以下示例中,我们使用了List模块下可用的一些重要功能。

import Data.List  
main = do  
   putStrLn("Different methods of List Module") 
   print(intersperse '.' "iowiki.com") 
   print(intercalate " " ["Lets","Start","with","Haskell"]) 
   print(splitAt 7 "HaskellTutorial") 
   print (sort [8,5,3,2,1,6,4,2])

在这里,我们有许多功能,甚至没有定义它们。 这是因为这些功能在List模块中可用。 导入List模块后,Haskell编译器在全局命名空间中提供了所有这些功能。 因此,我们可以使用这些功能。

我们的代码将产生以下输出 -

Different methods of List Module
"T.u.t.o.r.i.a.l.s.p.o.i.n.t...c.o.m"
"Lets Start with Haskell"
("Haskell","Tutorial")
[1,2,2,3,4,5,6,8]

字符模块

Char模块有许多预定义的函数可以使用Character类型。 看看下面的代码块 -

import Data.Char 
main = do  
   putStrLn("Different methods of Char Module") 
   print(toUpper 'a') 
   print(words "Let us study tonight") 
   print(toLower 'A')

这里, toUppertoLower函数已经在Char模块中定义。 它将产生以下输出 -

Different methods of Char Module
'A'
["Let","us","study","tonight"]
'a'

地图模块

Map是未排序的增值对类型数据类型。 它是一个广泛使用的模块,具有许多有用的功能。 以下示例显示如何使用Map模块中可用的预定义函数。

import Data.Map (Map) 
import qualified Data.Map as Map  --required for GHCI  
myMap :: Integer -> Map Integer [Integer] 
myMap n = Map.fromList (map makePair [1..n]) 
   where makePair x = (x, [x])  
main = print(myMap 3)

它将产生以下输出 -

fromList [(1,[1]),(2,[2]),(3,[3])] 

设置模块

Set模块有一些非常有用的预定义函数来操作数学数据。 集合实现为二叉树,因此集合中的所有元素必须是唯一的。

看一下下面的示例代码

import qualified Data.Set as Set   
text1 = "Hey buddy"   
text2 = "This tutorial is for Haskell"   
main = do  
   let set1 = Set.fromList text1   
       set2 = Set.fromList text2 
   print(set1) 
   print(set2)    

在这里,我们将String修改为Set。 它将产生以下输出。 观察输出集没有重复的字符。

fromList " Hbdeuy"
fromList " HTaefhiklorstu"

定制模块

让我们看看我们如何创建一个可以在其他程序中调用的自定义模块。 要实现这个自定义模块,我们将创建一个名为"custom.hs"的单独文件以及我们的"main.hs"

让我们创建自定义模块并在其中定义一些函数。

custom.hs

module Custom ( 
   showEven, 
   showBoolean 
) where 
showEven:: Int-> Bool 
showEven x = do 
if x 'rem' 2 == 0 
   then True 
else False 
showBoolean :: Bool->Int 
showBoolean c = do 
if c == True 
   then 1 
else 0 

我们的Custom模块准备就绪。 现在,让我们将其导入程序。

main.hs

import Custom 
main = do 
   print(showEven 4) 
   print(showBoolean True) 

我们的代码将生成以下输出 -

True
1

showEven函数返回True ,因为“4”是偶数。 showBoolean函数返回“1”,因为我们传递给函数的布尔函数是“True”。

Haskell - Input & Output

到目前为止我们讨论过的所有例子都是静态的。 在本章中,我们将学习与用户动态通信。 我们将学习Haskell中使用的不同输入和输出技术。

文件和流

到目前为止,我们已经对程序本身的所有输入进行了硬编码。 我们一直在从静态变量中获取输入。 现在,让我们学习如何从外部文件读取和写入。

让我们创建一个文件并将其命名为“abc.txt”。 接下来,在此文本文件中输入以下行:“欢迎使用IoWiki。在这里,您将获得学习Haskell的最佳资源。”

接下来,我们将编写以下代码,该代码将在控制台上显示此文件的内容。 在这里,我们使用readFile()函数读取文件,直到找到EOF字符。

main = do  
   let file = "abc.txt" 
   contents <- readFile file 
   putStrLn contents   

上面的代码将文件“abc.txt”作为String读取,直到遇到任何End of File字符。 这段代码将生成以下输出。

Welcome to IoWiki
Here, you will get the best resource to learn Haskell.

观察终端上打印的内容是否写入该文件中。

Command Line Argument

Haskell还提供了通过命令提示符操作文件的工具。 让我们回到我们的终端并输入"ghci" 。 然后,键入以下命令集 -

let file = "abc.txt" 
writeFile file "I am just experimenting here." 
readFile file 

在这里,我们创建了一个名为“abc.txt”的文本文件。 接下来,我们使用命令writeFile在文件中插入了一个语句。 最后,我们使用命令readFile在控制台上打印文件的内容。 我们的代码将产生以下输出 -

I am just experimenting here.

异常 (Exceptions)

可以将exception视为代码中的错误。 这是编译器在运行时没有获得预期输出的情况。 与任何其他优秀的编程语言一样,Haskell提供了一种实现异常处理的方法。

如果你熟悉Java,那么你可能知道Try-Catch块,我们通常会抛出一个错误并在catch块中catch 。 在Haskell中,我们也有相同的函数来捕获运行时错误。

try的函数定义看起来像“try :: Exception e =”IO a - “IO(Either)”。 看一下下面的示例代码。 它显示了如何捕获“除以零”异常。

import Control.Exception 
main = do 
   result <- try (evaluate (5 `div` 0)) :: IO (Either SomeException Int) 
   case result of 
      Left ex   -> putStrLn $ "Caught exception: " ++ show ex 
      Right val -> putStrLn $ "The answer was: " ++ show val 

在上面的例子中,我们使用了Control.Exception模块的内置try函数,因此我们预先捕获异常。 上面的代码将产生屏幕下方的输出。

Caught exception: divide by zero 

Haskell - Functor

Haskell中的Functor是一种可以映射的不同类型的函数表示。 它是实现多态性的高级概念。 根据Haskell开发人员的说法,所有类型如List,Map,Tree等都是Haskell Functor的实例。

Functor是一个内置类,其函数定义如下 -

class Functor f where 
   fmap :: (a -> b) -> f a -> f b 

通过这个定义,我们可以得出结论, Functor是一个函数,它接受一个函数,比如fmap()并返回另一个函数。 在上面的例子中, fmap()是函数map()的通用表示。

在下面的示例中,我们将看到Haskell Functor的工作原理。

main = do  
   print(map (subtract 1) [2,4,8,16])      
   print(fmap (subtract 1) [2,4,8,16])   

这里,我们在列表中使用map()fmap()进行减法运算。 您可以观察到两个语句将产生包含元素[1,3,7,15]的列表的相同结果。

这两个函数都调用了另一个名为subtract()函数来产生结果。

[1,3,7,15]
[1,3,7,15]

那么, mapfmap? 不同之处在于它们的使用。 Functor使我们能够在不同的数据类型中实现更多的功能主义者,比如“just”和“Nothing”。

main = do 
   print (fmap  (+7)(Just 10)) 
   print (fmap  (+7) Nothing)

上面的代码将在终端上产生以下输出 -

Just 17
Nothing

应用编织者

Applicative Functor是一个普通的Functor,具有Applicative Type类提供的一些额外功能。

使用Functor,我们通常将现有函数映射到其中定义的另一个函数。 但是没有任何方法可以将Functor中定义的函数映射到另一个Functor。 这就是为什么我们有另一个名为Applicative Functor 。 这种映射工具由Control模块下定义的Applicative Type类实现。 这个类只给我们两种方法:一个是pure ,另一个是《*》

以下是Applicative Functor的类定义。

class (Functor f) => Applicative f where   
   pure :: a -> f a   
   (<*>) :: f (a -> b) -> f a -> f b   

根据实现,我们可以使用两种方法映射另一个Functor: "Pure""《*》" 。 “Pure”方法应该采用任何类型的值,它将始终返回该值的Applicative Functor。

以下示例显示了Applicative Functor的工作原理 -

import Control.Applicative 
f1:: Int -> Int -> Int 
f1 x y = 2*x+y  
main = do  
   print(show $ f1 <$> (Just 1) <*> (Just 2) ) 

在这里,我们在函数f1的函数调用中实现了applicative functor。 我们的程序将产生以下输出。

"Just 4"

Monoids

我们都知道Haskell以函数的形式定义了所有东西。 在函数中,我们有选项将输入作为函数的输出。 这就像Monoid

Monoid是一组函数和运算符,其输出独立于其输入。 我们取一个函数(*)和一个整数(1)。 现在,无论输入是什么,其输出将仅保持相同的数字。 也就是说,如果将数字乘以1,您将获得相同的数字。

这是monoid的Type Class定义。

class Monoid m where  
   mempty :: m 
   mappend :: m -> m -> m  
   mconcat :: [m] -> m 
   mconcat = foldr mappend mempty 

看一下下面的例子,了解在Haskell中使用Monoid。

multi:: Int->Int 
multi x = x * 1 
add :: Int->Int 
add x = x + 0 
main = do  
   print(multi 9)  
   print (add 7)

我们的代码将产生以下输出 -

9
7

这里,函数“multi”将输入乘以“1”。 类似地,函数“add”将输入添加为“0”。 在这两种情况下,输出将与输入相同。 因此,函数{(*),1}{(+),0}是幺半群的完美例子。

Haskell - Monads

Monads只是一种具有一些额外功能的Applicative Functor。 它是一个Type类,它管理称为monadic rules三个基本monadic rules

所有这三条规则都严格适用于Monad声明,如下所述 -

class Monad m where  
   return :: a -> m a 
   (>>=) :: m a -> (a -> m b) -> m b 
   (>>) :: m a -> m b -> m b 
   x >> y = x >>= \_ -> y 
   fail :: String -> m a  
   fail msg = error msg 

适用于Monad声明的三项基本法律是 -

  • Left Identity Law - return函数不会更改值,也不应更改Monad中的任何内容。 它可以表示为“return”=“mf = mf”。

  • Right Identity Law - return函数不会改变值,它不应该改变Monad中的任何内容。 它可以表示为“mf”=“return = mf”。

  • Associativity - 根据这个定律,Functors和Monad实例应该以相同的方式工作。 它可以在数学上表示为“(f”==“g)”=“h = f”=“(g”= h)“。

前两个定律迭代相同的点,即return应该在bind运算符的两侧具有标识行为。

我们在之前的例子中已经使用了很多Monad而没有意识到他们是Monad。 请考虑以下示例,其中我们使用List Monad生成特定列表。

main = do
   print([1..10] >>= (\x -> if odd x then [x*2] else []))

此代码将生成以下输出 -

[2,6,10,14,18]

Haskell - Zippers

Haskell中的Zippers基本上是指向数据结构(如tree某个特定位置的指针。

让我们考虑一个具有5个元素[45,7,55,120,56]tree ,它可以表示为完美的二叉树。 如果我想更新此列表的最后一个元素,那么我需要遍历所有元素以在更新之前到达最后一个元素。 对?

但是,如果我们能够以这样的方式构造我们的树,即具有N元素的树是[(N-1),N]的集合。 然后,我们不需要遍历所有不需要的(N-1)元素。 我们可以直接更新第N个元素。 这正是Zipper的概念。 它聚焦或指向树的特定位置,我们可以在不移动整个树的情况下更新该值。

在下面的示例中,我们在List中实现了Zipper的概念。 以同样的方式,可以在treefile数据结构中实现Zipper。

data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
type Zipper_List a = ([a],[a])    
go_Forward :: Zipper_List a -> Zipper_List a   
go_Forward (x:xs, bs) = (xs, x:bs)   
go_Back :: Zipper_List a -> Zipper_List a   
go_Back (xs, b:bs) = (b:xs, bs)    
main = do 
   let list_Ex = [1,2,3,4] 
   print(go_Forward (list_Ex,[]))       
   print(go_Back([4],[3,2,1])) 

当您编译并执行上述程序时,它将产生以下输出 -

([2,3,4],[1]) 
([3,4],[2,1])

在这里,我们在前进或后退时专注于整个弦乐的元素。

↑回到顶部↑
WIKI教程 @2018