20分钟快速上手,函数式编程
分类:微服架构

  • F# 探险之旅-选择不同的开发方式
  • F# 探险之旅-函数式编程(上)
  • F# 探险之旅-函数式编程(中)
  • F# 探险之旅-函数式编程(下)
  • F# 探险之旅-命令式编程(上)
  • F# 探险之旅-命令式编程(下)
  • F# 探险之旅-面向对象编程(上)
  • F# 探险之旅-面向对象编程(中)
  • F# 探险之旅-面向对象编程(下)
  • F# 探险之旅-透过 F# 理解函数式编程(上)
  • F# 探险之旅-透过 F# 理解函数式编程(中)
  • F# 探险之旅-F# 代码的组织
  • F# 探险之旅-在 F# 中进行单元测试
  • F# 20分钟快速上手-一
  • F# 20分钟快速上手-二
  • F# 20分钟快速上手-一
  • F# 20分钟快速上手-二



**操作符(Operator)

1.不可变性(Immutability)
您也许已经注意到,我一直使用“值(value)”来表示一个标识符(identifier),而不是“变量(variable)”。这是由于默认情况下,F#中的类型是不可变的(immutable),也就是说,一经创建即不可修改。看起来这是一个很大的限制,但是不可变性可以避免某种类型的bug。另外,不可变的数据天然地具备线程安全的特性,这意味着您无需在处理并行情况时担心同步锁的发生。我将在系列的第三篇中介绍异步编程。

从 Allen Lee 的《从C# 3.0到F#》一文开始,感觉园子里F#正在升温。Chris Smith写了一个F#的小系列,这里翻译出来与大家分享。

**

如果您确实需要修改数据,可使用F#的mutable关键字,它会创建一个变量(而不是值)。我们可以通过左箭头操作符(<-)来修改变量的值。

第一篇,从零开始编写我们的第一个F#程序。

F#中,可把操作符看作一种函数调用的更为优雅的方式。操作符有两种:前缀(prefix)和中缀(infix),前者接受一个操作数(operand),出现在操作数之前;后者接受两个或多个操作数,出现在头两个操作数之间。

> let mutable x = "the original value.";;
val mutable x : string
> printfn "x's value is '%s'" x;;
x's value is 'the original value.'
val it : unit = ()

什么是F#,我为何要学它?

F#提供了丰富的操作符集,可用于数字、布尔值、字符串和集合类型。这些操作符数量甚众,限于篇幅,在此不再一一详解。本文将着重介绍如何使用和定义操作符。

> x <- "the new one.";;
val it : unit = ()
> printfn "x's value is now '%s'" x;;
x's value is now 'the new one.'
val it : unit = ()

F#是一种.NET平台上的函数式编程语言。就像C#和VB.NET,F#可以利用.NET的核心类库,如WPF,WCF,VSTO等等,通过F#您甚至可以使用XNA编写XBox游戏。

类似于C#,F#的操作符也可以重载,也就是说,我们可以将不同的类型用于同一操作符,如“+”;但是与C#不同的是,各个操作数必须为相同的类型。F#的操作符重载规则与C#类似,因此任何BCL或者使用C#编写的.NET类库中的支持操作符重载的类在F#中一样支持重载。

2. 引用值(Reference values,Microsoft.FSharp.Core.Ref<_>)

仅仅如此并不意味着您应该去学习它。那为何要使用F#呢?作为一种函数式编程语言,F#使得某些领域的编程要比命令式编程(如使用C#)更为容易。并行编程(Parallel Programming)和面向语言编程(Language-Oriented Programming)是其中的两个领域。

let words = "To live " + "is " + " to function." 

引用值是另一种表示可修改数据的方式。但它不是将变量存储在堆栈(stack),引用值其实是一个指向存储在堆(heap)上的变量的指针(pointer)。在F#中使用可修改的值时会有些限制(比如不可以在内部lambda表达式中使用)。而ref对象则可被安全地传递,因为它们是不可变的record值(只是它有一个可修改的字段)。

