在F#中,函数是否可以根据上下文获取一个必需参数和一个或多个可选参数?在下面的玩具示例中, whisk 最初将 eggYolks 作为唯一参数,但在下一步中,它将获取初始步骤的输出加上 granulatedSugar < / em>和 marsalaWine 。这是可能的吗?如何将其他成分喂给提拉米苏并将这两个步骤打印到控制台?
module Tiramisu =
// http://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAbout.html
open System
// Ingredients.
let eggYolks = "70<g> of egg yolks."
let granulatedSugar = "100<g> of granulated sugar."
let marsalaWine = "120<ml> of sweet marsala wine."
let whisk ingredient = printf "Whisk %s\t" ingredient
let tiramisu ingredients =
ingredients
|> whisk // eggYolks only.
// |> whisk // plus granulatedSugar and marsalaWine.
[<EntryPoint>]
tiramisu eggYolks
// tiramisu (eggYolks granulatedSugar marsalaWine)
答案 0 :(得分:2)
摘要:您应该编写whisk
来获取列表。请参阅下面的完整说明,以错误的方法开始,解释为什么它是错误的方法,然后转向正确的方法。
长解释:
您要问的问题是,您是否可以编写函数whisk
来接受多件事情,例如:您是否询问whisk
函数是否如下所示:
let whisk item1 maybeItem2 maybeItem3 =
printfn "Whisking %A" item1
match maybeItem2 with
| None -> ()
| Some item -> printfn "Also whisking %A" item
match maybeItem3 with
| None -> ()
| Some item -> printfn "Also whisking %A" item
但这种设计存在一些问题。首先,这个函数的类型签名是不方便的:第一个参数是一个成分,但第二个和第三个参数可能是成分(它们实际上是Option
S)。换句话说,如果您在函数中指定了参数的类型,它们将看起来像:
type Ingredient = string // For this example
let whisk (item1 : Ingredient) (maybeItem2 : Ingredient option) (maybeItem3 : Ingredient option) =
// ... function body goes here ...
为什么这样不方便?好吧,如果你只想打一个东西,你必须把这个功能称为whisk eggYolks None None
。 (在没有两个None
参数的情况下调用它会得到partially-applied function,这是一个不同的主题)。另外一个不便之处:仅限于三件物品;如果您想要扫描四个项目,则必须更改功能签名,然后您必须通过为每个调用添加额外None
来更改调用的所有位置以传递四个参数
此外,为简单起见,此示例函数实际上并未返回任何内容。如果它 返回了某些东西,它会变得更加复杂。例如,如果你来自像C#这样的命令式语言,你可以尝试写这个:
type Ingredient = string // For this example
let whisk (item1 : Ingredient) (maybeItem2 : Ingredient option) (maybeItem3 : Ingredient option) =
printfn "Whisking %A" item1
let mutable mixtureSoFar = item1
match maybeItem2 with
| None -> ()
| Some item ->
printfn "Also whisking %A" item
mixtureSoFar <- mixtureSoFar + item
match maybeItem3 with
| None -> ()
| Some item ->
printfn "Also whisking %A" item
mixtureSoFar <- mixtureSoFar + item
mixtureSoFar
但那很难看。当你的F#代码开始看起来很难看时,这通常表明你的设计不合时宜。例如,您可以让whisk
函数使用成分列表,而不是尝试传递多个参数,其中一些参数可能是None
。例如,whisk
函数看起来像:
let whisk (items : Ingredient list) =
// ... function body goes here ...
然后你就这样称呼它:
let whiskedEggYolks = whisk [eggYolks]
let mixture = whisk [whiskedEggYolks; granulatedSugar; marsalaWine]
该功能在内部会是什么样的?好吧,它可能会对每种成分进行一些转换,然后将一些组合功能结合起来组合成一个结果。在F#中,&#34;对每个项目应用一些转换&#34;被称为map
,并且&#34;应用一些组合功能将多个项目合并为一个项目&#34;是fold
或reduce
。 (我将解释下面fold
和reduce
之间的区别。在这里,我认为你想要reduce
,因为搅拌一个空碗并没有意义。所以我们的whisk
函数变为:
let whisk (ingredients : Ingredient list) =
ingredients
|> List.map (fun x -> sprintf "%s, whisked" x)
|> List.reduce (fun a b -> sprintf "%s, plus %s" a b)
当您"70<g> of egg yolks"
时,您会得到"70<g> of egg yolks, whisked"
。然后,当您与"100<g> of granulated sugar"
和"120<ml> of sweet marsala wine"
一起扫描时,您会得到输出:
"70<g> of egg yolks, whisked, plus 100<g> of granulated sugar, whisked, plus 120<ml> of sweet marsala wine, whisked"
然而你的功能非常简单(只需要三行来处理任何数字的成分!)而且你没有必要编写任何列表处理代码,因为那是由标准F#核心库函数List.map
和List.reduce
处理。 那种的优雅是你在进行函数式编程时应该瞄准的目标。
我说我会解释fold
和reduce
之间的区别。主要区别在于您是否希望有时处理空集合。 reduce
函数要求您要减少的集合中至少有一个项目,并且不需要初始值,因为该集合的第一项是作为初始值。但是因为reduce
需要集合的第一项来设置它的初始值,所以如果它被传递给一个空集合,它将抛出异常,因为它无法知道要使用什么值。 (F#故意避免使用null
,这是有充分理由的 - 所以并不总是可以为空集合确定一个好的值。而fold
要求您指定一个显式初始值,但是对于一个空集合是可以的,因为如果您传递一个空集合,那么它只返回默认值。如,
let zeroInts = []
let oneInt = [1]
let twoInts = [1; 2]
let add x y = x + y
zeroInts |> List.reduce add // Error
oneInt |> List.reduce add // Result: 1
twoInts |> List.reduce add // Result: 3
zeroInts |> List.fold add 0 // No error, result: 0
oneInt |> List.fold add 0 // Result: 1
twoInts |> List.fold add 0 // Result: 3
有关更多说明,另请参阅Difference between fold and reduce?。