F#奇妙游(4):F#的函数
let关键词
如果熟悉其他语言,你可能会认为let关键词是用来声明变量的。但在F#中,let关键词是用来声明值的。值是不可变的,而变量是可变的。(注:在F#中,变量是用关键词mutable来声明的。)值的概念,是函数式编程中的一个重要概念。值和变量的区别,是函数式编程和命令式编程的一个重要区别。变量,本质上是内存中一个特定长度的存储区域,用来存储数据。而值,可以理解为是一个表达式,用来计算数据,函数式编程中并不关心其对应的地址,只使用值代表函数的输入或者输出。
let x = 1
let f x = x * x
上面的例子中,x是一个值,而不是一个变量。x的值是1。f是一个函数,它的输入是x,输出是x * x。
从这里,可以看到两个概念:
- 不可变的值;
- 函数也是一个值。
在函数式编程中,函数是一等公民,也就是说,函数可以作为参数传递给其他函数,也可以作为返回值返回给其他函数。这对于学习过范函或者高等范函的人来说,应该不陌生。在
另外还有一个值的注意的是,F#不是一个纯粹的函数式编程语言,因此F#还有对象和变量,也能通过ref
获得变量的地址。
let mutable y = 1.0
let z = ref y
do z.Value <- 2.0
如果是初次接触函数式编程语言,应该首先把概念局限在值的范围来解决问题,这样可以更快地理解函数式编程的思想。所以我们首先明确下面的概念:
- 值是不可变的;
- 函数也是一个值;
- 使用关键词
let
来声明值(包括函数)。
数学函数及值的类型
数学中的函数有三个核心概念:自变量的取值范围、因变量的取值范围和自变量到因变量的映射。这三个要素在F#中对应为三个概念:输入量的类型,输出量的类型,函数的值。
例如,数学函数
y
=
f
(
x
)
=
x
2
y = f(x) = x ^ 2
y=f(x)=x2
直接映射到F#中,就是:
let f x = x * x
跟值的定义采用同样的语法,这样一来,函数是一个值的概念也自恰起来。因为函数是一个值,那么函数也自然有其类型。从数学函数那个看,
f : Ω → Π f : \Omega \to \Pi f:Ω→Π
对应到F#中,就是
let f (x:int): int = x * x
// val f : int -> int
适应值的概念,以及值的类型概念,F#被设计为静态强类型的语言,也就是在编译(从源代码到dotnet字节码或者本地机器代码)的过程中,所有类型都应该确定。为了避免像上面这个类子中一样频繁地书写类型,F#提供了类型推断的功能,也就是说,F#编译器可以根据上下文推断出类型。所以,上面的例子可以简化为:
let f x = x * x
// val f : int -> int
当发生歧义的时候,或者需要强制类型的时候,可以使用:
来指定类型。
多变量函数与F#函数偏应用
在数学上,多变量函数是一个很常见的概念。例如,函数
f ( x , y ) = x 2 + y 2 f(x, y) = x ^ 2 + y ^ 2 f(x,y)=x2+y2
是一个两个变量的函数。在F#中,可以这样定义:
let f x y = x * x + y * y
// val f : int -> int -> int
从数学上来讲,这个可以定义
g ( y ) ≜ f ( x , y ) ∣ x = x ˉ = x ˉ 2 + y 2 f ( x , y ) ≜ f ( x , y ) x = x ˉ , y = y ˉ = x ˉ 2 + y ˉ 2 \begin{split} & g(y) \triangleq f(x, y)|_{x=\bar{x}} = \bar{x}^2 + y^2 \\ & f(x,y) \triangleq f(x,y)_{x=\bar{x}, y=\bar{y}} = \bar{x}^2 + \bar{y}^2 \\ \end{split} g(y)≜f(x,y)∣x=xˉ=xˉ2+y2f(x,y)≜f(x,y)x=xˉ,y=yˉ=xˉ2+yˉ2
对应中间的F#函数,可以定义为:
let g y = f x y
// val g : int -> int
这个函数定义可以简写为:
let g = f x
// val g : int -> int
事实上,在程序中,可以直接把(f x)
当作一个函数使用,这就是F#中函数偏应用机制。
函数的组合
在数学中,函数的组合是一个很常见的概念。例如,函数
f ( x ) = x 2 f(x) = x ^ 2 f(x)=x2
和
g ( x ) = x + 1 g(x) = x + 1 g(x)=x+1
可以组合成
h ( x ) = f ( g ( x ) ) = ( x + 1 ) 2 h(x) = f(g(x)) = (x + 1) ^ 2 h(x)=f(g(x))=(x+1)2
在F#中,可以这样定义:
let f x = x * x
let g x = x + 1
let h x = f (g x)
// val f : int -> int
// val g : int -> int
// val h : int -> int
在F#中,函数的组合可以使用>>
操作符来实现:
let h = f >> g
// val h : (int -> int)
函数的柯里化
在数学中,函数的柯里化是一个很常见的概念。例如,函数
f ( x , y ) = x 2 + y 2 f(x, y) = x ^ 2 + y ^ 2 f(x,y)=x2+y2
可以柯里化为
f ( x ) = ( y ↦ x 2 + y 2 ) f(x) = (y \mapsto x ^ 2 + y ^ 2) f(x)=(y↦x2+y2)
在F#中,可以这样定义:
let f x y = x * x + y * y
let f' x = (fun y -> f x y)
let f'' = (fun x -> (fun y -> f x y))
// val f : int -> int -> int
// val f' : int -> (int -> int)
// val f'' : int -> (int -> int)
函数的反柯里化
在数学中,函数的反柯里化是一个很常见的概念。例如,函数
f ( x ) = ( y ↦ x 2 + y 2 ) f(x) = (y \mapsto x ^ 2 + y ^ 2) f(x)=(y↦x2+y2)
可以反柯里化为
f ( x , y ) = x 2 + y 2 f(x, y) = x ^ 2 + y ^ 2 f(x,y)=x2+y2
在F#中,可以这样定义:
let f x = (fun y -> x * x + y * y)
let f' x y = (f x) y
let f'' = (fun x y -> (f x) y)
// val f : int -> (int -> int)
// val f' : int -> (int -> int)
// val f'' : int -> (int -> int)
总结
F#中的函数是一个值,也就是说,函数也是一个值。函数的类型是输入量的类型到输出量的类型的映射。函数的组合、偏应用、柯里化和反柯里化是F#中的常见操作。
这些都是函数式编程中的最基础概念,跟数学中的函数概念关系十分密切,也是函数式编程中的基础。最好是能够反复体会,不然后面看到代码时就会有各种各样的困惑。
但是只要理解值不可变的核心观念,一切都可以理解。