question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How do I use Try<T> with an existing method?

See original GitHub issue

Hello,

I’m still learning how to use this amazing library (and FP as well, so please go easy on me!), and am a bit confused as to how I use Try<T>. I have a very OO/imperative code base, and am trying to introduce FP where I can.

For example, I have a sequence of statements like this…

ticket.FseTravelType = GetAnswer(PrimaryTravelTypeQuestionID);

Doesn’t really matter what this does, the main point is that is returns as string, but could throw an exception. Without FP, I would presumably have to do something like this…

try { ticket.FseTravelType = GetAnswer(PrimaryTravelTypeQuestionID); } catch (Exception ex) { Log.Error(ex.Message); }

However, given that I have quite a few very similar lines, I would like to cut down the boilerplate code. Try<T> looks like the answer, as (I think) should be able to write something like this…

GetAnswer(PrimaryTravelTypeQuestionID).Match( s => ticket.FseTravelType = s, e => Log.Error(e));

However, I can’t work out how to do this. I looked at the unit tests, but couldn’t work out how to translate what I saw there into this.

Anyone able to help me out here? Sorry if this is a dumb question, but as I said, I’m fairly new to FP, so am still a bit vague about how it works.

Thanks

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:2
  • Comments:15 (14 by maintainers)

github_iconTop GitHub Comments

17reactions
louthycommented, Oct 22, 2018

Anyone able to help me out here? Sorry if this is a dumb question, but as I said, I’m fairly new to FP, so am still a bit vague about how it works.

One thing I’d suggest is trying to stick to a few golden rules:

  • All functions should return a value (even if it’s unit)
  • Try to reduce side-effects as much as possible and write pure functions
    • This means:
      • Only depending on the arguments passed to a function
      • The only output of a function should be it’s return type
  • Write declaratively

Composition is the name of the game. By writing composable functions that have no side-effects you make it easier to create higher level abstractions. The example you gave above may be using types from this function library, but you’re doing it in a very imperative way.

So, this:

Try(() => GetAnswer(PrimaryTravelTypeQuestionID))
    .Match(
        s => ticket.FseTravelType = s,
        e => Log.Error(e));

Has:

  • External dependencies (Log.Error(e))
  • Causes side-effects
    • Log.Error(e) affects the world
    • GetAnswer(PrimaryTravelTypeQuestionID) presumably is talking to a database or something like that? Either way, it can’t get an answer from an ID without relying on some global state.

Now, it’s obviously not always possible to not cause side-effects, especailly using C# as a language. But we can improve the situation by:

  • Moving IO to the edges. This means do your IO first and last, and have pure code in-between. This at least silos the dangerous stuff, and makes it easier for us to build higher abstractions.
  • Using monadic types to abstract away from the messy details

GetAnswer is a good example of something we can improve with a bit of cunning:

Imagine this:

    static string log = "";
    static Map<int, string> answers;

    string GetAnswer(int id)
    {
        var answer = answers[id];
        log = log + $"Got answer {id}";
        return answer;
    }

When we write it like this, it becomes really clear what the external dependencies are. So, how do we improve the situation? Well, we want the function to only depend on it’s arguments, not some global state:

    (string answer, string log)  GetAnswer(int id, Map<int, string> answers, string log)
    {
        var answer = answers[id];
        log = log + $"Got answer {id}";
        return (answer, log);
    }

So, now we’re passing in all the possible answers, and returning the chosen answer and the updated log.

But, we still have some problems (apart from lots of argument typing). And it’s this:

    log = log + $"Got answer {id}";

Why should this function know anything about how to log? the + operation is the mechanics of logging, we shouldn’t know about it. And, wow many functions are we going to have to write this in? What happens when we change our minds? Could this function die if a log file gets full? Are there any performance impacts to this?

All of these things makes the manually written log code bad.

So, let’s change it slightly:

    (string answer, string log)  GetAnswer(int id, Map<int, string> answers)
    {
        var answer = answers[id];
        return (answer,  $"Got answer {id}");
    }

OK! That’s more like it, we don’t have a dependency on an external log, we’re not manually concatenating to a log, and we have very precise performance characteristics.

But let’s have add some other functions:

    (string text, string log)  GetQuestion(int id, Map<int, string> questions)
    {
        return (questions[id],  $"Got question {id}");
    }

    (string text, string log)  AskPerson(int id, Map<int, string> person, string question)
    {
        return ($"Hello {person[id]}, {question}",  $"Asked person {id}");
    }

