【问题标题】:F# return in functionsF# 返回函数
【发布时间】:2021-07-05 02:17:50
【问题描述】:

我是 F# 的新手,并且玩了一点,我已经使用过 C#。最近让我有点困惑的一件事是在函数中返回值。我明白一切都是一个表达式(如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”)吗?

【问题讨论】:

  • 我不懂 F#,但我知道您不应该逐字翻译语言。拥抱功能风格!在这里尝试使用高阶函数。
  • 我知道您不应该逐字翻译,这确实是个坏主意,但在某些情况下,使用类似于 C# 中的样式的东西会很有用。
  • 你能举一个/一些例子吗?
  • C#循环中break或return的解决方法很简单。研究 List、Seq 和 Array 模块中的函数。首先,看看 tryFind。
  • 模块功能确实很一般。那些特别的,例如List.max,可以使用更通用的实现。无论您的 C# 循环做什么,都可以使用这些函数来实现。 Spaghetti code de luxe 可能是一个例外,但在转换为 F# 之前将其清理干净。

标签: c# function f# return contains


【解决方案1】:

我同意你的观点:C# 中的早期退出可能很方便,尤其是通过避免嵌套 if 来提高可读性。

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

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

→ 这种编码风格很容易转换为 F#:

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

【讨论】:

    【解决方案2】:

    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
    

    【讨论】:

    • 对控制流使用异常是个坏主意
    • 是的,和 GOTO 一样...请参阅 :-) 和“永远不要那样做!”可能会让这一点更加明显:-D
    【解决方案3】:

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

    一个包含任何IEnumerable&lt;int&gt;的方法:

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

    System.Collections.Generic.List&lt;int&gt; 的类似方法:

    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 等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-21
      • 1970-01-01
      • 2022-10-01
      • 1970-01-01
      • 2014-04-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多