这是面向初学者的函数式语言 Haskell 系列的第一篇1
我使用Python已经有十年了,在编写Python时,我总是倾向于以“函数式”方式编写代码 - map
、filter
、lambda
等等,这让我感到整洁和快乐。遵循这种温暖的感觉,我决定尝试一种真正的函数式语言。
所以我在网上阅读了一些教程,并阅读了著名的Learn You a Haskell for Great Good!这是一段漫长而痛苦的旅程——还没有结束。回顾一番之后,我想根据我的 Python 经验分享我自己对 Haskell 的理解。
什么是 Haskell
Haskell ( /ˈhæskəl/ [27] ) 是一种通用的、静态类型的、纯函数式 编程语言,具有类型推断和惰性求值。[28] [29] Haskell 为教学、研究和工业应用而设计,开创了许多高级编程语言功能,例如类型类,这些功能支持类型安全的运算符重载。
很多非常花哨的词,希望这不会是你通往启蒙的道路上的障碍。
可以在此处找到安装指南:https : //www.haskell.org/platform/,根据你自己的操作系统选择安装。
交互式环境
就像大多数编程语言一样,Haskell 也有一个交互式环境,可以通过在终端输入以下内容打开: ghci
让我们试一下:
➜ ~ ghci
GHCi, version 8.10.5: https://www.haskell.org/ghc/ :? for help
Prelude> a = 4
Prelude> b = 6
Prelude> c = a + b
Prelude> c
10
这里没什么值得惊讶的,Haskell 拥有您可以想象的所有基本数据类型:Boolean、Num、Char(用单引号括起来的字符)、String(用双引号括起来的任意数量的字符)等等。
Types
GHCI 中有一个有用的命令,:t
将返回输入内容的类型:
Prelude> :t 'a'
'a' :: Char
Prelude> :t "abcd"
"abcd" :: [Char]
我们可以看到它'a'
是 Char 类型的,而"abcd"
的类型为[Char]
,一个 Char类型的列表。Char列表等于字符串?让我们检查一下是否属实。
我们将使用列表来验证它:
Prelude> let a = ['a', 'b', 'c']
Prelude> :t a
a :: [Char]
Prelude> let b = "abc"
Prelude> :t b
b :: [Char]
好吧,同样的类型,很好。但真的一样吗?
Prelude> a == b
True
确实一样。
Functions
现在你可以在文件中编写Haskell 代码了。 打开一个文件并将其命名为 fn.hs
,文件最好在已启动的ghci
所在目录,否则我建议您CTRL+D,结束当前ghci
,并在文件所在目录启动新的ghci
。
我们将编写以下内容:
powerOfTwo x = x ** 2
然后,您可以通过ghci
运行以下命令加载您的脚本,而不是编译并运行它(稍后将介绍):
Prelude> :l fn.hs
[1 of 1] Compiling Main ( fn.hs, interpreted )
Ok, one module loaded.
如您所见,我们的文件 fn.hs
已加载到环境中,我们现在可以使用它:
*Main> powerOfTwo 4
16.0
*Main> powerOfTwo 5
25.0
*Main> powerOfTwo 6
36.0
看起来挺好的。现在计算 3 的幂的函数怎么样?让我们编辑我们的文件 fn.hs
:
powerOfTwo x = x ** 2
powerOfThree x = x ** 3
然后我们可以很方便的重新加载它:
*Main> :r
[1 of 1] Compiling Main ( fn.hs, interpreted )
Ok, one module loaded.
请注意,ghci
有自动补全的功能,输入power
,然后按TAB
键,将会列出所有power
开头的函数。让我们试试我们的powerOfThree
函数:
*Main> powerOfThree 2
8.0
*Main> powerOfThree 23
12167.0
*Main> powerOfThree 4
64.0
看起来挺好的。但是现在,如何进行一些重构,只需要一个函数就可以计算 x 的 y 次幂?这样,当新要求说我们需要 7 和 42 的幂时,您不需要写重复代码。它应该是这样的:
powerOfTwo x = x ** 2
powerOfThree x = x ** 3
powerOfX x y = x ** y
然后像往常一样测试我们的代码:
*Main> :r
[1 of 1] Compiling Main ( fn.hs, interpreted )
Ok, one module loaded.
*Main> powerOf
powerOfThree powerOfTwo powerOfX
*Main> powerOfX 2 3
8.0
*Main> powerOfX 2 2
4.0
*Main> powerOfX 2 7
128.0
*Main> powerOfX 7 2
49.0
奇妙吧,我们重新加载了我们的脚本,保留了我们之前的函数(在第二行输入powerof
后按TAB
),并且能够使用我们的带两个参数新函数。
现在假设我们的函数powerOfTwo
和powerOfThree
在整个代码库中被调用了 1,000 次,并且很难将它们全部替换为powerOfx
. 但我们仍然想重构它。我们可以执行以下操作:
powerOfTwo x = powerOfX x 2
powerOfThree x = powerOfX x 3
powerOfX x y = x ** y
你会注意到这里的顺序并不重要,你仍然可以在后面声明powerOfX
,即使它会在更前面的地方使用到。
Main
现在,你不想再在ghci
中编写Haskell了,你想把文件编译成二进制的可执行文件。
你需要这样做:
powerOfTwo x = powerOfX x 2
powerOfThree x = powerOfX x 3
powerOfX x y = x ** y
main = do
putStrLn "Let's calculate some power !"
print (powerOfX 2 2)
这里有几件事需要说明:
- 如果你想运行一个程序,你需要从
main = do
这个概念开始,这跟许多其他的编程语言类似。我们将在讨论 I/O 后详细回顾这一点。 putStrLn
是您常用的函数,在标准输出中输出一个字符串,然后换行。- 当我们调用
powerOfX
时,使用了print
函数,它与putStrLn
的区别是没有换行。但重点是括号,在Haskell中没有针对函数调用的特定语法。之前我们简单地使用空格来分隔我们的参数,但在这里,我们在print
之后调用了powerOfX
,因此我们需要使用括号来解释参数属于哪个函数。另一种方法是使用$
:
print $ powerOfX 2 2
更多的解释会在接下来的文章中给出,因为它涉及到各种概念,比如柯里化。
最后,我们可以像这样编译和调用我们的程序:
$ ghc --make fn.hs
[1 of 1] Compiling Main ( fn.hs, fn.o )
Linking fn ...
$ ./fn
Let's calculate some power !
4.0
请注意,您可以使用许多不同的ghc
编译选项,更多详细信息可以在他们的文档中找到。
结论
我希望这不会太难,尽管我知道你们中的一些人在后面睡着了。在下一篇文章中,我们将讨论函数定义(我们只看实现)和列表,并与 Python 的代码进行一些比较。
本文翻译自Haskell series part 1 ↩︎