The majority of developers write IF
statements in the following way
if (condition)
{
//Do something here
}
of course this considered normal but often can create nested code which is not so elegant and a bit ugly.
So the question is: Is possible to convert traditional IF
statements to functional ones?
** This is an open question of possible ways to produce more readable code and i prefer not to accept any answer. I believe is better people to choose themselves the best solution for them and vote for the answer they chose.
Basically, an implementation is the combination of a compiler and the C library it supports. C language = what you type into a text editor; implementation = the thing that actually does something with what you typed.
The origin of C is closely tied to the development of the Unix operating system, originally implemented in assembly language on a PDP-7 by Dennis Ritchie and Ken Thompson, incorporating several ideas from colleagues. Eventually, they decided to port the operating system to a PDP-11.
In principle OOP can be done in any language, even assembly. This is because all OO language compilers/assemblers (e.g. C++) ultimately translate the high level constructs of the language into machine language.
In a nutshell, the main difference between C and C++ is that C is function-driven procedural language with no support for objects and classes, whereas C++ is a combination of procedural and object-oriented programming languages.
Although I have already added the response about the 'functional if' aka conditional expressions. It seems something more is being asked for, and that is to facilitate a decision tree. I referred to using monads as the best approach in a reply to that. Here is how it's done. If you haven't used monads before then this might seem like voodoo, but it's essentially a better way of doing what @Gert Arnold posted.
I will take Gert's idea of a decision tree based on the suitability of a client. Instead of using the fluent style with lambdas making the decisions, I'll use LINQ. Which is C#'s native support for monads. The result of using the code will look like this:
var client = new Client {
CriminalRecord = false,
UsesCreditCard = true,
YearsInJob = 10,
Income = 70000
};
var decision = from reliable in ClientDecisions.Reliable
from wealthy in ClientDecisions.Wealthy
select "They're reliable AND wealthy";
var result = decision(client);
if (result.HasValue) Console.WriteLine(result.Value);
decision = from reliableOrWealthy in Decision.Either(
ClientDecisions.Reliable,
ClientDecisions.Wealthy
)
from stable in ClientDecisions.Stable
select "They're reliable OR wealthy, AND stable";
result = decision(client);
if (result.HasValue) Console.WriteLine(result.Value);
decision = from reliable in ClientDecisions.Reliable
from wealthy in ClientDecisions.Wealthy
from stable in ClientDecisions.Stable
select "They're reliable AND wealthy, AND stable";
result = decision(client);
if (result.HasValue) Console.WriteLine(result.Value);
The beauty of this approach is that it is fully composable. You can work with small 'decision pieces' and combine them to make larger decisions. All without any need for if
.
In the above code you will see usage of a ClientDecisions
static class. This is holding some re-usable decision pieces:
public static class ClientDecisions
{
public static Decision<Client, Client> Reliable =>
from client in Decision.Ask<Client>()
where !client.CriminalRecord && client.UsesCreditCard
select client;
public static Decision<Client, Client> Wealthy =>
from client in Decision.Ask<Client>()
where client.Income > 100000
select client;
public static Decision<Client, Client> Stable =>
from client in Decision.Ask<Client>()
where client.YearsInJob > 2
select client;
}
NOTE This is not using IEnumerable
or IQueryable
. In C# it's possible to provide Select
, SelectMany
and Where
methods for any type and if implemented correctly allows you to make any type into a monadic type.
Interestingly in this instance I will be making a delegate into a monad. The reason for this is because we need a value passing into the computation. This is the definition of the delegate:
public delegate DecisionResult<O> Decision<I,O>(I input);
The input in the examples above is Client
and the output is string
. But notice that the delegate above returns a DecisionResult<O>
, and not O
(which would be the string
). This is because we propagate through the computation a property called HasValue
. If HasValue
is false
at any point then the computation ends. This allows us to make decisions during the computation which stops the rest of the computation from executing. This is the DecisionResult<T>
class:
public struct DecisionResult<T>
{
private readonly T value;
public readonly bool HasValue;
internal DecisionResult(bool hasValue, T value = default(T))
{
this.value = value;
HasValue = hasValue;
}
public T Value =>
HasValue
? value
: throw new DecisionFailedException();
}
Next we'll add a couple of helper methods that will produce DecisionResult<T>
values. They will be used in the Select
, SelectMany
and Where
methods later.
public static class DecisionResult
{
public static DecisionResult<O> Nothing<O>() =>
new DecisionResult<O>(false);
public static DecisionResult<O> Return<O>(O value) =>
new DecisionResult<O>(true,value);
}
Now that we have the core types we can write our Select
, SelectMany
and Where
extension methods to the Decision<I,O>
delegate (Yes, you can write extension methods for delegates!)
public static class Decision
{
public static Decision<I, V> Select<I, U, V>(this Decision<I, U> self, Func<U, V> map) =>
input =>
{
var res = self(input);
return res.HasValue
? DecisionResult.Return(map(res.Value))
: DecisionResult.Nothing<V>();
};
public static Decision<I, V> SelectMany<I, T, U, V>(
this Decision<I, T> self,
Func<T, Decision<I, U>> select,
Func<T, U, V> project) =>
input =>
{
var resT = self(input);
if (resT.HasValue)
{
var resU = select(resT.Value)(input);
return resU.HasValue
? DecisionResult.Return(project(resT.Value, resU.Value))
: DecisionResult.Nothing<V>();
}
else
{
return DecisionResult.Nothing<V>();
}
};
public static Decision<I, O> Where<I, O>(this Decision<I, O> self, Predicate<O> pred) =>
input =>
{
var res = self(input);
return res.HasValue
? pred(res.Value)
? DecisionResult.Return(res.Value)
: DecisionResult.Nothing<O>()
: DecisionResult.Nothing<O>();
};
}
These are the methods that allow Decision<I,O>
to be used in a LINQ expression. We will need a couple of additional methods in the Decision
class. The first is Ask<I>
, it simply gets the value that was passed to the computation and allows it to be used in the expression. It's named Ask
because this monad is very similar to the standard Reader
monad from Haskell, and it's called ask
by convention.
public static class Decision
{
public static Decision<I,I> Ask<I>() =>
input => DecisionResult.Return(input);
}
We also want to allow conditional operation, so we'll also add Either<I,O>(...)
to Decision
. This takes any number of Decision<I,O>
monads, runs through them one-by-one, if one succeeds it returns the result immediately, if not it carries on. If none of the computations succeed then the whole computation will end. This allows for 'or' type behaviour, and 'switch' style behaviour:
public static class Decision
{
public static Decision<I, O> Either<I, O>( params Decision<I, O>[] decisions ) =>
input =>
{
foreach(var decision in decisions)
{
var res = decision(input);
if( res.HasValue )
{
return res;
}
}
return DecisionResult.Nothing<O>();
};
}
We don't need 'logical and', because it can be implemented as a series of from
expressions or where
expressions.
Finally we need an exception type in case the programmer tries to use .Value
in DecisionResult<T>
when .HasValue == false
.
public class DecisionFailedException : Exception
{
public DecisionFailedException()
:
base("The decision wasn't made, and therefore doesn't have a value.")
{
}
}
Using this technique (and building on the extension methods) you can avoid using if
entirely. This is a truly functional technique. Of course it's not idiomatic, but it's the most declarative way you'll find in C#.
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