在 F# 中使用函数
简单的函数定义如下所示:
let f x = x + 1
在上一示例中,函数名称为 f
,参数为 x
(具有类型 int
),函数体为 x + 1
,且返回值的类型为 int
。
F# 的一个决定性特征是,函数具有第一类值的地位。 在付出相同努力的情况下,你可以像对其他内置类型的值一样,对函数执行相同的操作。
可以为函数值命名。
可以将函数存储在数据结构(例如列表)中。
可以在函数调用中将函数作为参数传递。
可以通过函数调用返回函数。
为值命名
如果函数是第一类值,你必须能够为其命名,就像为整数、字符串和其他内置类型命名一样。 这在函数编程文献中称为将标识符绑定到值。 F# 使用 let
绑定将名称绑定到值:let <identifier> = <value>
。 以下代码展示了两个示例。
// Integer and string.
let num = 10
let str = "F#"
你可以轻松地为函数命名。 以下示例定义一个函数,该函数squareIt
通过将标识符squareIt
绑定到 lambda 表达式fun n -> n * n
。 函数 squareIt
有一个参数 n
,它返回该参数的平方值。
let squareIt = fun n -> n * n
F# 提供了下面这种更简洁的语法,以更少的输入来实现相同的结果。
let squareIt2 n = n * n
下面的示例大多使用第一种样式 let <function-name> = <lambda-expression>
,以强调函数声明与其他类型的值的声明之间的相似性。 但是,所有命名函数也可以用简洁的语法编写。 一些示例以两种方式编写。
将值存储在数据结构中
第一类值可以存储在数据结构中。 以下代码展示了将值存储在列表和元组中的示例。
// Lists.
// Storing integers and strings.
let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
let stringList = [ "one"; "two"; "three" ]
// You cannot mix types in a list. The following declaration causes a
// type-mismatch compiler error.
//let failedList = [ 5; "six" ]
// In F#, functions can be stored in a list, as long as the functions
// have the same signature.
// Function doubleIt has the same signature as squareIt, declared previously.
//let squareIt = fun n -> n * n
let doubleIt = fun n -> 2 * n
// Functions squareIt and doubleIt can be stored together in a list.
let funList = [ squareIt; doubleIt ]
// Function squareIt cannot be stored in a list together with a function
// that has a different signature, such as the following body mass
// index (BMI) calculator.
let BMICalculator = fun ht wt ->
(float wt / float (squareIt ht)) * 703.0
// The following expression causes a type-mismatch compiler error.
//let failedFunList = [ squareIt; BMICalculator ]
// Tuples.
// Integers and strings.
let integerTuple = ( 1, -7 )
let stringTuple = ( "one", "two", "three" )
// A tuple does not require its elements to be of the same type.
let mixedTuple = ( 1, "two", 3.3 )
// Similarly, function elements in tuples can have different signatures.
let funTuple = ( squareIt, BMICalculator )
// Functions can be mixed with integers, strings, and other types in
// a tuple. Identifier num was declared previously.
//let num = 10
let moreMixedTuple = ( num, "two", 3.3, squareIt )
为了验证存储在元组中的函数名称是否确实计算为函数,以下示例使用 fst
和 snd
运算符从元组 funAndArgTuple
中提取第一个和第二个元素。 元组中的第一个元素是 squareIt
,第二个元素是 num
。 在前面的示例中,标识符 num
绑定到整数 10,这是 squareIt
函数的有效参数。 第二个表达式将元组中的第一个元素应用到元组中的第二个元素:squareIt num
。
// You can pull a function out of a tuple and apply it. Both squareIt and num
// were defined previously.
let funAndArgTuple = (squareIt, num)
// The following expression applies squareIt to num, returns 100, and
// then displays 100.
System.Console.WriteLine((fst funAndArgTuple)(snd funAndArgTuple))
同样,正如标识符 num
和整数 10 可以互换使用一样,标识符 squareIt
和 Lambda 表达式 fun n -> n * n
也可以互换使用。
// Make a tuple of values instead of identifiers.
let funAndArgTuple2 = ((fun n -> n * n), 10)
// The following expression applies a squaring function to 10, returns
// 100, and then displays 100.
System.Console.WriteLine((fst funAndArgTuple2)(snd funAndArgTuple2))
将值作为参数传递
如果某个值在某种语言中具有第一类值的地位,则可以将其作为参数传递给函数。 例如,通常将整数和字符串作为参数传递。 以下代码展示了在 F# 中作为参数传递的整数和字符串。
// An integer is passed to squareIt. Both squareIt and num are defined in
// previous examples.
//let num = 10
//let squareIt = fun n -> n * n
System.Console.WriteLine(squareIt num)
// String.
// Function repeatString concatenates a string with itself.
let repeatString = fun s -> s + s
// A string is passed to repeatString. HelloHello is returned and displayed.
let greeting = "Hello"
System.Console.WriteLine(repeatString greeting)
如果函数具有第一类值的地位,你必须能够以相同的方式将其作为参数传递。 记住,这是高阶函数的第一个特征。
在以下示例中,函数 applyIt
有两个参数:op
和 arg
。 如果为 op
发送具有一个形参的函数,并将该函数的适当实参发送到 arg
,函数会返回将 op
应用于 arg
的结果。 以下示例使用函数参数和整数参数的名称以相同的方式发送这两个参数。
// Define the function, again using lambda expression syntax.
let applyIt = fun op arg -> op arg
// Send squareIt for the function, op, and num for the argument you want to
// apply squareIt to, arg. Both squareIt and num are defined in previous
// examples. The result returned and displayed is 100.
System.Console.WriteLine(applyIt squareIt num)
// The following expression shows the concise syntax for the previous function
// definition.
let applyIt2 op arg = op arg
// The following line also displays 100.
System.Console.WriteLine(applyIt2 squareIt num)
将函数作为参数发送给另一个函数的能力是函数编程语言中常见抽象的基础,例如映射或筛选操作。 例如,映射操作是一个高阶函数,它捕获函数共享的计算,这些函数遍历列表,对每个元素执行一些操作,然后返回结果列表。 你可能希望递增整数列表中的每个元素,或将每个元素乘二次方,或将字符串列表中的每个元素更改为大写。 计算中容易出错的部分是遍历列表并生成要返回的结果列表的递归过程。 该部分在映射函数中捕获。 对于特定应用程序,只需编写要逐一应用于每个列表元素的函数(相加、乘二次方、更改大小写)。 该函数作为参数发送到映射函数,就像在前面的示例中将 squareIt
发送到 applyIt
一样。
F# 为大多数集合类型(包括列表、数组和序列)提供映射方法。 以下示例使用列表。 语法为 List.map <the function> <the list>
。
// List integerList was defined previously:
//let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
// You can send the function argument by name, if an appropriate function
// is available. The following expression uses squareIt.
let squareAll = List.map squareIt integerList
// The following line displays [1; 4; 9; 16; 25; 36; 49]
printfn "%A" squareAll
// Or you can define the action to apply to each list element inline.
// For example, no function that tests for even integers has been defined,
// so the following expression defines the appropriate function inline.
// The function returns true if n is even; otherwise it returns false.
let evenOrNot = List.map (fun n -> n % 2 = 0) integerList
// The following line displays [false; true; false; true; false; true; false]
printfn "%A" evenOrNot
有关详细信息,请参阅列表。
通过函数调用返回值
最后,如果某个函数在某种语言中具有第一类值的地位,你必须能够将它作为函数调用的值返回,就像返回其他类型(例如整数和字符串)一样。
以下函数调用返回并显示整数。
// Function doubleIt is defined in a previous example.
//let doubleIt = fun n -> 2 * n
System.Console.WriteLine(doubleIt 3)
System.Console.WriteLine(squareIt 4)
以下函数调用返回一个字符串。
// str is defined in a previous section.
//let str = "F#"
let lowercase = str.ToLower()
以下以内联方式声明的函数调用返回一个布尔值。 显示的值为 True
。
System.Console.WriteLine((fun n -> n % 2 = 1) 15)
将函数作为函数调用的值返回的能力是高阶函数的第二个特征。 在以下示例中,checkFor
定义为一个函数,该函数采用一个参数 item
并返回新函数作为其值。 返回的函数将列表作为其参数 lst
,并在 lst
中搜索 item
。 如果存在 item
,则函数返回 true
。 如果不存在 item
,则函数返回 false
。 如上一部分所示,以下代码使用提供的列表函数 List.exists 来搜索列表。
let checkFor item =
let functionToReturn = fun lst ->
List.exists (fun a -> a = item) lst
functionToReturn
以下代码使用 checkFor
创建一个新函数,该函数采用一个参数(一个列表),并在列表中搜索 7。
// integerList and stringList were defined earlier.
//let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
//let stringList = [ "one"; "two"; "three" ]
// The returned function is given the name checkFor7.
let checkFor7 = checkFor 7
// The result displayed when checkFor7 is applied to integerList is True.
System.Console.WriteLine(checkFor7 integerList)
// The following code repeats the process for "seven" in stringList.
let checkForSeven = checkFor "seven"
// The result displayed is False.
System.Console.WriteLine(checkForSeven stringList)
以下示例利用 F# 中函数的第一类值地位来声明函数 compose
,该函数返回两个函数参数的组合。
// Function compose takes two arguments. Each argument is a function
// that takes one argument of the same type. The following declaration
// uses lambda expression syntax.
let compose =
fun op1 op2 ->
fun n ->
op1 (op2 n)
// To clarify what you are returning, use a nested let expression:
let compose2 =
fun op1 op2 ->
// Use a let expression to build the function that will be returned.
let funToReturn = fun n ->
op1 (op2 n)
// Then just return it.
funToReturn
// Or, integrating the more concise syntax:
let compose3 op1 op2 =
let funToReturn = fun n ->
op1 (op2 n)
funToReturn
注意
如需更简短的版本,请参阅下一部分“扩充函数”。
以下代码将两个函数作为参数发送到 compose
,这两个函数都采用相同类型的单个参数。 返回值是一个新函数,它是两个函数参数的组合。
// Functions squareIt and doubleIt were defined in a previous example.
let doubleAndSquare = compose squareIt doubleIt
// The following expression doubles 3, squares 6, and returns and
// displays 36.
System.Console.WriteLine(doubleAndSquare 3)
let squareAndDouble = compose doubleIt squareIt
// The following expression squares 3, doubles 9, returns 18, and
// then displays 18.
System.Console.WriteLine(squareAndDouble 3)
注意
F# 提供两个运算符 <<
和 >>
,用于组合函数。 例如,let squareAndDouble2 = doubleIt << squareIt
等效于前面示例中的 let squareAndDouble = compose doubleIt squareIt
。
以下将函数作为函数调用的值返回的示例将创建一个简单的猜谜游戏。 若要创建游戏,请调用 makeGame
,并为 target
发送你希望他人猜测的值。 函数 makeGame
的返回值是一个函数,该函数采用一个参数(猜测)并报告猜测是否正确。
let makeGame target =
// Build a lambda expression that is the function that plays the game.
let game = fun guess ->
if guess = target then
System.Console.WriteLine("You win!")
else
System.Console.WriteLine("Wrong. Try again.")
// Now just return it.
game
以下代码调用 makeGame
,并为 target
发送值 7
。 标识符 playGame
绑定到返回的 Lambda 表达式。 因此,playGame
是一个将 guess
的值作为其一个参数的函数。
let playGame = makeGame 7
// Send in some guesses.
playGame 2
playGame 9
playGame 7
// Output:
// Wrong. Try again.
// Wrong. Try again.
// You win!
// The following game specifies a character instead of an integer for target.
let alphaGame = makeGame 'q'
alphaGame 'c'
alphaGame 'r'
alphaGame 'j'
alphaGame 'q'
// Output:
// Wrong. Try again.
// Wrong. Try again.
// Wrong. Try again.
// You win!
扩充函数
通过利用 F# 函数声明中的隐式扩充,可以更简洁地编写上一部分中的许多示例。 扩充是将一个具有多个参数的函数转换为一系列嵌入函数并且每个嵌入函数都有一个参数的过程。 在 F# 中,具有多个参数的函数本质上就是扩充函数。 例如,上一部分中的 compose
可以编写成下面这种具有三个参数的简洁样式。
let compose4 op1 op2 n = op1 (op2 n)
但是,其结果是,包含一个参数的函数返回包含一个参数的函数,后者又返回另一个包含一个参数的函数,如 compose4curried
所示。
let compose4curried =
fun op1 ->
fun op2 ->
fun n -> op1 (op2 n)
你可以通过多种方式访问此函数。 以下每个示例都返回并显示 18。 你可以在任何示例中将 compose4
替换为 compose4curried
。
// Access one layer at a time.
System.Console.WriteLine(((compose4 doubleIt) squareIt) 3)
// Access as in the original compose examples, sending arguments for
// op1 and op2, then applying the resulting function to a value.
System.Console.WriteLine((compose4 doubleIt squareIt) 3)
// Access by sending all three arguments at the same time.
System.Console.WriteLine(compose4 doubleIt squareIt 3)
若要验证该函数是否仍像以前一样工作,请再次尝试原始测试用例。
let doubleAndSquare4 = compose4 squareIt doubleIt
// The following expression returns and displays 36.
System.Console.WriteLine(doubleAndSquare4 3)
let squareAndDouble4 = compose4 doubleIt squareIt
// The following expression returns and displays 18.
System.Console.WriteLine(squareAndDouble4 3)
注意
可以通过将参数包含在元组中来限制扩充。 有关详细信息,请参阅形参和实参中的“参数模式”。
以下示例使用隐式扩充来编写 makeGame
的较短版本。 在此格式中,有关 makeGame
如何构造和返回 game
函数的详细信息不太明确,但你可以使用原始测试用例验证结果是否相同。
let makeGame2 target guess =
if guess = target then
System.Console.WriteLine("You win!")
else
System.Console.WriteLine("Wrong. Try again.")
let playGame2 = makeGame2 7
playGame2 2
playGame2 9
playGame2 7
let alphaGame2 = makeGame2 'q'
alphaGame2 'c'
alphaGame2 'r'
alphaGame2 'j'
alphaGame2 'q'
有关扩充的详细信息,请参阅函数中的“参数的部分应用”。
标识符和函数定义可互换
前面示例中的变量名 num
的计算结果为整数 10,毫无疑问,在 num
有效的地方,10 也是有效的。 函数标识符及其值也是如此:在任何可以使用函数名称的地方,都可以使用它所绑定的 Lambda 表达式。
以下示例定义了一个名为 isNegative
的 Boolean
函数,然后交替使用函数名称和函数定义。 接下来的三个示例都返回并显示 False
。
let isNegative = fun n -> n < 0
// This example uses the names of the function argument and the integer
// argument. Identifier num is defined in a previous example.
//let num = 10
System.Console.WriteLine(applyIt isNegative num)
// This example substitutes the value that num is bound to for num, and the
// value that isNegative is bound to for isNegative.
System.Console.WriteLine(applyIt (fun n -> n < 0) 10)
若要更进一步,请将 applyIt
绑定到的值替换为 applyIt
。
System.Console.WriteLine((fun op arg -> op arg) (fun n -> n < 0) 10)
函数是 F# 中的第一类值
前面部分中的示例表明,F# 中的函数满足成为 F# 中的第一类值的标准:
- 可以将标识符绑定到函数定义。
let squareIt = fun n -> n * n
- 可以将函数存储在数据结构中。
let funTuple2 = ( BMICalculator, fun n -> n * n )
- 可以将函数作为参数传递。
let increments = List.map (fun n -> n + 1) [ 1; 2; 3; 4; 5; 6; 7 ]
- 可以将函数作为函数调用的值返回。
let checkFor item =
let functionToReturn = fun lst ->
List.exists (fun a -> a = item) lst
functionToReturn
有关 F# 的详细信息,请参阅 F# 语言参考。
示例
说明
以下代码包含本主题中的所有示例。
代码
// ** GIVE THE VALUE A NAME **
// Integer and string.
let num = 10
let str = "F#"
let squareIt = fun n -> n * n
let squareIt2 n = n * n
// ** STORE THE VALUE IN A DATA STRUCTURE **
// Lists.
// Storing integers and strings.
let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
let stringList = [ "one"; "two"; "three" ]
// You cannot mix types in a list. The following declaration causes a
// type-mismatch compiler error.
//let failedList = [ 5; "six" ]
// In F#, functions can be stored in a list, as long as the functions
// have the same signature.
// Function doubleIt has the same signature as squareIt, declared previously.
//let squareIt = fun n -> n * n
let doubleIt = fun n -> 2 * n
// Functions squareIt and doubleIt can be stored together in a list.
let funList = [ squareIt; doubleIt ]
// Function squareIt cannot be stored in a list together with a function
// that has a different signature, such as the following body mass
// index (BMI) calculator.
let BMICalculator = fun ht wt ->
(float wt / float (squareIt ht)) * 703.0
// The following expression causes a type-mismatch compiler error.
//let failedFunList = [ squareIt; BMICalculator ]
// Tuples.
// Integers and strings.
let integerTuple = ( 1, -7 )
let stringTuple = ( "one", "two", "three" )
// A tuple does not require its elements to be of the same type.
let mixedTuple = ( 1, "two", 3.3 )
// Similarly, function elements in tuples can have different signatures.
let funTuple = ( squareIt, BMICalculator )
// Functions can be mixed with integers, strings, and other types in
// a tuple. Identifier num was declared previously.
//let num = 10
let moreMixedTuple = ( num, "two", 3.3, squareIt )
// You can pull a function out of a tuple and apply it. Both squareIt and num
// were defined previously.
let funAndArgTuple = (squareIt, num)
// The following expression applies squareIt to num, returns 100, and
// then displays 100.
System.Console.WriteLine((fst funAndArgTuple)(snd funAndArgTuple))
// Make a list of values instead of identifiers.
let funAndArgTuple2 = ((fun n -> n * n), 10)
// The following expression applies a squaring function to 10, returns
// 100, and then displays 100.
System.Console.WriteLine((fst funAndArgTuple2)(snd funAndArgTuple2))
// ** PASS THE VALUE AS AN ARGUMENT **
// An integer is passed to squareIt. Both squareIt and num are defined in
// previous examples.
//let num = 10
//let squareIt = fun n -> n * n
System.Console.WriteLine(squareIt num)
// String.
// Function repeatString concatenates a string with itself.
let repeatString = fun s -> s + s
// A string is passed to repeatString. HelloHello is returned and displayed.
let greeting = "Hello"
System.Console.WriteLine(repeatString greeting)
// Define the function, again using lambda expression syntax.
let applyIt = fun op arg -> op arg
// Send squareIt for the function, op, and num for the argument you want to
// apply squareIt to, arg. Both squareIt and num are defined in previous
// examples. The result returned and displayed is 100.
System.Console.WriteLine(applyIt squareIt num)
// The following expression shows the concise syntax for the previous function
// definition.
let applyIt2 op arg = op arg
// The following line also displays 100.
System.Console.WriteLine(applyIt2 squareIt num)
// List integerList was defined previously:
//let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
// You can send the function argument by name, if an appropriate function
// is available. The following expression uses squareIt.
let squareAll = List.map squareIt integerList
// The following line displays [1; 4; 9; 16; 25; 36; 49]
printfn "%A" squareAll
// Or you can define the action to apply to each list element inline.
// For example, no function that tests for even integers has been defined,
// so the following expression defines the appropriate function inline.
// The function returns true if n is even; otherwise it returns false.
let evenOrNot = List.map (fun n -> n % 2 = 0) integerList
// The following line displays [false; true; false; true; false; true; false]
printfn "%A" evenOrNot
// ** RETURN THE VALUE FROM A FUNCTION CALL **
// Function doubleIt is defined in a previous example.
//let doubleIt = fun n -> 2 * n
System.Console.WriteLine(doubleIt 3)
System.Console.WriteLine(squareIt 4)
// The following function call returns a string:
// str is defined in a previous section.
//let str = "F#"
let lowercase = str.ToLower()
System.Console.WriteLine((fun n -> n % 2 = 1) 15)
let checkFor item =
let functionToReturn = fun lst ->
List.exists (fun a -> a = item) lst
functionToReturn
// integerList and stringList were defined earlier.
//let integerList = [ 1; 2; 3; 4; 5; 6; 7 ]
//let stringList = [ "one"; "two"; "three" ]
// The returned function is given the name checkFor7.
let checkFor7 = checkFor 7
// The result displayed when checkFor7 is applied to integerList is True.
System.Console.WriteLine(checkFor7 integerList)
// The following code repeats the process for "seven" in stringList.
let checkForSeven = checkFor "seven"
// The result displayed is False.
System.Console.WriteLine(checkForSeven stringList)
// Function compose takes two arguments. Each argument is a function
// that takes one argument of the same type. The following declaration
// uses lambda expression syntax.
let compose =
fun op1 op2 ->
fun n ->
op1 (op2 n)
// To clarify what you are returning, use a nested let expression:
let compose2 =
fun op1 op2 ->
// Use a let expression to build the function that will be returned.
let funToReturn = fun n ->
op1 (op2 n)
// Then just return it.
funToReturn
// Or, integrating the more concise syntax:
let compose3 op1 op2 =
let funToReturn = fun n ->
op1 (op2 n)
funToReturn
// Functions squareIt and doubleIt were defined in a previous example.
let doubleAndSquare = compose squareIt doubleIt
// The following expression doubles 3, squares 6, and returns and
// displays 36.
System.Console.WriteLine(doubleAndSquare 3)
let squareAndDouble = compose doubleIt squareIt
// The following expression squares 3, doubles 9, returns 18, and
// then displays 18.
System.Console.WriteLine(squareAndDouble 3)
let makeGame target =
// Build a lambda expression that is the function that plays the game.
let game = fun guess ->
if guess = target then
System.Console.WriteLine("You win!")
else
System.Console.WriteLine("Wrong. Try again.")
// Now just return it.
game
let playGame = makeGame 7
// Send in some guesses.
playGame 2
playGame 9
playGame 7
// Output:
// Wrong. Try again.
// Wrong. Try again.
// You win!
// The following game specifies a character instead of an integer for target.
let alphaGame = makeGame 'q'
alphaGame 'c'
alphaGame 'r'
alphaGame 'j'
alphaGame 'q'
// Output:
// Wrong. Try again.
// Wrong. Try again.
// Wrong. Try again.
// You win!
// ** CURRIED FUNCTIONS **
let compose4 op1 op2 n = op1 (op2 n)
let compose4curried =
fun op1 ->
fun op2 ->
fun n -> op1 (op2 n)
// Access one layer at a time.
System.Console.WriteLine(((compose4 doubleIt) squareIt) 3)
// Access as in the original compose examples, sending arguments for
// op1 and op2, then applying the resulting function to a value.
System.Console.WriteLine((compose4 doubleIt squareIt) 3)
// Access by sending all three arguments at the same time.
System.Console.WriteLine(compose4 doubleIt squareIt 3)
let doubleAndSquare4 = compose4 squareIt doubleIt
// The following expression returns and displays 36.
System.Console.WriteLine(doubleAndSquare4 3)
let squareAndDouble4 = compose4 doubleIt squareIt
// The following expression returns and displays 18.
System.Console.WriteLine(squareAndDouble4 3)
let makeGame2 target guess =
if guess = target then
System.Console.WriteLine("You win!")
else
System.Console.WriteLine("Wrong. Try again.")
let playGame2 = makeGame2 7
playGame2 2
playGame2 9
playGame2 7
let alphaGame2 = makeGame2 'q'
alphaGame2 'c'
alphaGame2 'r'
alphaGame2 'j'
alphaGame2 'q'
// ** IDENTIFIER AND FUNCTION DEFINITION ARE INTERCHANGEABLE **
let isNegative = fun n -> n < 0
// This example uses the names of the function argument and the integer
// argument. Identifier num is defined in a previous example.
//let num = 10
System.Console.WriteLine(applyIt isNegative num)
// This example substitutes the value that num is bound to for num, and the
// value that isNegative is bound to for isNegative.
System.Console.WriteLine(applyIt (fun n -> n < 0) 10)
System.Console.WriteLine((fun op arg -> op arg) (fun n -> n < 0) 10)
// ** FUNCTIONS ARE FIRST-CLASS VALUES IN F# **
//let squareIt = fun n -> n * n
let funTuple2 = ( BMICalculator, fun n -> n * n )
let increments = List.map (fun n -> n + 1) [ 1; 2; 3; 4; 5; 6; 7 ]
//let checkFor item =
// let functionToReturn = fun lst ->
// List.exists (fun a -> a = item) lst
// functionToReturn