F# - 具有不同参数的相同功能

时间:2017-07-19 17:35:13

标签: function parameters f# optional

在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)

1 个答案:

答案 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;是foldreduce。 (我将解释下面foldreduce之间的区别。在这里,我认为你想要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.mapList.reduce处理。 那种的优雅是你在进行函数式编程时应该瞄准的目标。

折叠并减少

我说我会解释foldreduce之间的区别。主要区别在于您是否希望有时处理空集合。 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?