简体   繁体   English

F# 函数返回

[英]F# return in functions

I'm new to F# and playing around a bit, I already did work with C#.我是 F# 的新手,玩了一会儿,我已经使用过 C#。 One thing that recently confused me a bit was returning values in functions.最近让我有点困惑的一件事是在函数中返回值。 I do unterstand that everything is an expression (as described here ) and that the following code won't compile nor execute:我明白一切都是一个表达式(如此所述)并且以下代码不会编译或执行:

let foo x =
   if x = 0 then x + 2 //The compiler will complain here due to a missing else branch.
   x - 2

Instead, you would have to write that:相反,您必须这样写:

let foo x =
   if x = 0 then x + 2
   else x - 2

I do see why that is and a similar problem also occurs when having a loop.我确实明白为什么会这样,并且在有循环时也会出现类似的问题。

But consider a function which equals a "contains" function of a list in C#:但是考虑一个 function ,它等于 C# 中列表的“包含”function:

public static bool Contains(int num, List<int> list) {
   foreach (int i in list) {
      if (i == num) return true;
   }
   return false;
}

If you would translate that one by one, the compiler will tell you there is missing an else branch and I do unterstand why:如果您逐一翻译,编译器会告诉您缺少一个 else 分支,我不明白为什么:

let Contains (num : int) list =
   for i in list do
      if i = x then true //Won't compile due to a missing else branch!
   false

Instead, you'd probably write a recursive function like that:相反,您可能会像这样编写递归 function :

let rec Contains (num : int) list =
   match list with
   |  [] -> false
   |  head::tail -> head = num || (Contains num tail)

And I'm be fine with that, but in some situations, it is hard to do pattern matching.我对此很好,但在某些情况下,很难进行模式匹配。 Is there really no way to do the described above?真的没有办法做到上面描述的吗? Couldn't one use a keyword like yield (or something similar to "return" in C#)?不能使用像 yield 这样的关键字(或类似于 C# 中的“return”)吗?

I agree with you: early exits in C# can be convenient, especially to increase readability by avoiding nested if .我同意你的观点:C# 中的早期退出可能很方便,尤其是通过避免嵌套if来提高可读性。

In F#, to avoid nested if or too big pattern matching (ie with too much cases or an input tuple with too much items), we can extract sub-functions: with meaningful name, it improves the code readability.在 F# 中,为了避免嵌套if或太大的模式匹配(即有太多的 case 或具有太多项的输入元组),我们可以提取子函数:具有有意义的名称,它可以提高代码的可读性。

With lists and sequences ( IEnumerable ) in C#, code is generally more declarative using built-in methods or LINQ than using for/foreach loops.对于 C# 中的列表和序列 ( IEnumerable ),使用内置方法或 LINQ 的代码通常比使用for/foreach循环更具声明性。 It's not always true but it's worth trying.这并不总是正确的,但值得一试。 By the way, in some situations, ReSharper can suggest to refactor for/foreach loops to LINQ queries.顺便说一句,在某些情况下,ReSharper 可以建议将for/foreach循环重构为 LINQ 查询。

→ This coding style is easily translatable to F#: → 这种编码风格很容易转换为 F#:

  • List:列表:
    • C# → list.Contains(num) C# → list.Contains(num)
    • F# → [1;2;3] |> List.contains 1 F# → [1;2;3] |> List.contains 1
  • Sequence:序列:
    • C# → list.Any(x => x == num) C# → list.Any(x => x == num)
    • F# → seq { 1;2;3 } |> Seq.exists ((=) 1) F# → seq { 1;2;3 } |> Seq.exists ((=) 1)
    • F# → seq { 1;2;3 } |> Seq.contains 1 F# → seq { 1;2;3 } |> Seq.contains 1

An early return in C# is really just a GOTO. C# 的早期return实际上只是一个 GOTO。 And I would consider that bad style, except for the "bouncer pattern" (validation).我会考虑那种糟糕的风格,除了“保镖模式”(验证)。 The argument there usually is to get rid of mental overload as early as possible.通常的论点是尽早摆脱精神超负荷。 Given F#'s expressiveness, most validation can happen through types, so the otherwise ubiquitous if param = null || invalid(param) return鉴于 F# 的表现力,大多数验证都可以通过类型进行,因此if param = null || invalid(param) return if param = null || invalid(param) return style is not (or at least less) needed.不需要(或至少更少) if param = null || invalid(param) return样式。 Once you embrace the functional style, you'll find that there is less and less need for it.一旦你接受了功能风格,你会发现对它的需求越来越少。 If needed, you can always simulate the goto:-)如果需要,您可以随时模拟 goto :-)

exception Ret of string

let needsEarlyReturn foos =
    try
        for f in foos do
            if f > 42 then Ret "don't do" |> raise
            // more code
            if f > 7 then Ret "that ever" |> raise
        "!"
    with Ret v -> v

Keep in mind that the F# compiler converts tail recursion into a loop.请记住,F# 编译器将尾递归转换为循环。 So the solution is really to use a tail-recursive helper function even if you're using a type that doesn't have the pattern matching support of the F# list.因此,即使您使用的类型不具备 F# 列表的模式匹配支持,解决方案实际上也是使用尾递归帮助程序 function 。 Some examples:一些例子:

A contains method for any IEnumerable<int> :任何IEnumerable<int>的 contains 方法:

let contains item (xs : int seq) =
    use e = xs.GetEnumerator()
    let rec helper() = e.MoveNext() && (e.Current = item || helper())
    helper()

A similar method for a System.Collections.Generic.List<int> : System.Collections.Generic.List<int>的类似方法:

let contains item (xs : ResizeArray<int>) =
    let rec helper i = i < xs.Count && (xs.[i] = item || helper (i + 1)) 
    helper 0

In general, you can use a helper function for early exit like this (to keep the examples simple, the loop is infinite; the only way out is through the early exit):通常,您可以使用帮助程序 function 像这样提前退出(为了使示例简单,循环是无限的;唯一的出路是通过提前退出):

let shortCircuitLoop shouldReturn getNextValue seed =
    let rec helper accumulator =
        if shouldReturn accumulator then accumulator else helper (getNextValue accumulator)
    helper seed

A C# version of this: C# 版本:

T ShortCircuit<T>(Func<T, bool> shouldReturn, Func<T, T> getNextValue, T seed) {
    while (true)
    {
        if (shouldReturn(seed))
            return seed;
        seed = getNextValue(seed);
    }
}

These examples are for illustration only, of course, as a pedagogical exercise.这些例子只是为了说明,当然,作为一个教学练习。 In production code, as noted by Romain Deneau, you'll want to use the library functions such as Seq.contains , List.contains , and so on.正如 Romain Deneau 所指出的,在生产代码中,您需要使用库函数,例如Seq.containsList.contains等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM