3、函数的生命周期
在函数内定义的每个值都有一个特定的范围,也即是它使用的生命周期。默认情况下,定义后的值作用范围在于它所在的模块内,意味着值一旦申明后就可以在任意地方使用。然后,如果指定义在函数内,则它的作用范围仅限于它的函数内。因此,一个函数可以任何函数外面定义的值,但函数外面的其它地方则不能引用一个函数内部定义的值。下面,我们将定义一个在模块范围内的值moduleValue并使用在一个函数functionA中以及定义一个作用于函数内部的值functionValue,同时在函数外部调用functionValue将引发一个未定义异常:
> // Scope
let moduleValue = 10
let functionA x =
x + moduleValue;;
val moduleValue : int = 10
val functionA : int -> int
> // Error case
let functionB x =
let functionValue = 20
x + functionValue
// 'functionValue' not in scope
functionValue;;
functionValue;;
^^^^^^^^^^^^^
error FS0039: The value or constructor 'functionValue' is not defined.
值的作用范围看起来并不是很重要的一个细节,重要的是在F#,因为它允许嵌套函数,在F#中,你可以在一个函数的块内申请一个新的函数。嵌套函数可以获取更高一层作用范围内的任何值,比如它的父函数、所在的模块以及在嵌套函数内新定义的值。下面的代码显示嵌套在实际中的应用, 请注意函数g能够利用其父函数f's参数fParam:
> let moduleValue = 1
- let f fParam =
- let g gParam = fParam + gParam + moduleValue
- let a = g 1
- let b = g 2
- a+b;;
val moduleValue : int = 1
val f : int -> int
在一个函数中定义一个函数,它看起了只能导致混乱,但是在限制函数的作用范围上有非常有用的,同时从职能上讲,也说明特定的功能仅仅利用在需要的地方,它有助于防止模块污染。如果在一个嵌套函数内想申请一个x值,但是在更高一层范围中已经存在了x值,会是什么样呢?在F#中,两个相同名字的值并不会引起编译错误,相反,它将会导致覆盖前面的x值。当发生这种情况,这两个值都存在于内存中,除非是没有办法访问先前声明价值,否则最后一定申明的将会被使用,比如下面的函数:
> let bytesToGB x =
- let x = x / 1024I // B to KB
- let x = x / 1024I // KB to MB
- let x = x / 1024I // MB to GB
- x;;
val bytesToGB : System.Numerics.BigInteger -> System.Numerics.BigInteger
> let hardDriveSize = bytesToGB 268435456000I;;
val hardDriveSize : System.Numerics.BigInteger = 250
在上面的例子中,在每次let绑定到一个x值后,前面的值将会被后面的所代替,表明上看起来x值的已经变化了,但实际上它创建了一个新的x值,只是名字相同而已。下面的例子显示了如何得到编译后代码:
> let bytesToGB x =
- let x_2 = x / 1024I // B to KB
- let x_3 = x_2 / 1024I // KB to MB
- let x_4 = x_3 / 1024I // MB to GB
- x_4;;
val bytesToGB : System.Numerics.BigInteger -> System.Numerics.BigInteger
4、控制结构
在一个函数,你可以使用if关键字来控制流程分支,if语句的条件表达式必须是bool类型,并且如果为true,则执行语句。下面的代码将在控制台中打印一个消息:
> let printGreeting shouldGreet greeting =
- if shouldGreet then
- printfn "%s" greeting;;
val printGreeting : bool -> string -> unit
> printGreeting true "Hello!";;
Hello!
val it : unit = ()
> printGreeting false "Hello again!";;
val it : unit = ()
可以使用if关键字控制更复杂的分支流程,if表达式跟C#的是一样的: 如果条件表达式的值为真,那么第一个代码块执行,否则,代码的第二块执行,然而,有些事情使得F#与其他语言不同的是,if表达式返回一个值,在下面的代码中,在if表达式中值result绑定到一个结果,所以如果x % 2 = 0, result的值是"Yes it is"; 否则就是"No itis not":
> let isEven x =
- let result =
- if x % 2 = 0 then
- "Yes it is"
- else
- "No it is not"
- result;;
val isEven : int -> string
> isEven 5;;
val it : string = "No it is not"
> isEven 4;;
val it : string = "Yes it is"
if表达式可以使用嵌套来实现更复杂的流程分支, 但很快变得难以维持,比如:
> let isWeekend day =
- if day = "Sunday" then
- true
- else
- if day = "Saturday" then
- true
- else
- false;;
val isWeekend : string -> bool
F#提供了elif关键来代替if表达式嵌套的语法,利用它,可以将多个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
- ;;
val isWeekday : string -> bool
因为if语句最后都要返回一个值,所以在所有的if分支中,返回值的类型都必须是相同的,否则会出错:
> let x =
- if 1 > 2 then
- 42
- else
- "a string";;
"a string";;
--------^^^^^^^^^^
stdin(52,9): error FS0001: This expression was expected to have type
int
but here has type
string
但是如果你仅仅只有一个if并且没有elif,该怎么做呢?这种情况下必须使用unit,unit在F#中是一种特殊的类型,意思就是没有返回值(跟C#中的void是一样的),下面的代码演示了返回unit的类型:
> let describeNumber x =
- if x % 2 = 0 then
- printfn "x is a multiple of 2"
- if x % 3 = 0 then
- printfn "x is a multiple of 3"
- if x % 5 = 0 then
- printfn "x is a multiple of 5"
- ();;
val describeNumber : int -> unit