F# 20分钟快速上手(二)

 这是系列文章的第二篇,读完本文后,您应当能够具备相当的阅读F#代码的能力。

  1.不可变性(Immutability)

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

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

> letmutable x = "the original value.";;
valmutable x : string
> printfn "x's value is '%s'" x;;
x's value is 'the original value.'
val it : unit = ()
> 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 = ()

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

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

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

> let refCell = ref 42;;
val refCell : int ref
> refCell := -1;;
val it : unit = ()
> !refCell;;
val it : int = –1

  3. 模块(Modules)

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

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

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

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

  4. 元组(Tuples)

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

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

>lettuple=(1,false,"text");;
valtuple:int*bool*string
>letgetNumberInfo(x:int)=(x,x.ToString(),x*x);;
valgetNumberInfo:int->int*string*int
>getNumberInfo42;;
valit:int*string*int=(42,"42",1764)

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

>letprintBlogInfo(owner,title,url)=printfn"%s'sblog[%s]isonlineat'%s'"ownertitleurl;;
valprintBlogInfo:string*string*string->unit
>letmyBlog=("Chris","CompletelyUniqueView","http://blogs.msdn.com/chrsmith");;
valmyBlog:string*string*string
>printBlogInfomyBlog;;
Chris'sblog[CompletelyUniqueView]isonlineat'http://blogs.msdn.com/chrsmith'
valit:unit=()

  5. 函数柯里化(Function Currying)

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

>letaddThreexyz=x+y+z;;
valaddThree:int->int->int->int
>letaddTwoxy=addThree10xy;;
valaddTwo:int->int->int
>addTwo11;;
valit:int=12

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

  考虑下面的枚举值:

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

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

CardSuitinvalid1=(CardSuit)9000;
CardSuitinvalid2=CardSuit.Club|CardSuit.Diamond;

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

enumTitle{Mr,Mrs}

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

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

typeMicrosoftEmployee=
  |BillGates
  |SteveBalmer
  |Workerofstring
  |Leadofstring*MicrosoftEmployeelist

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

letmyBoss=Lead("Yasir",[Worker("Chris");Worker("Matteo");Worker("Santosh")])
letprintGreeting(emp:MicrosoftEmployee)=
  matchempwith
  |BillGates ->printfn"Hello,Bill"
  |SteveBalmer->printfn"Hello,Steve"
  |Worker(name)|Lead(name,_)
         ->printfn"Hello,%s"name

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

typeMicrosoftEmployee=
  |BillGates
  |SteveBalmer
  |Workerofstring
  |Lead ofstring*MicrosoftEmployeelist
  |ChrisSmith

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

F# 20分钟快速上手(二)

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

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

  7. 模式匹配(Pattern Matching)

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

letprintGreeting(emp:MicrosoftEmployee)=
  matchempwith
  |BillGates ->printfn"Hello,Bill"
  |SteveBalmer->printfn"Hello,Steve"
  |Worker(name)|Lead(name,_)
         ->printfn"Hello,%s"name

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

letlistLengthaList=
  matchaListwith
  |[]->0
  |a::[]->1
  |a::b::[]->2
  |a::b::c::[]->3
  |_->failwith"Lististoobig!"

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

letisOddx=
  matchxwith
  |_whenx%2=0->false
  |_whenx%2=1->true

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

letgetType(x:obj)=
  matchxwith
  |:?string->"xisastring"
  |:?int->"xisaint"
  |:?System.Exception->"xisanexception"
  |:?_->"invalidtype"

  8. 记录类型(Records)

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

typeAddress={Name:string;Address:string;Zip:int}
letwhiteHouse={Name="TheWhiteHouse";Address="1600PennsylvaniaAvenue";
    Zip=20500} 

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

  9. Forward Pipe Operator(|>)

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

let(|>)xf=fx

  其类型前面信息为:

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

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

  还是来看个例子吧:

//Takeanumber,squareit,thenconvertittoastring,thenreversethatstring
letsquarex    =x*x
lettoStr(x:int) =x.ToString()
letrev (x:string)=newString(Array.rev(x.ToCharArray()))
//32->1024->"1024"->"4201"
letresult=rev(toStr(square32))

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

letstep1=square32
letstep2=toStrstep1
letstep3=revstep2
letresult=step3

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

letresult=32|>square|>toStr|>rev

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

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

letallIntegers=Seq.init_infinite(funi->i)

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

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

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

List.iter(funi->printfn"Haselement%d"i)[1..10]

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

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

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

Seq.fold(funacci->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中,中间的字段可能有值,也可能没有值。

typePerson={First:string;MI:stringoption;Last:string}
letbillg  ={First="Bill"; MI=Some("H");Last="Gates"}
letchrsmith={First="Chris";MI=None;   Last="Smith"}

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

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

>letx=lazy(printfn"Computed.";42);;
valx:Lazy<int>
>letlistOfX=[x;x;x];;
vallistOfX:Lazy<int>list
>x.Force();;
Computed.
valit:int=42

 

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

typeInfiniteList=
|ListNodeofint*InfiniteList
letreccircularList=ListNode(1,circularList)

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值