7

我有一个看起来有点像这样的 C# 方法:

bool Eval() {
  // do some work
  if (conditionA) {
     // do some work
     if (conditionB) {
       // do some work
       if (conditionC) {
         // do some work
         return true;
       }
     }
  }
  return false;
}

在 F# 中,由于强制性的 else 分支,这最终看起来有点丑陋:

let eval() =
  // do some work
  if conditionA then
    // do some work
    if conditionB then
      // do some work
      if conditionC then
        // do some work
        true
      else
        false
    else
      false
  else
    false

用 F# 编写这个更简洁的方法是什么?

4

6 回答 6

13
module Condition =
  type ConditionBuilder() =
    member x.Bind(v, f) = if v then f() else false
    member x.Return(v) = v
  let condition = ConditionBuilder()

open Condition

let eval() =
  condition {
    // do some work
    do! conditionA
    // do some work
    do! conditionB
    // do some work
    do! conditionC
    return true
  }
于 2012-08-15T21:15:49.357 回答
9

如评论中所述,您可以反转条件。这简化了 C# 代码,因为您可以编写:

if (!conditionA) return false;
// do some work

虽然 F# 没有命令式返回(如果要返回,则需要 true 和 false 分支),但它实际上也简化了这段代码,因为您可以编写:

let eval() = 
  // do some work 
  if not conditionA then false else
  // do some work 
  if not conditionB then false else
  // do some work 
  if not conditionC then false else
    // do some work 
    true 

您仍然需要编写false多次,但至少您不必将代码缩进太多。有无限数量的复杂解决方案,但这可能是最简单的选择。至于更复杂的解决方案,您可以使用允许使用命令式返回的 F# 计算表达式。这类似于 Daniel 的计算,但更强大一些。

于 2012-08-15T21:16:07.220 回答
6

好吧,既然“做一些工作”已经势在必行(大概),那么我认为

let eval() =
    let mutable result = false
    ... // ifs
        result <- true
    ... // no more elses
    result

更短更合理。(换句话说,else仅对if返回值的表达式是强制性的;由于您正在执行命令式工作,因此请使用if不需要 . 的语句else。)

于 2012-08-15T20:22:46.097 回答
6

请不要害怕提取函数。这是控制复杂逻辑的关键。

let rec partA () =
  // do some work
  let aValue = makeA ()
  if conditionA 
  then partB aValue 
  else false
and partB aValue =
  // do some work
  let bValue = makeB aValue
  if conditionB 
  then partC bValue
  else false
and partC bValue =
  // do some work
  conditionC 
于 2012-08-15T20:53:29.097 回答
4

使用模块中的高阶函数Option可以使这个流程非常干净,没有任何可变状态:

let Eval () =
    // do some work
    if not conditionA then None else
        // do some work
        Some state
    |> Option.bind (fun state ->
        if not conditionB then None else
            // do some work
            Some state')
    |> Option.bind (fun state ->
        if not conditionC then None else
            // do some work
            Some true)
    |> defaultArg <| false

或者为了更清楚起见,使用命名函数而不是 lambda:

let Eval () =
    let a () =
        if not conditionA then None else
            // do some work
            Some state
    let b state =
        if not conditionB then None else
            // do some work
            Some state'
    let c state =
        if not conditionC then None else
            // do some work
            Some true
    // do some work
    a () |> Option.bind b |> Option.bind c |> defaultArg <| false
于 2012-08-15T20:48:40.133 回答
1

您可以将您的代码变成一种真值表,这取决于您的实际情况可能会使其更加明确:

let condA() = true
let condB() = false
let condC() = true

let doThingA() = Console.WriteLine("Did work A")
let doThingB() = Console.WriteLine("Did work B")
let doThingC() = Console.WriteLine("Did work C")

let Eval() : bool =
    match condA(), condB(), condC() with
    | true,  false, _     -> doThingA();                           false;
    | true,  true,  false -> doThingA(); doThingB();               false;
    | true,  true,  true  -> doThingA(); doThingB(); doThingC();   true;
    | false, _,     _     ->                                       false;
于 2012-08-17T07:00:40.540 回答