I've been learning F# and functional programming and trying to do things the functional way. However, when it comes to rewriting some code I'd already written in C# I get stuck at simple if-then statements (ones that only do something, not return a value). I know you can pull this off in F#:
if expr then do ()
However, I thought this was an imperative approach to coding? Maybe I've not learned enough about functional programming, but it doesn't seem functional to me. I thought the functional approach was to compose functions and expressions, not simply execute statements one after the other which is what if-then seems to encourage.
So, am I missing something and if-then is perfectly fine in the functional world? If not, what is the functional equivalent of such a statement? How could I take an if-then and turn it functional?
Edit: I might've asked the wrong question (sorry, still fairly new to functional programming): Let's take a real world example that made me even ask this:
if not <| System.String.IsNullOrWhiteSpace(data) then do let byteData = System.Text.Encoding.Unicode.GetBytes(data) req.ContentLength <- int64 byteData.Length let postStream : System.IO.Stream = req.GetRequestStream() postStream.Write(byteData, 0, byteData.Length) postStream.Flush() postStream.Dispose()
The body of that if-then doesn't return anything, but I don't know how I could make this more functional (if that's even possible). I don't know the proper technique for minimizing imperative code. Given F#'s nature it's fairly easy to just transport my C# directly, but I'm having difficulties turning it functional. Every time I reach such an if statement in C#, and I'm trying to transport it to F#, I get discouraged that I can't think of a way to make the code more functional.
' or ' '. An alternative to IF-THEN-ELSE I use often is the use of logical expressions. A logical expression is specified within parentheses '()' and are evaluated as being true or false.
This function has an if statement without an else . So while a ternary operator could work, it is not ideal. Here we need to run dispatch only when we have a value , otherwise we do nothing.
i.e. you could just as easily write bool isSmall = i < 10; - it's this that avoids the if statement, not the separate function. Code of the form if (test) { x = true; } else { x = false; } or if (test) { return true; } else { return false; } is always silly; just use x = test or return test .
The if / then statement is a conditional statement that executes its sub-statement, which follows the then keyword, only if the provided condition evaluates to true: if x < 10 then x := x+1; In the above example, the condition is x < 10 , and the statement to execute is x := x+1 .
An important point that hasn't been mentioned so far is the difference between if .. then .. else
and if .. then
without the else
branch.
If
in functional languagesThe functional interpretation of if
is that it is an expression that evaluates to some value. To evaluate the value of if c then e1 else e2
you evaluate the condition c
and then evaluate either e1
or e2
, depending on the condition. This gives you the result of the if .. then .. else
.
If you have just if c then e
, then you don't know what the result of the evaluation should be if c
is false
, because there is no else
branch! The following clearly does not make sense:
let num = if input > 0 then 10
In F#, expressions that have side-effects like printf "hi"
return a special value of type unit
. The type has only a single value (written as ()
) and so you can write if
which does an effect in just a single case:
let u = if input > 0 then printf "hi" else ()
This always evaluates to unit
, but in the true
branch, it also performs the side-effect. In the false
branch, it just returns a unit
value. In F#, you don't have to write the else ()
bit by hand, but conceptually, it is still there. You can write:
let u = if input > 0 then printfn "hi"
The code looks perfectly fine to me. When you have to deal with API that is imperative (like lots of the .NET libraries), then the best option is to use the imperative features like if
with a unit
-returning branch.
You can use various tweaks, like represent your data using option<string>
(instead of just string
with null
or empty string). That way, you can use None
to represent missing data and anything else would be valid input. Then you can use some higher-order functions for working with options, such as Option.iter
, which calls a given function if there is a value:
maybeData |> Option.iter (fun data -> let byteData = System.Text.Encoding.Unicode.GetBytes(data) req.ContentLength <- int64 byteData.Length use postStream = req.GetRequestStream() postStream.Write(byteData, 0, byteData.Length) )
This is not really less imperative, but it is more declarative, because you don't have to write the if
yourself. BTW: I also recommend using use
if you want to Dispose
object auotmatically.
There's nothing wrong with if-then in functional world.
Your example is actually similar to let _ = expr
since expr
has side effects and we ignore its return value. A more interesting example is:
if cond then expr
which is equivalent to:
match cond with | true -> expr | false -> ()
if we use pattern matching.
When the condition is simple or there is only one conditional expression, if-then is more readable than pattern matching. Moreover, it is worth to note that everything in functional programming is expression. So if cond then expr
is actually the shortcut of if cond then expr else ()
.
If-then itself is not imperative, using if-then as a statement is an imperative way of thinking. From my experience, functional programming is more about the way of thinking than concrete control flows in programming languages.
EDIT:
Your code is totally readable. Some minor points are getting rid of redundant do
keyword, type annotation and postStream.Dispose()
(by using use
keyword):
if not <| System.String.IsNullOrWhiteSpace(data) then let byteData = System.Text.Encoding.Unicode.GetBytes(data) req.ContentLength <- int64 byteData.Length use postStream = req.GetRequestStream() postStream.Write(byteData, 0, byteData.Length) postStream.Flush()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With