热爱函数式的你,句句纯正的 Haskell【函数篇】

2022-09-19 11:15:40 浏览数 (1)

函数本质

Haskell 里变量的值在绑定后不会改变,所有变量一定意义上可以理解为定值。 无论如何,定义过的值是没法再改变的。

Haskell 值与函数是统一的,函数只是需要其他参数输入的值。如果定义的是函数,那么这个函数的行为在运行过程中也是不会改变的,对于某一个特定的输入返回的结果总是确定的,这样的函数为纯函数。

有人觉得不改内存状态的想法听上去很荒诞,甚至觉得这样是没有办法做计算的。其实,这两种想法都是错误的。不改变内存状态自有道理,而其它编程语言可以完成的工作,Haskell 一样可以完成。

再三强调,在 Haskell 中,函数与值没有本质的区别,它可以是单一的定值,也可以是任意两个函数间的映射;

实际上,在 Haskell 世界里,所有的运算符号都可以被看做是函数,如加号 是一个需要两个参数的函数。

代码语言:javascript复制
Prelude> ( )5 7
12

函数定义

直接上干货~

实现:f(x) = 4x 1

代码语言:javascript复制
Prelude> f1(x)=4*x   1
Prelude> f1 4
17
Prelude> :t f1
f1 :: Num a => a -> a

再比如实现:f(x,y) = 4x 5y 1,

我们可以设想到这个函数的类型是:

代码语言:javascript复制
f2 :: Num a => (a, a) -> a

验证一下:

代码语言:javascript复制
Prelude> f2(x,y)=4*x 5*y 1
Prelude> f2(4,3)
32
Prelude> :t f2
f2 :: Num a => (a, a) -> a

确实如此;b( ̄▽ ̄)d

Haskell 中定义的函数的大致格式是这样的:

代码语言:javascript复制
// 定义方式 1

函数名 (参数1,参数2,...) = 函数体

// 定义方式 2

函数名 参数1 参数2.. =函数体

// 类型

函数名 :: 参数1的类型->参数2的类型->...->结果类型

说这么多,不如在编译器中感受感受:

代码语言:javascript复制
Prelude> f3 x y z=3*x 2*y-z
Prelude> f3 1 2 3
4
Prelude> :t f3
f3 :: Num a => a -> a -> a -> a

我们惊人的发现,从定义方式 1 到 定义方式 2 的过程,就是柯里化的过程!

λ表达式

Haskell 还有另外一种书写函数的格式,即 λ 表达式;

代码语言:javascript复制
// 定义方式 3

函数名= (参数1 -> 参数2 -> ... ->函数体)

示例:

代码语言:javascript复制
Prelude> f4= (x -> y -> x*y)
Prelude> f4 2 3
6

Prelude> f5 =(x -> y->4*x 5*y 1)
Prelude> f5 2 3
24

在使用一些高阶函数时,如果不想定义新函数,可以使用 λ 表达式来定义这个函数:

代码语言:javascript复制
Prelude> map(x->2*x 7)[1..10]
[9,11,13,15,17,19,21,23,25,27]

x -> 2*x 7 是一个没有名字的匿名函数,在 Haskell 中,通常用 λ 表达式来构造匿名函数;

阶段小结

小结中,我们再来回归三种定义函数的方式:

代码语言:javascript复制
// 方式 1:

f2(x,y)=4*x 5*y 1

// 方式 2:

f3 x y z=3*x 2*y-z

// 方式 3:

f4= (x -> y -> x*y)

函数作为 Haskell 基础之基础,牢记 3 种函数定义的方式则是基础之基础之基础。

第 1 种方式到 第 2 种方式是柯里化思想的体现。柯里化如此自然,就像呼吸一般~还有 λ 表达式,是实现匿名函数的有效方式!!

以上,真的要在编译器中敲一敲才会有更多体验。看看不同语言对于函数申明及调用的不同实现,体会函数式编程参数在函数中的输入、传递 ......

我是掘金安东尼,输出暴露输入,技术洞见生活,再会~

0 人点赞