如何重构所需的else子句?

时间:2012-08-15 20:19:59

标签: c# f#

我有一个看起来有点像这样的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#中,由于强制性的其他分支,这最终看起来更加丑陋:

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#中写一个更简洁的方法是什么?

6 个答案:

答案 0 :(得分: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
  }

答案 1 :(得分:9)

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

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

虽然F#没有命令性返回(如果你想返回,你需要真假分支),它实际上也简化了这段代码,因为你可以写:

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# computation expression that allows using imperative-style returns。这类似于丹尼尔的计算,但更强大。

答案 2 :(得分:6)

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

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

更短更合理。 (换句话说,else仅对返回值的if表达式是必需的;因为您正在执行命令式工作,请使用不需要if的{​​{1}}语句。)

答案 3 :(得分: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 

答案 4 :(得分: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

或者为了进一步说明,使用命名函数而不是lambdas:

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

答案 5 :(得分: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;