I'm new to F# and have been implementing simple algorithms to learn the language constructs. I implemented the bisection method using if/else
and then wanted to learn how to do it using matching.
if fc = 0.0 then printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
else if ((b - a) * 0.5) < eps then printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
else if new_count = n then printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
else if fc * fa < 0.0 then bisect a c new_count
else if fc * fb < 0.0 then bisect c b new_count
I found that using match a, b, fa, fb, fc
would cause type errors, where if I took just a single parameter I could essentially ignore the parameter and check my conditions. What is the idiomatic F#/Functional way to use matching for this? Or should I just stick to if/else?
match a with
| a when fc = 0.0 -> printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
| a when ((b - a) * 0.5) < eps -> printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
| a when new_count = n -> printfn "%i/%i - f(%f) = %f, f(%f) = %f, f(%f) = %f" new_count n a fa b fb c fc
| a when fc * fa < 0.0 -> bisect a c new_count
| a when fc * fb < 0.0 -> bisect c b new_count
Your conditions all deal with disparate things, unrelated to each other, so the string of
if
s is just fine. The only thing I'd recommend is usingelif
instead ofelse if
.match
should be understood along the lines of "given this thing that can be of different flavors, here's how to handle those flavors". One particular strength ofmatch
is that the compiler will figure out, and tell you, if you missed any of the "flavors". In particular, the code you gave in your question should produce a compiler warning, complaining that "Incomplete pattern matches on this expression". Think about it: what would be the result of that expression when none of the cases match?With
if
s, this will also be the case. For example, this doesn't compile:Why? Because the compiler knows what the result should be when
a < 5
(i.e. it should be7
), but what should it be otherwise? The compiler can't decide for you, so it will generate an error.This, on the other hand, would compile:
But in your particular case, the compiler lets you get away with this, because all your branches return a
unit
(why? becauseprintf
returnsunit
, and all others are recursive). In other words, the following will compile:And the following:
The compiler lets you get away with this, because
unit
is special: it can only ever have one value (namely,()
), so the compiler can decide for you what the result of the expression would be when the condition isn'ttrue
.One practical upshot of this would be that, if you didn't think about your conditions very carefuly, it could conceivably happen so that none of your conditions are
true
, and so the whole thing will returnunit
and not print anything. I can't say if that could happen in your particular case, because I don't see the whole function definition.Sometimes, as Fyodor Soikin correctly explains, a series of
if
,else if
,else
expressions is the best option, although I'd useelif
instead ofelse if
.What sometimes make sense is to compute some of the values before, and put them into a data structure you can match on - typically a tuple.
Using a simplified version of the above question, imagine that you only need to check the first two cases, you could do it like this:
Notice the comma after
fc = 0.
, which makes the match expression into a tuple - more specifically abool * bool
.This has the disadvantage that it's inefficient, because you'd always be evaluating the expression
((b - a) * 0.5) < eps
, even iffc = 0.
evaluates totrue
.Still, evaluating a simple expression like
((b - a) * 0.5) < eps
will be so fast that you probably wouldn't be able to measure it, so if you think this way of expressing the algorithm is more readable, you could decide to trade off that small inefficiency for better readability.In this case, though, I don't think it's more readable, so I'd still go with a series of
if
,elif
,else
expressions.Here's an example where pre-computing values and putting them into a tuple makes more sense:
This is a common implementation of the FizzBuzz kata. Here it make sense because both modulo numbers are needed for the first match, so there's no inefficiency, and the code is quite readable as well.
The point about inefficiency above is true for F# because F# is eagerly evaluated. In Haskell, on the other hand, expressions are lazily evaluated, so there you'd be able to do the following without loss of efficiency:
The second expression in the tuple would only be evaluated if necessary, so if the first case (
(True, _)
) is matched, there'd be no need to evaluate the second expression.