F#探险之旅(二):函数式编程(中)

简介:
操作符(Operator) 

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

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

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

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

我们可以定义自己的操作符,也可以重定义已有的任何操作符(不建议这样做)。看看下面这种不好的做法:
let  (+) a b = a – b
print_int (
1  +  2 )

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

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

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

结果为30。

列表(Lists)

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

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

另外我们还可使用“@”操作符来连接两个列表:
let  concatenateLists = [ " one,  " " two,  " ] @ [ " three,  " " four " ]

F#要求列表中的元素类型必须是相同的,如果你确实需要列表包含不同类型的元素,那只好创建一个obj(即System.Object)类型的列表了:
let  objList = [box  1 ; box  2.0 ; box  " three " ]

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

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

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

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

printList threeItems
printList reversedList
复制代码

 

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

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

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

列表推导(List Comprehensions)  

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

最简单的情况是指定列表的范围,如:
let  numericList = [ 0  ..  9 ]
let  charList = [ ' A '  ..  ' Z ' ]

这两个列表的类型分别是int list和char list,范围分别是从0到9和从’A’到’Z’。
更复杂的情况是指定一个步长:
let  multipleOfThree = [ 0  ..  3  ..  30 ]
let  revNumericList = [ 9 .. - 1  ..  0 ]

第一个列表的值是0到30间所有3的倍数,第二个列表的元素则包含了从9递减至0。
我们还可以通过对一个列表进行循环操作得到另一个列表。例如:
let  squares = [ for  x  in   1  ..  10   ->  x * x]

通过for进行循环,squares列表的元素是1到10间的整数的平方。
此外还可以为循环添加when子句对元素进行过滤,只有when子句的值为true时才对其进行运算:
let  evens = [ for  x  in   1  ..  10   when  x %  2  =  0   ->  x]

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

控制流程(Control Flow)

F#拥有强的控制流程概念,这与很多纯函数式编程语言不同,在这些语言中表达式可以以任何顺序进行求值。看下面的例子:
复制代码
let  absoluteValue x =
    
if  x <  0   then
        -x
    elif x = 
0   then
        
0
    
else
        x
复制代码

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

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

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

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

在fsi中可看到它们的信息是:
val  strValue :  string
val  intValue :  int

可以理解,编译器跟据赋给标识符的文字值来推导其类型。再看看下面函数的情况:
let  makeMessage x = (string_of_bool x) +  "  is a boolean value "
let  half x = x /  2

在fsi中可看到它们的信息是:
val  makeMessage :  bool   ->   string
val  half :  int   ->   int

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

这两个函数的信息是:
val  div1 :  int   ->   int   ->   int
val  div2 :  int  *  int   ->   int

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

其信息为:
val  doNothing :  ' a ->  ' a

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

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

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

小结 

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

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


本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2008/08/31/fs-adventure-fp-part-two.html,如需转载请自行联系原作者。

目录
相关文章
|
28天前
|
算法 程序员
探索编程世界的初心和坚持
【9月更文挑战第6天】在代码的海洋中,每一个程序员都是一艘航行的船。从大学毕业时的迷茫,到大胆尝试新领域,再到不断学习和提升,我找到了人生的方向。正如乔布斯所说,“人生中的每一个点都会在未来某个时刻连接起来”。这篇文章将带你领略编程世界的奥秘,让你明白初心和坚持的重要性。
|
2月前
|
程序员 Python
探索代码之美:我的编程感悟之旅
【8月更文挑战第31天】编程,一门艺术与科学的结合体。本文将带你走进编程世界,分享个人在代码编写过程中的心得体会。从最初的迷茫到逐步掌握,再到深入理解,每一步都充满挑战与惊喜。让我们一起领略代码的魅力,感受编程带来的成就感与乐趣。
|
2月前
|
人工智能 数据挖掘 数据库
探索代码之美:我的编程之旅与技术感悟
【8月更文挑战第31天】在数字世界的海洋中,编程是那艘能带我们探索未知的船。我通过编程找到了自己的方向,从一个迷茫的大学毕业生成长为一名不断学习和提升的技术人员。就像甘地所说,“你必须成为你希望在世界上看到的改变。”我在代码中看到了创造和改变的力量,这篇文章将分享我的技术旅程和对编程之美的理解。
|
4月前
|
算法 开发者
代码之美:我的编程之旅与技术感悟
【6月更文挑战第23天】编程不仅是技术的实践,更是艺术的创造。本文将通过个人经历,探讨如何从初学者成长为一名有洞察力的开发者,并分享在编程旅途中的技术感悟。我们将一起探索编程的本质、学习过程中的挑战与乐趣,以及如何培养解决问题的能力,最终达到技术与创造力的融合。
|
5月前
|
设计模式 开发者
探索代码之美:我的编程艺术之旅
【5月更文挑战第19天】 在数字的海洋中,我是一位潜水者,每一次键盘的敲击都是对未知世界的探索。本文记录了我在编程实践中的一些感悟和经验,从最初的困惑到最后的豁然开朗,我逐渐理解了编程不仅仅是一种技能,更是一种艺术。我将分享如何通过不断学习和实践,将代码转化为优雅的解决方案,以及在这个过程中所经历的挑战和收获。
|
5月前
|
设计模式 算法 测试技术
探索代码之美:我的编程思考之旅
【5月更文挑战第8天】 在数字化的浪潮中,编程已成为一种艺术,一种用逻辑与创造力编织的语言。本文将分享我在编程实践中的一些技术感悟,从最初的困惑到逐渐的深入理解,再到最后的灵活应用,我经历了一段充满挑战与收获的旅程。文章不仅探讨了编程技巧的提升,还涉及了对软件设计原则的认识,以及如何通过不断学习来适应快速变化的技术环境。
|
5月前
|
程序员
代码与禅意:编程中的悟性之旅
【5月更文挑战第31天】在数字世界的繁花似锦中,我们常常忽略了编码背后蕴含的哲学。本文将探讨编程不仅仅是一门技术,更是一种艺术和内省的过程。从禅宗的角度出发,我们将一窥那些静谧的代码行间所折射出的深邃智慧,以及它如何影响程序员的思考方式和解决问题的策略。
|
5月前
|
编译器
泛型编程的启蒙之旅
泛型编程的启蒙之旅
32 0
下一篇
无影云桌面