基本函数:

#light

open System
open System.IO

// Gets all files under a given folder
let rec filesUnderFolder rootFolder = 
    seq {
        for file in Directory.GetFiles(rootFolder) do
            yield file
        for dir in Directory.GetDirectories(rootFolder) do
            yield! filesUnderFolder dir }

// Gets the information about a file
let fileInfo filename = new FileInfo(filename)

// Gets the file size from a FileInfo object
let fileSize (fileinfo : FileInfo) = fileinfo.Length

// Converts a byte count to MB
let bytesToMB (bytes : Int64) = bytes / (1024L * 1024L)

下面是以前比较流行的方法,命令式编程方式来计算结果,它将调用序列中的每个函数,存储结果,然后将结果传到下一个函数。

跛足的(别扭的)命令式代码

// Doing things the lame way
let photosInMB_lame =
    let folder = @"C:\Users\chrsmith\Pictures\"
    let filesInFolder = filesUnderFolder folder
    let fileInfos     = Seq.map fileInfo filesInFolder
    let fileSizes     = Seq.map fileSize fileInfos
    let totalSize     = Seq.fold (+) 0L fileSizes
    let fileSizeInMB  = bytesToMB totalSize
    fileSizeInMB

我们可以在以上的代码基础上使用管道运算符(|>),此运算符的定义如下:

let inline (|>) x f = f x

管道运算符可以让你重新排列函数的顺序,通过这样来实现参数出现在函数体的前面。使用此运算符之后,你可以“管道(pipe)”多个函数一起从而避免使用哪些临时变量。那么接下来我们将文件路径管道(pipe)给函数“filesUnderFolder,然后管道(pipe)其结果序列给函数“Seq.map fileInfo,接下来管道(pipe)其结果给“Seq.map fileSize”, 如此等等。

优化,使用管道运算符

// Using the Pipe-Forward operator (|>)
let photosInMB_pipeforward =
    @"C:\Users\chrsmith\Pictures\"
    |> filesUnderFolder
    |> Seq.map fileInfo
    |> Seq.map fileSize
    |> Seq.fold (+) 0L 
    |> bytesToMB

使用管道运算符这个方案很优雅。但是其中包含了一个不明显的问题,那就是你需要提供初始参数从而开始这些管道处理。因此,为了能重复使用代码,你需要一些像这样的改动:

// A reusable function using Piping
let photosInMB_reusable baseFolder =
    baseFolder
    |> filesUnderFolder
    |> Seq.map fileInfo
    |> Seq.map fileSize
    |> Seq.fold (+) 0L 
    |> bytesToMB

老实地说,最近我用这种方法写了许多的代码。创建带一个参数的函数,然后将此参数传到一系列的连续的管道函数。但是啊,其实还有一种更好的方法呢!

介绍一下合成函数运算符(>>:

let inline (>>) f g x = g(f x)

它的读法是:给定两个函数fg,给定一个值x,计算x传入f后的结果,并将此结果传入g.这里有趣的事情是,你可以使用(>>)函数并且只传入参数f g,那么它的结果将是一个函数,且此函数只需要一个参数,然后产生结果g(f(x)).

下面是将一些小函数组合成一个函数的小例子:

let negate x = x * -1
let square x = x * x
let print  x = printfn "The number is: %d" x

let square_negate_then_print = square >> negate >> print
asserdo square_negate_then_print 2

当运行后,会打印出“-4.我们已经成功地将几个函数拼接在一起了,就像我们使用(|>)一样,但是我们并不需要声明一个参数并将它传入到我们的函数链。取而代之的是我们依靠利用(>>)创建好的不完整程序来实现它。现在,我可以使用合成函数来重写一下我们的目录统计装置(就是上面用来计算文件夹大小的程序):

// Using the Function-Composition operator (>>)
let getFolderSize = 
    filesUnderFolder 
    >> Seq.map fileInfo 
    >> Seq.map fileSize 
    >> Seq.fold (+) 0L 
    >> bytesToMB
let photosInMB_funccomp = getFolderSize @"C:\Users\chrsmith\Pictures\"

使用合成函数(>>)时,你可以像使用管道(|>)一样,除了你不需要指定最前面的第一个参数。(>>)可以实现更加精确的代码,并提供了一种便于理解你所使用的函数的新思路。

原文链接: http://blogs.msdn.com/b/chrsmith/archive/2008/06/14/function-composition.aspx