如果您曾经编写过.NET应用程序,并感觉自己在努力使用手头的语言表达自己的想法,也许F#就是您在寻找的。

open System
let oneYearLater = DateTime.Now + new TimeSpan(365, 0, 0, 0, 0)

使用引用值时,用“:=”赋一个新值,使用“!”进行解引用。

上路

我们可以定义自己的操作符,也可以重定义已有的任何操作符(不建议这样做)。看看下面这种不好的做法:

> let refCell = ref 42;;
val refCell : int ref

首先得下载(F#的最新版本1.9.4.19)和安装。安装程序会在VS2005和VS2008中安装F#的项目系统(项目模板和项模板)。先来创建一个新的F#项目。

let (+) a b = a – b
print_int (1 + 2)

> refCell := -1;;
val it : unit = ()

图片 1

看到这里,你想到了什么?是不是:这分明是在定义一个函数嘛!所以我们前面说“可把操作符看作一种函数调用的更为优雅的方式”。我们重定义了“+”操作符,所以“1 + 2”的结果为-1,这当然没什么好处,在VS中使用FSI时,怎样把“+”改回它原本的含义呢?我一般在任务管理器中把fsi进程关掉,再按回车,“+”就回来了。

> !refCell;;
val it : int = –1

然后添加一个新的F#源文件。默认条件下,新建的源文件包含了很多“教学”代码,全部删除,然后输入下面的代码:

自定义的操作符不能包含字母和数字,可以使用的字符如下:
!$%&+-./<=>?@^|~
:

3. 模块(Modules)

#light

操作符的第一个字符可以是上面第一行的任意字符,其后的字符则可以是上面的任意字符了。定义语法与函数类似,除了要将操作括起来。看下面的例子:

在上篇文章中,我只是随意地声明了几个值和函数。您也许会问,“要把它们放在哪里呢?”,因为在C#中所有一切都要属于相应的类。尽管在F#中,我们仍然可以用熟悉的方式声明标准的.NET类,但它也有模块的概念,模块是值、函数和类型的集合(可以对比一下命名空间,后者只能包含类型)。

let square x = x * x
let numbers = [1 .. 10]
let squares = List.map square numbers
printfn "N^2 = %A" squares

let (+^*) a b = (a + b) * (a * b)

这也是我们能够访问“List.map”的原因。在F#库(FSharp.Core.dll)中,有一个名为“List”的模块,它包含了函数“map”。

open System
Console.ReadKey(true)

结果为30。

在快速开发的过程中,如果不需要花费时间去设计严格的面向对象类型体系,就可以采用模块来封装代码。要声明自己的模块,要使用module关键字。在下面的例子中,我们将为模块添加一个可修改的变量,该变量也是一个全局变量。

按下F5运行程序,您会看到:

列表(Lists)

module ProgramSettings =
    let version = "1.0.0.0"
    let debugMode = ref false

图片 2

列表是内置于F#的简单集合类型。可以是一个空表(empty list),使用方括号表示([])。我们可以将一个值与列表连接,此时要使用“::”操作符,注意要将值作为第一个操作数:

module MyProgram =
    do printfn "Version %s" ProgramSettings.version
    open ProgramSettings
    debugMode := true

这些并没有太多让人兴奋的。我们来逐行的分析下代码,看看到底有什么不同之处,在此之前先介绍下VFSI。

let emptyList = []
let oneItem = "one" :: []
let twoItem = "two" :: oneItem

4. 元组(Tuples)

Visual Studio中的F#交互(Interactive)

在VS中可以看到oneItem的类型为string list。如果列表包含多个项,用上面的方法显得麻烦了,我们可以使用下面的语法:

元组(tuple,发音为‘two-pull’)表示值的有序集合,而这些值可看作一个整体。按传统的方式,如果您要传递一组相关的值,需要创建结构(struct)或类(class),或者还需要“out”参数。使用元组我们可以将相关的值组织起来,同时并不需要引入新的类型。

F#交互控制台(F# Interactive Console, FSI)采用的是“REPL loop”模式,即Read-Evaluate-Print-Loop。也就是输入一段代码,编译并执行,然后输出结果。通过它您可以快速地开发和测试程序。要在VS中启用FSI,打开Add-in Manager窗口。

let shortHand = ["hello "; "world!"]

要定义一个元组,只要将一组值用逗号分隔,并用圆括号把它们括起来即可。

图片 3

另外我们还可使用“@”操作符来连接两个列表:

> let tuple = (1, false, "text");;
val tuple : int * bool * string

选中“F# Inactive for Visual Studio”。然后,选中程序的前两行代码:

let concatenateLists = ["one, "; "two, "] @ ["three, "; "four"]

> let getNumberInfo (x : int) = (x, x.ToString(), x * x);;
val getNumberInfo : int -> int * string * int

图片 4

F#要求列表中的元素类型必须是相同的,如果你确实需要列表包含不同类型的元素,那只好创建一个obj(即System.Object)类型的列表了:

> getNumberInfo 42;;
val it : int * string * int = (42, "42", 1764)

接着按下Alt+Enter(实际上,如果FSI还么有打开,需要按两次Alt+Enter)。这时会看到出现了一个工具窗口:

let objList = [box 1; box 2.0; box "three"]

函数甚至可以接受元组为参数:

图片 5

其中第三个box是可选的,这个让我想起了C#中的装箱。

> let printBlogInfo (owner, title, url) = printfn "%s's blog [%s] is online at '%s'" owner title url;; 
val printBlogInfo : string * string * string -> unit 

我们刚才做的事情是将代码片段直接发送给FSI会话,FSI将结果输出。结果是函数“square”的定义,它接受int类型参数,返回类型也是int。

F#中的列表是不可修改的,一旦创建就不能修改了。作用于列表的函数和操作符也不能修改列表,而是创建了列表的一个副本。这个特性很像C#中的string类型。看下面的例子:

> let myBlog = ("Chris", "Completely Unique View", "");;
val myBlog : string * string * string

接下来在FSI窗口输入“List.map square [1 .. 2 .. 10];;”。“;;”是告诉FSI停止阅读程序,立即进行求值。

#light

> printBlogInfo myBlog;;
Chris's blog [Completely Unique View] is online at ''
val it : unit = ()

> List.map square [1 .. 2 .. 10];;
val it : int list = [1; 9; 25; 49; 81]

let printList list =
    List.iter print_string list
    print_newline()

5. 函数柯里化(Function Currying)

现在我们可以方便地通过FSI来学习F#了,马上来看看我们的程序究竟做了什么吧。不过仍建议您在VS源代码编辑器中输入代码,使用“Select(原文是Highlight,感觉Select更贴切)

let threeItems = ["one "; "two "; "three "]
let reversedList = List.rev threeItems

F#提供的一个新奇的特性是可以只接受参数的一个子集,而接受部分参数的结果则是一个新的函数。这就是所谓的“函数柯里化”。比如,假设有一个函数接受3个整数,返回它们的和。我们可以只传入第一个参数,假设值为10,这样我们就可以说将原来的函数柯里化了,而它会返回一个新的函数——新函数接受两个整数,返回它们与10的和。

  • Alt + Enter”将代码片段发送至FSI。

printList threeItems
printList reversedList

> let addThree x y z = x + y + z;;
val addThree : int -> int -> int -> int

语言基础

 

> let addTwo x y = addThree 10 x y;;
val addTwo : int -> int -> int

#light(OCaml兼容)

上面的iter方法接受两个参数,第一个是函数,第二个是列表,其作用是将函数依次应用于列表的每个元素,有点像C#中的foreach循环。而rev方法则返回列表的逆序列表。

> addTwo 1 1;;
val it : int = 12

F#源自OCaml,具有交互编译OCaml的能力,也就是可以不经修改即可编译简单的OCaml程序。这种能力也带来了令人讨厌的语法。#light(发音为hash-light)是一个编译器指令,可以简化F#的语法。

打印结果为:
one tow three
three two one

6. Union类型(Union Types,Discriminated Unions)

强烈建议您保持使用#light,您会发现,在大多数F#代码片段中要么会声明它,要么是假定已经声明了它。

上述两个方法都没有改变原来的列表。要了解关于F#列表的更多信息,建议阅读这篇文章:Mastering F# Lists。

考虑下面的枚举值:

let square x = x * x(类型推演)

列表推导(List Comprehensions)

enum CardSuit { Spade = 1, Club = 2, Heart = 3, Diamond = 4};

 

列表推导的概念源于数学,它使得创建和转换集合的操作变得简单。在F#中可以使用这样的推导语法直接创建列表,序列(sequence)和数组(序列和数组将在后面介绍)。要了解这个概念的更多内容可以查看:List_comprehension。

理论上,一个card实例只有一种可能的取值,但由于enum本质上只是整数,您不能确定它的值是否是有效的,在C#中,你可以这么写:

这行代码定义了一个函数:square,它会求得数字x的平方。考虑一下C#中等价的代码:

最简单的情况是指定列表的范围,如:

CardSuit invalid1 = (CardSuit) 9000;
CardSuit invalid2 = CardSuit.Club | CardSuit.Diamond;

public static int square(int x)
{
    return x * x;
}

let numericList = [0 .. 9]
let charList = ['A' .. 'Z']

另外,考虑下面的情形。如果您需要扩展一个enum:

在C#中,您需要制定参数和返回值的类型信息,而F#则帮您搞定了。这种行为称为类型推演(Type Inference)

这两个列表的类型分别是int list和char list,范围分别是从0到9和从’A’到’Z’。
更复杂的情况是指定一个步长:

enum Title { Mr, Mrs }

从函数的签名,F#可以知道“square”函数接受一个参数“x”,并且函数返回“x * x”(在函数体内的最后一次求值将作为返回值,因此无须return关键字)。因为很多基元类型都支持*操作,比如byte,uint64,double等,F#默认会使用int类型,有符号的32位整数。

let multipleOfThree = [0 .. 3 .. 30]
let revNumericList = [9.. -1 .. 0]

Title枚举可以工作地很好,但一段时间后,如果需要添加一个“Ms”值,那么每一个switch语句都面临一个潜在的bug。当然您可以尝试修复所有的代码,却难免会发生遗漏。

现在考虑下面的代码,它为其中的一个参数提供了“类型注解(type annotation)”,告诉编译器期望的类型。因为x标为“string”,“+”操作只定义在两个string间,因此y也必须为string类型,返回值是两个字符串拼接的结果。

第一个列表的值是0到30间所有3的倍数,第二个列表的元素则包含了从9递减至0。
我们还可以通过对一个列表进行循环操作得到另一个列表。例如:

枚举可以很好地表达某些概念,但是却无法提供足够的编译器检查。F#中的Union类型可设定为一组有限的值:数据标签(data tag)。例如,考虑一个表示微软员工的Union:

> let concat (x : string) y = x + y;;
val concat : string -> string -> string

let squares = [for x in 1 .. 10 -> x * x]

type MicrosoftEmployee =
    | BillGates
    | SteveBalmer
    | Worker of string
    | Lead of string * MicrosoftEmployee list

> concat "Hello, " "World!";;
val it : string = "Hello, World!"

通过for进行循环,squares列表的元素是1到10间的整数的平方。
此外还可以为循环添加when子句对元素进行过滤,只有when子句的值为true时才对其进行运算:

如果有一个MicrosoftEmployee类型的实例,您就知道它必定是{BillGates,SteveBalmer,Worker,Lead}之一。另外,如果它是Worker,您可以知道有一个字符串与之关联,也许是他的名字。我们可以轻松地创建Union类型,而后使用模式匹配(下一小节)来匹配它们的值。

后面我们将讨论类型推演的更多高级主题,现在您只要享受F#编译器的智能带来的方便就好了。 

let evens = [for x in 1 .. 10 when x % 2 = 0 -> x]

let myBoss = Lead("Yasir", [Worker("Chris"); Worker("Matteo"); Worker("Santosh")])

 

evens的元素为[2; 4; 6; 8; 10]。

let printGreeting (emp : MicrosoftEmployee) =
    match emp with
    | BillGates   -> printfn "Hello, Bill"
    | SteveBalmer -> printfn "Hello, Steve"
    | Worker(name) | Lead(name, _)
                  -> printfn "Hello, %s" name

let numbers = [1 .. 10](F# lists)

控制流程(Control Flow)

现在假设需要扩展Union类型:

这行代码声明了一个列表(list),其元素是从1至10。如果您用的是[|1 .. 10|],F#会创建一个.NET的整型数组(array)。而在F#中,列表是一个不可变的链表(linked list),这也是函数式编程的基础。试着将这些代码输入到FSI中(记住添加“;;”):

F#拥有强的控制流程概念,这与很多纯函数式编程语言不同,在这些语言中表达式可以以任何顺序进行求值。看下面的例子:

type MicrosoftEmployee =
    | BillGates
    | SteveBalmer
    | Worker of string
    | Lead   of string * MicrosoftEmployee list
    | ChrisSmith

// Define a list 
let vowels = ['e'; 'i'; 'o'; 'u'] 

let absoluteValue x =
    if x < 0 then
        -x
    elif x = 0 then
        0
    else
        x

我们会看到一些编译器警告信息:

// Attach item to front (cons)
let cons = 'a' :: vowels 

if, elif, then, else组成的结构我们应当很熟悉,在F#中该结构是一个表达式,也就是说它需要返回一个值。而且每个分支返回的值应当具有相同的类型,否则就会有编译错误。如果确实要返回多个类型的值,在值前加box关键字,就像前面创建列表时那样,这样表达式的返回类型为obj。

图片 6

// Concat two lists
let sometimes = vowels @ ['y']

类型与类型推导(Types and Type Inference)

编译器检测到您没有匹配Union的每一个数据标签,发出了警告。像这样的检查会避免很多bug,要了解

我将在本系列的第二篇中更深入地介绍列表。

F#是一种强类型的语言,传给函数的值必须是指定的类型。如果函数接受string类型的参数,就不能传给它int类型的值。一种语言处理其中值的类型的方式称为语言的类型系统。F#的类型系统与一般语言不同,包括函数在内,所有的值都具有自己的类型。

更多的关于Union类型的信息,看这篇文章。

let squares = List.map square numbers

通常情况下,我们不需要显式地声明类型,编译器会尝试从值的文字值或调用的函数返回类型来判断其类型,这个过程称为类型推导。可在编译时使用-i开关来显示所有的推导类型,在VS中我们则可以使用工具提示来查看标识符的类型。先看下面值的类型推导情况:

7. 模式匹配(Pattern Matching)

现在我们有了一个整型列表(numbers)和一个函数(square),我们希望创建一个新的列表,它的每一项是对numbers的每一项进行square运算后的结果。

let strValue = "String Value"
let intValue = 12

模式匹配看起来像是增强版的switch语句,允许您完成分支型控制流程。除了跟常数值进行比较外,还可以捕获新的值。比如在前面的例子中,我们在匹配Union数据标签时绑定了标识符“name”。

幸运的是,List.map可以做到。考虑下面的例子:

在fsi中可看到它们的信息是:

let printGreeting (emp : MicrosoftEmployee) =
    match emp with
    | BillGates   -> printfn "Hello, Bill"
    | SteveBalmer -> printfn "Hello, Steve"
    | Worker(name) | Lead(name, _)
                  -> printfn "Hello, %s" name

> List.map (fun x -> x % 2 = 0) [1 .. 10];;
val it : bool list
= [false; true; false; true; false; true; false; true; false; true]

val strValue : string
val intValue : int

还可以对数据的“结构”进行匹配,比如对列表(list)进行匹配。(还记得吗,x :: y表示x为列表的一个元素,y是x之后的元素,而[]则是空列表。)

代码(fun x -> x % 2 = 0)定义了一个匿名函数,称为lamdba表达式,接受一个参数x,返回值为表达式“x % 2 = 0”的结果,也就是判断x是否为偶数。

可以理解,编译器跟据赋给标识符的文字值来推导其类型。再看看下面函数的情况:

let listLength aList =
    match aList with
    | [] -> 0
    | a :: [] -> 1
    | a :: b :: [] -> 2
    | a :: b :: c :: [] -> 3
    | _ -> failwith "List is too big!"

注意我们刚才做的——将一个函数作为参数传递给另一个函数。在C#中这个并不容易。但在F#可以很清楚地表达出来,而且代码很简洁。将函数像值一样传递被称为“一等函数(first order functions)”,也是函数式编程的基础。

let makeMessage x = (string_of_bool x) + " is a boolean value"
let half x = x / 2

在这个匹配的最后,我们使用了通配符“_”(下划线),它匹配任意值。如果aList变量包含多于三个的元素,最后的模式子句将执行,并抛出一个异常。模式匹配还可以我们执行任意表达式来确定模式是否匹配(如果表达式的值为false,则不匹配)。

printfn "N^2 = %A" squares

在fsi中可看到它们的信息是:

let isOdd x =
    match x with
    | _ when x % 2 = 0 -> false
    | _ when x % 2 = 1 -> true

printf是打印文本到控制台窗口的一种简单而又类型安全的方式。要更好地了解printf,考虑下面的例子,它打印一个整数、浮点数和字符串。

val makeMessage : bool -> string
val half : int -> int

我们甚至可以使用动态类型测试进行匹配:

> printfn "%d * %f = %s" 5 0.75 ((5.0 * 0.75).ToString());;
5 * 0.750000 = 3.75
val it : unit = ()

有意思的是,函数名前面也有个val,这表明函数也是值,后面的bool -> string是什么意思呢?它表明函数接受bool类型参数,返回string类型的值,注意x作为string_of_bool的参数,所以x必须为bool类型,返回值是两个字符串相加的值,故返回值也是string类型。对于half函数,单从定义不能确定x类型,此时编译器采用默认的类型int。再看看稍微复杂点的情况:

let getType (x : obj) =
    match x with
    | :? string -> "x is a string"
    | :? int -> "x is a int"
    | :? System.Exception -> "x is an exception"
    | :? _ -> "invalid type"

%d,%f,%s分别是int、float、string的占位符。%A则可用于打印任何值。

let div1 x y = x / y
let div2 (x, y) = x / y

8. 记录类型(Records)

Console.ReadKey(true) (.NET互操作)

这两个函数的信息是:

在声明包含若干个公有属性的类型时,记录类型是一种轻量级的方式。它的一个优势是,借助于类型推演系统,编译器可以通过值的声明得出适当的记录类型。

我们程序的最后一行只是简单地调用了System.Console.ReadKey方法,这样可以让程序在关闭其暂停。因为F#建立在.NET的基础上,您可以在F#中调用任何.NET类库——从正则表达式到WinForms。代码“open System”用于打开命名空间,类似于C#中的using。

val div1 : int -> int -> int
val div2 : int * int -> int

type Address = {Name : string; Address : string; Zip : int}

现在我们已经有了F#的基础知识,可以继续学习更有趣的基础类型和F#概念了,希望您关注第二篇文章!

div1函数可接受部分参数(可柯里化),而div2则必须同时传入两个int类型的值。考虑下面的函数:

let whiteHouse = {Name = "The White House"; Address = "1600 Pennsylvania Avenue";
        Zip = 20500} 

原文链接:。

let doNothing x = x

在上面的例子中,首先定义了“Address”类型,那么在声明它的实例时,无须显式地使用类型注解,编译器可根据字段(属性)的名称自行得出类型的信息。所以whiteHouse的类型为Address。

来源:

其信息为:

9. Forward Pipe Operator(|>)

  • F# 20分钟快速上手-一
  • F# 20分钟快速上手-二

val doNothing : 'a -> 'a

|>操作符只是简单地定义为:


a’ -> a’表示函数接受任意类型,并返回与其相同类型的值。以’打头的类型表示可变类型(variable type),编译器虽然不能确定类型的参数,却能确定返回值类型必须与参数类型相同,类型系统的这种特性称为类型参数化,通过它编译器也能发现更多的类型错误。可变类型或类型参数化的概念,类似于.NET 2.0的泛型,如果F#基于支持泛型的CLI,那么它会充分利用泛型的优势。另外,F#的创建者Don Syme,正是CLR中泛型的设计者和实现者。

let (|>) x f = f x

F#的类型推导固然强大,但它显然不能揣测出开发人员所有的心思来,如果有特殊需求该怎么办呢?看下面的例子:

其类型前面信息为:

let doNothingToFloat (x : float32) = x

'a -> ('a -> 'b) -> 'b

float32即System.Single,这里我们手动指定了x的类型,这个有时称为类型标注(type annotation)。如果要在F#中使用其它.NET语言编写的类库,或者与非托管的类库进行互操作,它会很有用。

可以这么来理解:x的类型为'a,函数f接受'a类型的参数,返回类型为'b,操作符的结果就是将x传递给f后所求得的值。

小结

还是来看个例子吧:

接上一篇,本文继续介绍F#中的函数式编程范式,主要包含了操作符、列表、列表推导、类型推导、类型标注等概念。类型推导又称隐式类型,通常是——但不限于——函数式编程语言的特性,比如C# 3.0和VB.NET 9.0都提供了一定的支持,它使很多编程任务变得更为简单。

// Take a number, square it, then convert it to a string, then reverse that string

参考:
《Foundations of F#》 by Robert Pickering
《F# Specs》

let square x         = x * x
let toStr (x : int)  = x.ToString()
let rev   (x : string) = new String(Array.rev (x.ToCharArray()))

作者:Anders Cui
出处:http://anderslly.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

// 32 -> 1024 -> "1024" -> "4201"
let result = rev (toStr (square 32))

  • F# 探险之旅-选择不同的开发方式
  • F# 探险之旅-函数式编程(上)
  • F# 探险之旅-函数式编程(中)
  • F# 探险之旅-函数式编程(下)
  • F# 探险之旅-命令式编程(上)
  • F# 探险之旅-命令式编程(下)
  • F# 探险之旅-面向对象编程(上)
  • F# 探险之旅-面向对象编程(中)
  • F# 探险之旅-面向对象编程(下)
  • F# 探险之旅-透过 F# 理解函数式编程(上)
  • F# 探险之旅-透过 F# 理解函数式编程(中)
  • F# 探险之旅-F# 代码的组织
  • F# 探险之旅-在 F# 中进行单元测试

上面的代码是很直白的,但语法看起来却不太好。我们所做的就是将一个运算的结果传给下一个运算。我们可以通过引入几个变量来改写代码为:


let step1 = square 32
let step2 = toStr step1
let step3 = rev step2
let result = step3

但是我们需要维护这几个临时变量。|>操作符接受一个值,将其“转交”给一个函数。这会大大地简化F#代码:

let result = 32 |> square |> toStr |> rev

10. 序列(Sequence,System.Collections.Generic.IEnumerator<_>)

序列(在F#中为seq)是 System.Collections.Generic.IEnumerator的别名,但它在F#中有另外的作用。不像列表和数组,序列可包含无穷个值。只有当前的值保存在内存中,一旦序列计算了下个值,当前的值就会被忘记(丢弃)。例如,下面的代码生成了一个包含所有整数的序列。

let allIntegers = Seq.init_infinite (fun i -> i)

11. 集合(Collections:Seq,List,Array)

在F#中,如果您想表示一个值的集合,至少有三个好的选择——数组、列表和序列,它们都有各自的优点。而且每种类型都有一系列的模块内置于F#库中。您可以使用VS的智能感知来探究这些方法,这里我们来看看最常用的那些:

iter。“iter”函数遍历集合的每一项。这与“foreach”循环是一致的。下面的代码打印列表的每一项:

List.iter (fun i -> printfn "Has element %d" i) [1 .. 10]

map。像我在上篇文章中所说的,map函数基于一个指定的函数对集合的值进行转换。下面的例子将数组的整数值转换为它们的字符串表示:

Array.map (fun (i : int) -> i.ToString()) [| 1 .. 10 |]

fold。“fold”函数接受一个集合,并将集合的值折叠为单个的值。像iter和map一样,它接受一个函数,将其应用于集合的每个元素,但它还接受另一个“accumulator”参数。fold函数基于上一次运算不断地累积accumulator参数的值。看下面的例子:

Seq.fold (fun acc i -> i + acc) 10 { 1 .. 10 }

该代码的功能是:以10为基数(acculator),累加序列中的每一项。

只有序列有fold方法,列表和数组则有fold_left和fold_right方法。它们的不同之处在于计算顺序的不同。

12. 可选值(Option Values)

基于函数式编程的特点,在F#中很难见到null值。但有些情况下,null值比未初始化变量更有意义。有时可选值则表示值未提供(可选值就像C#中的nullable类型)。

F#中的“可选类型(option type)”有两种状态:“Some”和“None”。在下面的记录类型Person中,中间的字段可能有值,也可能没有值。

type Person = { First : string; MI : string option; Last : string }
let billg    = {First = "Bill";  MI = Some("H"); Last = "Gates" }
let chrsmith = {First = "Chris"; MI = None;      Last = "Smith" }

13. 延迟求值(惰性值,Lazy Values,Microsoft.FSharp.Core.Lazy<_>)

延迟初始化表示一些值,它们在需要时才进行计算。F#拥有延迟求值特性。看下面的例子,“x”是一个整数,当对其进行求值时会打印“Computed”。

> let x = lazy (printfn "Computed."; 42);;
val x : Lazy<int>

> let listOfX = [x; x; x];;
val listOfX : Lazy<int> list

> x.Force();;
Computed.
val it : int = 42

可以看到,我们在调用“Force”方法时,对x进行求值,返回的值是42。您可以使用延迟初始化来避免不必要的计算。另外在构造递归值时,也很有用。例如,考虑一个Union值,它用来表示循环列表:

type InfiniteList =
| ListNode of int * InfiniteList

let rec circularList = ListNode(1, circularList)

“circularList”拥有对自身的引用(表示一个无限循环)。不使用延迟初始化的话,声明这样类型的值是不可能的。

现在,您应该对F#的基础有了足够的了解了,下一步,在系列文章的第三部分中,我们将学习一些高级主题——一些F#能做而其他的.NET语言不能做的事情,敬请期待!

原文链接:F# in 20 Minutes – Part II。

来源:

  • F# 20分钟快速上手-一
  • F# 20分钟快速上手-二

本文由10bet手机官网发布于微服架构,转载请注明出处:20分钟快速上手,函数式编程

上一篇:函数式编程 下一篇:选择不同的开发方式
猜你喜欢
热门排行
精彩图文