I have a C# method that looks a bit like this:
bool Eval() {
// do some work
if (conditionA) {
// do some work
if (conditionB) {
// do s
Well, since 'do some work' is already imperative (presumably), then I think that
let eval() =
let mutable result = false
... // ifs
result <- true
... // no more elses
result
is shorter and reasonable. (In other words, else
is only mandatory for if
expressions that return values; since you're doing imperative work, use if
statements that don't need an else
.)
Please don't be afraid to extract functions. This is key to controlling complex logic.
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
As mentioned in the comments, you could inverse the condition. This simplifies the C# code, because you can write:
if (!conditionA) return false;
// do some work
Although F# does not have imperative returns (if you want to return, you need both true and false branches), it actually simplifies this code a bit too, because you can write:
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
You still have to write false
multiple times, but at least you don't have to indent your code too far. There is an unlimited number of complex solutions, but this is probably the simplest option. As for more complex solution, you could use an F# computation expression that allows using imperative-style returns. This is similar to Daniel's computation, but a bit more powerful.
Using the higher-order functions in the Option
module can make this flow very cleanly without any mutable state:
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
Or for further clarity, using named functions rather than 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
You could make your code into a kind of truth table, which depending on your real-world case might make it more explicit:
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;
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
}