-
函数的定义
函数的定义也是采用let语句,所不同的是函数名后面可以跟参数列表。语法如下:
let FunctionName Param1 Param2 = FunctionBody
和其他很多语言不同,F#中没有return关键字,每个函数的返回值就是函数中最后一个表达式的值。
函数的调用方式为函数名+空格+参数1+空格+参数2……,如:
> let add x y = x + y;;
val add : int -> int -> int
> add 1 2;;
Val it : 3
在FSI中的输出int -> int -> int表示函数接受两个int类型的参数,返回一个int类型的参数。
-
类型推理
F#是静态类型的, 但是在编写程序的时候并不一定要求指定函数的所有参数的类型,因为编译器会根据使用情况来自动指定。这就是类型推理(Type Inference)。但是类型推理并不是动态类型,虽然编程时未指定类型,但是所有参数都是有类型的,并且编译期会进行类型检查。这一点我到现在都还很不适应,总觉得写起来有些怪异的感觉,但是可以想象这是因为F#的函数式设计的原因。
例如,对上面那个函数传递float类型的参数是会报错的:
> add 1.0 2.0;;
add 1.0 2.0;;
----^^^^
stdin(3,5): error FS0001: This expression has type float but is here used with type int.
这是由于函数中没有更多信息能够表明参数的类型,而+操作符能用于几种类型,所以编译器只是简单的将它默认为int类型的加法。但是如果代码片断中还有其他语句能够提供一些关于类型的信息给编译器,那么它就能识别出来编程者正确的意图:
> // Type inference in action
let mult x y = x * y
let result = mult 4.0 5.5;;
val mult : float -> float -> float
val result : float
双分号";;"是FSI的代码输入终结符。上面这个例子在定义了mult函数之后紧接着使用它来计算两个float类型数的值,因此编译器推断出正确的类型应该是float。这些特性到目前为止我觉得有些自作聪明了,编译器引入这种所谓"智能"的特性常常在实际应用中是帮倒忙的,例如恶心的ActionScript就是如此。希望随着后面的学习能够改变我的看法吧,呵呵。
除了自动的类型推理之外,也可以自己指定参数的类型。方法是在参数名后加上一个冒号和类型声明,并且将整个参数声明用园括号括起来。例如:
> let add (x : float) y = x + y;;
val add : float -> float -> float
可以看到编译器将y的类型也推断成了float,这是因为只有重载版本的+才能接受两个不同类型的值。
总的来说,传统的C/C++、以及动态类型的PHP、LUA等语言的程序员肯定会非常不适应F#这种"半动态类型"的设计。为什么做成这样暂时我还没有清晰的认识。
-
普通函数(Generic Functions)
不知道叫成"普通函数"是否合适,呵呵。实际上想表达的并不是"普通"这个意思。
有时候在代码中没有任何信息能够让编译器来推断参数的类型,这时编译器就把它作为"普通"参数。"普通"参数是没有类型的,它能够接受任何类型的数据,如整数、字符串或者浮点数。在FSI中的输出类似这样:
> let ident x = x;;
val ident : 'a -> 'a
> ident "a string";;
val it : string = "a string"
> ident 1234L;;
val it : int64 = 1234L
"普通"参数的类型名字可以是以单引号开头后跟任意合法标识符的形式。但是一般来说都是用"'a"。这样就可以明确的声明一个参数是普通类型的。如:
> let ident2 (x : 'a) = x;;
val ident2 : 'a -> 'a
> let ident (x: 'abc) = x;;
val ident : 'abc -> 'abc
ident和ident2函数实质上是一样的。
-
作用域
F#中定义的值的作用域和其他语言类似。默认来说,值的作用域是在模块内部,即在它的定义之后的任何位置都可以使用。函数中定义的值的作用域在函数内部,函数中可以使用函数内部定义的值、以及在函数外部并且位于函数之前定义的值。
F#中允许嵌套函数,在内层函数中可以使用外层定义的所有值,但是在外层不能访问内层函数定义的值。当内层函数中定义的值名字和位于外层的值名字相同时,所访问的始终是内层定义的那个值。
F#的作用域是很常规的设计,没有什么特殊的东西需要注意的。
-
控制流
F#中可以使用if … then …的形式来产生程序分支。这条语句和其他语言中的类似语句作用一样,没什么好说的。需要注意的是F#采用代码对齐的方式来管理代码块,所以使用if语句时一定要注意代码的对齐。
这种方式其实让代码看起来变得很不舒服,没有明显的代码块分隔符的时候,复杂语句很容易让人头晕眼花,比如下面这个:
let isWeekend day =
if day = "Sunday" then
true
else
if day = "Saturday" then
true
else
false
在多个if语句嵌套的时候,可以使用下面这种形式来简化代码:
let isWeekday day =
if day = "Monday" then true
elif day = "Tuesday" then true
elif day = "Wednesday" then true
elif day = "Thursday" then true
elif day = "Friday" then true
else false
if语句的结果也是一个值,因此在if中的所有分支都必须返回相同类型的值。
> // ERROR: Different types for if expression clauses
let x =
if 1 > 2 then
42
else
"a string";;
else "a string";;
------------------^^^^^^^^^^^
stdin(118,19): error FS0001: This expression has type string but is here used with type
int. stopped due to error
当if语句没有else的时候,返回的类型为unit。在F#中unit是一个特殊类型,用来表示"无值"类似于C/C++系列语言中的void。