F# return 在函数中

F# return in functions

我是 F# 的新手并且玩了一会儿,我已经使用过 C#。最近让我有点困惑的一件事是 returning 函数中的值。我不明白一切都是表达式(如 here 所述)并且以下代码不会编译也不会执行:

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

相反,您必须这样写:

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

我明白为什么会这样,并且在循环时也会出现类似的问题。

但是考虑一个函数,它等于 C# 中列表的“包含”函数:

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

如果你将那个一个一个地翻译,编译器会告诉你缺少一个 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

相反,您可能会编写这样的递归函数:

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

我对此没有意见,但在某些情况下,很难进行模式匹配。真的没有办法做到上述吗?不能使用像 yield 这样的关键字(或类似于 C# 中的“return”的关键字)吗?

C# 中的早期 return 实际上只是一个 GOTO。我会认为这种糟糕的风格,除了“保镖模式”(验证)。那里的争论通常是尽早摆脱精神超负荷。鉴于 F# 的表现力,大多数验证都可以通过类型进行,因此不需要(或至少不需要)无处不在的 if param = null || invalid(param) return 样式。一旦您接受了功能性风格,您会发现对它的需求越来越少。如果需要,您可以随时模拟 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

我同意你的看法:在 C# 中提前退出可能很方便,尤其是通过避免嵌套 if.

来提高可读性

在 F# 中,为了避免嵌套 if 或太大的模式匹配(即情况太多或输入元组包含太多项),我们可以提取子函数:使用有意义的名称,它改进代码可读性。

对于 C# 中的列表和序列 (IEnumerable),使用内置方法或 LINQ 的代码通常比使用 for/foreach 循环更具声明性。这并不总是正确的,但值得一试。顺便说一下,在某些情况下,ReSharper 可以建议将 for/foreach 循环重构为 LINQ 查询。

→ 这种编码风格很容易翻译成 F#:

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

请记住,F# 编译器会将尾递归转换为循环。因此,解决方案实际上是使用尾递归辅助函数,即使您使用的类型不具有 F# 列表的模式匹配支持。一些例子:

A 包含任何 IEnumerable<int>:

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

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

一般来说,您可以像这样使用一个辅助函数来提前退出(为了使示例简单,循环是无限的;唯一的出路是通过提前退出):

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

此的 C# 版本:

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

当然,这些示例仅供参考,作为教学练习。在生产代码中,如 Romain Deneau 所述,您需要使用 Seq.containsList.contains 等库函数。