First, I love this library. I'm learning so much about FP and this library has helped me a ton!
Question: How do I take the imperative logic below and make it FP using the library?
I was reading your wiki page on Function Signatures. You stated it is best to stay in context and use Match as a last resort when you need to get to the bound values. I'm trying to figure out the best way to build a pipeline and stay in context _(I assume this means that evaluation stays lazy and at the end of the pipeline, I end up with something monadic over T)_.
Option<string>.Action<Exception> delegate. and return an Option<string>.None.Func<string> getDefaultValue = () => {...} delegate.static readonly Action<Exception> WriteExceptionsToTelemetryClient = (Exception ex) => {...};
static Option<string> HandleDelegatesToExternalSources(Func<string> GetStringFromExternalSource)
{
Option<string> result;
try
{
result = GetStringFromExternalSource();
}
catch (Exception ex)
{
WriteExceptionsToTelemetryClient(ex);
result = Option<string>.None;
}
return result;
}
static readonly Func<string> GetStringFromFirstExternalSource = () => throw new Exception();
static readonly Func<string> GetStringFromSecondExternalSource = () => null;
static readonly Func<Some<string>> GetStringFromSourceThatDoesntThrowExceptions = () => LanguageExt.Some.Create<string>("foo");
// Function signature indicates that caller gets an Option in Some state. Returning None is not an option.
static Some<string> GetString()
{
Option<string> result;
// First external source
result = HandleDelegatesToExternalSources(GetStringFromFirstExternalSource);
if (result.IsSome) return new Some<string>(result);
// Second external source
result = HandleDelegatesToExternalSources(GetStringFromSecondExternalSource);
if (result.IsSome) return new Some<string>(result);
// Default value
return GetStringFromSourceThatDoesntThrowExceptions();
}
The result of calling GetString() is Some<string>("foo"). Also the Telemetry Client wrote an exception.
I'd do something like this:
```c#
using LanguageExt;
using static LanguageExt.Prelude;
using System;
namespace ConsoleApp11
{
public static class Telemetry
{
public static readonly Action
{
Console.WriteLine(ex.Message);
};
}
public static class TryExtensions
{
public static Try<A> LogFailures<A>(this Try<A> ma) =>
ma.Match(
Succ: x => Try(x),
Fail: ex =>
{
Telemetry.Client(ex);
return Try<A>(ex);
});
}
class Program
{
static readonly Func<string> GetStringFromFirstExternalSource = () => throw new Exception();
static readonly Func<string> GetStringFromSecondExternalSource = () => null;
static readonly Func<string> GetStringFromSourceThatDoesntThrowExceptions = () => LanguageExt.Some.Create<string>("foo");
static string GetStringOrDefault() =>
GetString().IfNone(GetStringFromSourceThatDoesntThrowExceptions());
static Option<string> GetString() =>
Get(Seq(GetStringFromFirstExternalSource, GetStringFromSecondExternalSource));
static Option<A> Get<A>(Seq<Func<A>> delegates) =>
delegates.FoldWhile(
Option<A>.None,
(state, fun) => Try(fun).LogFailures().ToOption(),
(state) => state.IsNone);
}
}
```
Personally I wouldn't bother so much with the Some<A> type, mostly because now that I always use Option for optional values, when I see a reference type that isn't an Option then I know it should never be null. This does require a certain amount of team discipline obviously.
You'll notice I created an extension method for Try<A> for logging, this is only required if you don't use the built in error-logger in TryConfig, and instead you want to make a decision on a case-by-case basis whether you want to log or not.
I have split the GetString method up into a more general Get and then less general GetString, and GetStringOrDefault method. This makes each function much simpler and easier to reason about.
One thing I'd warn against is the path you appear to be going down of creating lots of 'global' fields. That's going to get you in to trouble quite quickly. Consider either passing external IO functions through to the functions, or trying to move IO to the edges, or build your own monadic types to capture IO operations.
Pure functions are your best friend, and that's where you'll get the most wins from taking a functional approach.
Thanks! Can you explain what you mean by "global" fields? I've been struggling with how to handle connections, clients, telemetry, etc.
Can you explain what you mean by "global" fields
Essentially lots of public static fields or properties. It will create a messy set of dependencies that will be hard to reason about and maintain. For example if I have a function prototype like this:
```c#
int Divide(int x, int y);
It should do nothing other than divide two numbers. If behind the scenes it fires off calls to various global services then it hides what's really going on, that approach over time makes code brittle and hard to deal with.
Now imagine this:
```c#
int Divide(int x, int y, Func<Exception, Unit> telemetry);
It's obvious to all users of your code that it may do something more than add two numbers together.
The problem with that approach is that there are still possible side-effects happening during the function. Which may have performance implications, race condition implications, or just _extra argument to every function_ implications.
So, we could change Divide to not do any telemetry at all, and just return the result or the error:
```c#
Either
Now, that talks to me. The function should have no external dependencies other than the arguments being passed, but the result could be either an `Exception` or an `int`. Obviously the exception could come from divide-by-zero, and so now you can imagine what the function might look like:
```c#
Either<Exception, int> Divide(int x, int y) =>
Try(() => x / y).ToEither();
We now have a _declarative_ and _pure_ function.
So, what happens with the Exception, when does it get logged?
Let's imagine we had a small API that was _declarative_ and _pure_ like the Divide function:
```c#
public class Error : NewType
{
public Error(string value) : base(value) { }
public static Error FromEx(Exception e) => new Error(e.Message);
}
Either
Try(() => x / y).ToEither(Error.FromEx);
Either
parseInt(value).ToEither(Error.New("String can't be parsed into an integer"));
Either
from l in FromString(left)
from r in FromString(right)
from n in Div(l, r)
select n;
Either
from l in FromString(left)
from r in FromString(right)
select l + r;
Either
from l in FromString(left)
from r in FromString(right)
select l - r;
Either
from l in FromString(left)
from r in FromString(right)
select l * r;
Either
from x in Div("48", "2")
from y in Add("10", "12")
from z in Sub("5", "3")
select x + y + z;
Notice how the `Error` type propagates, and function `Foo` which is using other possible error throwing functions is relatively more complex. You can maintain this right up to the 'edges' of your application. i.e. away from your pure code. That means if something goes wrong, the expression backs out and then you `Match` on failure and call your telemetry providers there. No need for injection.
That works well if you never recover from failure. In your example you try to return a default value if the others fail (I'd argue this isn't always great practice, it can make much more sense to propagate the error, but it really does depend on the use-case). But if you do need call IO functionality mid-way through an expression then there are a number of routes to achieve this.
### Reader monad
This is a built in type in lang-ext that allows you to pass an _environment_ to your computations. It isn't always the easiest thing to use, but it is effective in that you can gain access to telemetry without having to explicitly pass an extra argument:
First, you create a type to pass in as the telemetry:
```c#
public class World
{
public readonly Func<string, Unit> SendTelemetry;
public World(Func<string, Unit> sendTelemetry)
{
SendTelemetry = sendTelemetry;
}
}
Then we'll add a couple of extension methods that work with Reader<World, A>. Writing bespoke extensions for your environment type tends to make it easier to work with. These will convert between Try<A>, Option<A> to Reader<World, A>:
```c#
public static Reader
ma.Match(
Succ: x => (x, false),
Fail: ex =>
{
env.SendTelemetry(ex.ToString());
return (default(A), true);
});
public static Reader<World, A> ToReader<A>(this Option<A> ma, string message) => env =>
ma.Match(
Some: x => (x, false),
None: () =>
{
env.SendTelemetry(message);
return (default(A), true);
});
Notice the `Try<A>` variant will send the telemetry if there's an exception caught.
If the returning of `(x, false)` and ` (default(A), true)` looks wierd. Take a look at the declaration of `Reader`:
```c#
namespace LanguageExt
{
public delegate (A Value, bool IsFaulted) Reader<Env, A>(Env env);
}
It's just a delegate that takes an Env and returns (A, bool) the bool indicates whether it's faulted or not.
Before we use the Reader in anger, we can add a helper function to send random telemetry whenever we want:
```c#
public static Reader
from world in ask
let _ = world.SendTelemetry(message)
select _;
Notice how the function only takes a `string`, but somehow returns a `Reader<World, Unit>`. That's because of the `ask<World>()` call. Which goes and gets the environment for you and puts it in `world`. We can then invoke the injected `Func` to deliver the message.
Now we can use for real:
```c#
static Reader<World, int> Div(int x, int y) =>
Try(() => x / y).ToReader();
static Reader<World, int> FromString(string value) =>
parseInt(value).ToReader("String can't be parsed into an integer");
static Reader<World, int> Div(string left, string right) =>
from l in FromString(left)
from r in FromString(right)
from n in Div(l, r)
from _ in SendTelemetry($"Result of {left} / {right} = {n}")
select n;
static Reader<World, int> Add(string left, string right) =>
from l in FromString(left)
from r in FromString(right)
from _ in SendTelemetry($"Result of {left} + {right} = {l + r}")
select l + r;
static Reader<World, int> Sub(string left, string right) =>
from l in FromString(left)
from r in FromString(right)
from _ in SendTelemetry($"Result of {left} - {right} = {l - r}")
select l - r;
static Reader<World, int> Mul(string left, string right) =>
from l in FromString(left)
from r in FromString(right)
from _ in SendTelemetry($"Result of {left} - {right} = {l * r}")
select l * r;
Notice the calls to SendTelemetry.
Obviously that still calls the telemetry during the computation. So, another approach is to use a Writer<Exception, A> monad to collect telemetry and then dump it all in one go when you're done. Instead of using ask to get the environment, you call tell(exception) to log your output.
Another approach is to build your own monadic type. That might be a bit out of your reach at the moment if you're just getting started, but once you know how to write a Bind function the rest is trivial.
For example if we create a lightweight Maybe monad (which works like the Option monad):
```c#
public class Maybe
{
public static Maybe Just(A value) => new Just(value);
public readonly static Maybe Nothing = new Nothing();
}
public class Just<A> : Maybe<A>
{
public readonly A Value;
public Just(A value)
{
Value = value;
}
}
public class Nothing<A> : Maybe<A>
{
public Nothing()
{
}
}
The `Bind` function would look like this:
```c#
public static class MaybeExtensions
{
public static Maybe<B> Bind<A, B>(this Maybe<A> ma, Func<A, Maybe<B>> f) =>
ma is Just<A> just
? f(just.Value)
: Maybe<B>.Nothing;
}
It encapsulates the rules of the monad (i.e. don't run anything else if we get a Nothing).
Once the Bind is written, the extensions to make it work with LINQ is almost cut n paste:
```c#
public static Maybe Select(this Maybe ma, Func f) =>
ma.Bind(x => Maybe.Just(f(x)));
public static Maybe<B> SelectMany<A, B>(this Maybe<A> ma, Func<A, Maybe<B>> f) =>
ma.Bind(f);
public static Maybe<C> SelectMany<A, B, C>(this Maybe<A> ma, Func<A, Maybe<B>> bind, Func<A, B, C> project) =>
ma.Bind(a => bind(a).Select(b => project(a, b)));
``
Just changeMaybe` to the name of your monad and it will work for anything.
The point of this is that you can relatively easily roll your own monadic types, which carry injected functionality and are less cumbersome than Reader or Writer; and easily allow you to change what you do with IO or any external dependencies.
And the final, most pure method, is the Free monad approach. This creates true separation between the pure and IO code. I've written about this in depth before and there are two samples in the Samples folders that use this approach.
It is very powerful, but requires a reasonable amount of typing, however it's a reeeeally powerful technique.
@louthy Wow, another amazing reply! I wish you would turn these replies into articles, there is sooo much useful info in here, and it just gets lost when the issue is closed.
Anyway, I'm working my way through this, trying to understand it all, but am struggling to understand what the Reader monad is used for. You created a Div function that takes two strings, and attempts to divide their int values. Please can you explain a) how you would use such a function, as I can't see what you do with the Reader<World, int> that is returned, and b) what the Reader adds here that you didn't have with the code you showed earlier.
Thanks again for another great article
@louthy Paul, I know you just closed this, but is there any chance you could answer the two questions I asked on Feb 7?
Thanks
Most helpful comment
Essentially lots of public static fields or properties. It will create a messy set of dependencies that will be hard to reason about and maintain. For example if I have a function prototype like this:
```c#
int Divide(int x, int y);
It's obvious to all users of your code that it may do something more than add two numbers together.
The problem with that approach is that there are still possible side-effects happening during the function. Which may have performance implications, race condition implications, or just _extra argument to every function_ implications.
So, we could change
Divideto not do any telemetry at all, and just return the result or the error:```c# Divide(int x, int y);
Either
We now have a _declarative_ and _pure_ function.
So, what happens with the
Exception, when does it get logged?Let's imagine we had a small API that was _declarative_ and _pure_ like the
Dividefunction:```c#
public class Error : NewType
{
public Error(string value) : base(value) { }
public static Error FromEx(Exception e) => new Error(e.Message);
}
Either Div(int x, int y) =>
Try(() => x / y).ToEither(Error.FromEx);
Either FromString(string value) =>
parseInt(value).ToEither(Error.New("String can't be parsed into an integer"));
Either Div(string left, string right) =>
from l in FromString(left)
from r in FromString(right)
from n in Div(l, r)
select n;
Either Add(string left, string right) =>
from l in FromString(left)
from r in FromString(right)
select l + r;
Either Sub(string left, string right) =>
from l in FromString(left)
from r in FromString(right)
select l - r;
Either Mul(string left, string right) =>
from l in FromString(left)
from r in FromString(right)
select l * r;
Either Foo() =>
from x in Div("48", "2")
from y in Add("10", "12")
from z in Sub("5", "3")
select x + y + z;
Then we'll add a couple of extension methods that work with ToReader(this Try ma) => env =>
Reader<World, A>. Writing bespoke extensions for your environment type tends to make it easier to work with. These will convert betweenTry<A>,Option<A>toReader<World, A>:```c#
public static Reader
ma.Match(
Succ: x => (x, false),
Fail: ex =>
{
env.SendTelemetry(ex.ToString());
return (default(A), true);
});
It's just a
delegatethat takes anEnvand returns(A, bool)theboolindicates whether it's faulted or not.Before we use the SendTelemetry(string message) =>()
Readerin anger, we can add a helper function to send random telemetry whenever we want:```c#
public static Reader
from world in ask
let _ = world.SendTelemetry(message)
select _;
Notice the calls to
SendTelemetry.Writer monad
Obviously that still calls the telemetry during the computation. So, another approach is to use a
Writer<Exception, A>monad to collect telemetry and then dump it all in one go when you're done. Instead of usingaskto get the environment, you calltell(exception)to log your output.Roll your own
Another approach is to build your own monadic type. That might be a bit out of your reach at the moment if you're just getting started, but once you know how to write a
Bindfunction the rest is trivial.For example if we create a lightweight
Maybemonad (which works like theOptionmonad):```c#
public class Maybe
{
public static Maybe Just(A value) => new Just(value);
public readonly static Maybe Nothing = new Nothing();
}
It encapsulates the rules of the monad (i.e. don't run anything else if we get a
Nothing).Once the
Bindis written, the extensions to make it work with LINQ is almost cut n paste:```c#
public static Maybe Select(this Maybe ma, Func f) =>
ma.Bind(x => Maybe.Just(f(x)));
``
Just changeMaybe` to the name of your monad and it will work for anything.The point of this is that you can relatively easily roll your own monadic types, which carry injected functionality and are less cumbersome than
ReaderorWriter; and easily allow you to change what you do with IO or any external dependencies.Free monad
And the final, most pure method, is the Free monad approach. This creates true separation between the pure and IO code. I've written about this in depth before and there are two samples in the
Samplesfolders that use this approach.It is very powerful, but requires a reasonable amount of typing, however it's a reeeeally powerful technique.