The problem is now we have lots of separate log values a poor composition story:

    var (question, log1) = GetQuestion(1, questions);
    var (answer, log2) = GetAnswer(1, answers);

A composed solution would look like this:

    var (text, logN) = AskPerson(42, people, GetQuestion(1, questions));

But clearly we’re not there, as the return type from GetQuestion gets in the way and we still have no way to join the log together that doesn’t involve the code doing it manually.

Enter function composition:

static Func<A, (C, string Log)> Compose<A, B, C>(Func<A, (B Value, string Log)> f, Func<B, (C Value, string Log)> g) => 
    a =>
    {
        var (valueA, logA) = f(a);
        var (valueB, logB) = g(valueA);

        return (valueB, logA + logB);
    };

This may look a bit crazy at first, but it takes two arguments, both functions. One from A -> (B, string) the other to B -> (C, string).

Notice how it concatenates the logA and logB for us?

If we slightly rewrite our original functions, we can inject the global state also:

static Func<int, (string text, string log)> GetQuestion(Map<int, string> questions) => 
    id => 
        (questions[id], $"Got question {id}");

static Func<int, Func<string, (string text, string log)>> AskPerson(Map<int, string> people) => 
    id => 
        text => ($"Hello {people[id]}, {text}", $"Asked person {id}");

Now if I call:

    var getQuestion = GetQuestion(questionsDb);
    var askPerson = AskPerson(peopleDb);

The getQuestion is function that takes an int and returns a (string, string). It’s captured the questionsDb. And askPerson is a function that takes an int and returns a string -> (string, string).

That means we can all askPerson with a person ID to get a string -> (string, string). If we Compose those functions we get:

var questioner = Compose(getQuestion, askPerson(1));

This gives us a Func that calls GetQuestion and AskPerson and also logs everything without us having to know how the log works.

This is a full example of it:

Map<int, string> questionsDb = Map(
    (1, "What's the meaning of life?"), 
    (2, "Is there anybody out there?"));

Map<int, string> peopleDb = Map(
    (1, "Mr Yossu"),
    (2, "Louthy"));

var getQuestion = GetQuestion(questionsDb);
var askPerson = AskPerson(peopleDb);

var questioner = Compose(getQuestion, askPerson(1));

// ("Hello Mr Yossu, What's the meaning of life?", "Got question 1\nAsked person 1")
var result1 = questioner(1);

// ("Hello Mr Yossu, Is there anybody out there?", "Got question 2\nAsked person 1")
var result2 = questioner(2);

Another aspect to this is that the string concatenation is actually monodial.

What does monoidal mean? A monoid has two properties:

  • It has a unit value. Often called Empty
  • It must have an associative binary operator. It is usually called Append.

Now, language-ext supports monoids (although C# fights us). And it already has one for string called TString, other monoids are MSeq, MLst, MSet, and so we can do some more cunning stuff. Let’s update our Compose function:

static Func<A, (C, W Log)> Compose<MonoidW, W, A, B, C>(Func<A, (B Value, W Log)> f, Func<B, (C Value, W Log)> g)
    where MonoidW : struct, Monoid<W> =>
        a =>
        {
            var (valueA, logA) = f(a);
            var (valueB, logB) = g(valueA);

            return (valueB, default(MonoidW).Append(logA, logB));
        };
    }

Notice how the resulting Log is now of type W, and there’s another generic argument called MonoidW, which is constrained to be struct, Monoid<W>.

The String.Join has also gone, and is now replaced with:

    default(MonoidW).Append(logA, logB);

So, now we can inject any logging mechanism we like into the composed behaviour:

var questioner = Compose<MSeq<string>, Seq<string>, int, string, string>(
    getQuestion, 
    askPerson(1)
    );

This will log using Seq<string>.

Now, all this may seem like a lot of work to create some logging abstractions. But what we’ve actually created here is a monad.

The Writer monad to be exact. Which already exists in language-ext. And what you use the Writer monad you get all this stuff for free.

The general idea of performing ‘hidden’ operations (like the log append) between functions is what monads are all about. So, if you’re looking to learn how to do the functional thing well, then it’s worth understanding everything above.

So, with the Writer monad you get this:

static Writer<MSeq<string>, Seq<string>, string> GetQuestion(int id, Map<int, string> questions) =>
    from q in Return(questions[id])
    from _ in Log($"Got question {id}")
    select q;

