在大多数其它的编程语言里,函数和数据被当做是两个完全不同的概念。然而在函数式编程语言中,对待函数就像其它别的数据类型一样。例如,函数可以当做另一个函数的参数,除此之外,函数还可以创建并返回新的函数。
这个特性可以使我们的代码更加抽象以及算法的重复使用。看下面的一个例子:
下面的例子定义了一个negate函数,这个函数是个单一的int类型。当这个函数做为一个参数传递给List.map时,这个函数将应用于整个List,将对所有的List元素求反。
1 >let negate x=-x;;
2 val negate : int -> int //函数类型
3
4 >List.map negate [1 .. 10]
5 val it : int list = [-1;−2; −3; −4; −5; −6; −7; −8; −9; −10]
从上面我们也看到了,使用函数是非常方便的,但结果就是你得写很多简单的功能单一的函数,只单单利用自己并没有太多实用价值。例如我们上面例子,除了对一个数组求反恐怕其它的地方根本用不到。
所以我们不用把所有当参数传递的小函数都进行命名,而是可以通用一个匿名函数(C#里面也有!),又叫做lambda表达式的玩意儿,来创建一个内联函数。
要创建一个lambda表达式,很简单,使用"fun"关键字,紧接着是函数的参数和一个箭头"->"。
下面的代码片段,创建了一个lambda表达式并传递一个值为5的参数。当函数执行后,参数x将增加3,结果为8:
>(fun x -> x + 3) 5;;
val it : int = 8
现在我们用lambda表达式重写刚才的对数组取反的例子:
1 >List.map (fun i -> -i)[1 .. 10];;
2 val it : int list = [−1; −2; −3; −4; −5; −6; −7; −8; −9; −10]
注意:要注意让lambdas表达式变得尽量简单,因为当他们变的复杂之后,将变的很难以调试。特别是我们在自己的代码里复制粘贴这些lambdas的时候。。
局部函数的应用
函数编程的另一方面是局部函数应用,也就是其它人经常说的“柯里化”,这里我们也可以叫他局部套用。让我们来看下面一个简单的例子,功能就是使用.net库来追加一个文本文件。
1 >open System.IO
2 let appendFile (fileName : string) (text : string) =
3 use file = new StreamWriter(fileName,true)
4 file.WriteLine(text)
5 file.Close();;
6 //函数类型
7 val appendFile : string -> string -> unit
8
9 >appendFile @"D:\test.txt" "测试字符串追加函数...";;//函数调用
10 val it : unit = ()
看到没?我们已经可以用F#来写文件了!就是这么简单。
这个appendFile函数看上去非常简单,但是难道你想重新的写同一个文件?那么你就不得不总是保持文件路径做为第一个参数。这样子也许不错,但是我们可以创建一个新版本的appendFile函数,将第一个参数固定成@"D:\test.txt"。局部函数应用提供了指定函数的一些参数的功能,并且产生某个一些参数已经确定了的函数。我们可以重新设计一下appendFile的第一个参数,并产生一个只带一个参数的新函数:
1 >let curriedAppendFile = appendFile @"D:\test.txt";;
2 val curriedAppendFile : (string -> unit)
3
4 >//现在只需要传入文本
5 curriedAppendFile "测试2";;
6 val it : unit = ()
没错,这就是传说中的柯里化。appendFile函数的类型是:string -> string -> unit
所以当第一个参数传入后,结果将变为一个带着字符串的函数并且输出类型为unit,也就是string -> unit.
柯里化可以使代码更简单,但也可以使代码变的难以debug.所以不要在程序里乱用以至于增加更多的复杂度,在需要的时候用即可。
下一篇继续讨论柯里化。