static Writer<MSeq<string>, Seq<string>, string> AskPerson(int id, Map<int, string> people, string text) =>
    from p in Return(people[id])
    from _ in Log($"Gog question {id}")
    select $"Hello {p}, {text}";

I used a couple of helper functions:

static Writer<MSeq<string>, Seq<string>, A> ReturnW<A>(A value) =>
    Writer<MSeq<string>, Seq<string>, A>(value);

static Writer<MSeq<string>, Seq<string>, Unit> LogW(string message) =>
    tell<MSeq<string>, Seq<string>>(Seq1(message));

Writer is the constructor and tell is the function that logs to the monoid.

And now the composed version looks like this:

var question = from q in GetQuestion(1, questionsDb)
               from t in AskPerson(2, peopleDb, q)
               select t;

Personally I love the way that LINQ (which is the C# support for monads) can make everything so declarative.

But now we have a new problem. And that is that questionsDb and peopleDb are directly involved again. And that is because there is no state being inputted to the composed functions.

If you remember our original functions how they returned a value and a log (some state), but they dropped the incoming state other that the arguments. Well the State monad deals with that for us…

First, let’s create a type that encapsulates the world as it is before we call any of our code. Imagine taking a snapshot of the state of your program. In reality you only need to capture the things that your code will need, but it’s a useful idea to keep in mind:

class World
{
    public readonly Map<int, string> Questions;
    public readonly Map<int, string> People;
    public readonly Seq<string> Output;

    public World(Map<int, string> questions, Map<int, string> people, Seq<string> output)
    {
        Questions = questions;
        People = people;
        Output = output;
    }

    public string GetQuestion(int id) =>
        Questions[id];

    public string GetPerson(int id) =>
        People[id];

    public World Log(string text) =>
        new World(Questions, People, Output.Add(text));
}

So, this represents all the stuff that is usauall being called directly by things like Log.Error or ticket.FseTravelType = GetAnswer(PrimaryTravelTypeQuestionID)

We can now create some reusable functions that work with our World type:

static State<World, Unit> Log(string log) =>
    from s in get<World>()
    from _ in put(s.Log(log))
    select unit;

static State<World, string> GetQuestion(int id) =>
    from s in get<World>()
    from _ in Log($"Got question {id}")
    select s.GetQuestion(id);

static State<World, string> AskPerson(int id, string text) =>
    from s in get<World>()
    let p = s.GetPerson(id)
    from _ in Log($"Got question {id}")
    select $"Hello {p}, {text}";

So, now when I use those functions:

var question = from q in GetQuestion(2)
               from t in AskPerson(1, q)
               select t;

You’ll notice I am not providing questionsDb or peopleDb. It is done like so:

var world = new World(questionsDb, peopleDb, Seq<string>());

var (text, world2, _) = question(world);

Obviously the more you build out of these monads, the less you have to run these invocations to get the results. You compose bigger and bigger chunks into higher levels of abstraction. With all operations being pure.

Composition is the essence of functional programming in my humble opinion. To successfully compose smaller functions into bigger ones, we must be able to trust what’s inside those smaller functions. Otherwise you have non-deterministic and frankly unknowable code. We can get away with this when our applications are small, but not when they grow to multi-million line behemoths.

All of the examples in this comment are here

8reactions
dknowlescommented, Oct 23, 2018

@louthy I just want to say how impressed and grateful I am that you put so much time and effort into answering questions and providing insight into a lot of this stuff. I follow everything with interest even though I’m only lurking for the most part.

Cheers

Read more comments on GitHub >

github_iconTop Results From Across the Web

Embed the existing code of a method in a try-finally block (2)
The solution was to visit a label for the try block at the beginning of the method body in visitCode() and to complete...
Read more >
Java - Try with Resources
A quick and practical guide to how we can use the try-with-resources functionality introduced in Java 7 to auto-close resources and simplify ...
Read more >
Handling Errors by Using Try Methods - Business Central
The main purpose of try methods is to catch errors/exceptions that are thrown by Business Central or exceptions that are thrown during ....
Read more >
The try-with-resources Statement - Exceptions
The try -with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable , which ......
Read more >
Error handling, "try...catch"
So, try...catch can only handle errors that occur in valid code. Such errors are called “runtime errors” or, sometimes, “exceptions”.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found