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.

Functional approach to try-catch-finally

See original GitHub issue

Hello,

I find my way into functional programming and love to see, that I can get started without leaving C# behind. Thanks for the great work! But I cannot get my head around on how to implement a try-catch-finally behaviour in a FP style.

I have to following 3 functions to work with and every single one can throw an exception…

  int AllocatateResource(int id)
  void UseResource(int resId)
  void DeleteResource(int resId)

With try catch finally it would look like this (it is important, that the finally block can exit with an exception):

int resId;
try
{
    resId = AllocatateResource(1);
    UseResource(resId);
}
finally
{
    if(resId != 0)
    {
        DeleteResource(resId);
    }
}

This is a real mess in my opinion so I changed those methods to the following:

  Fin<int> AllocatateResource(int id)
  Fin<Unit> UseResource(int resId)
  Fin<Unit> DeleteResource(int resId)

Now, I can do this, which looks pretty good and is easier to understand.

return from resId in AllocatateResource(1)
           from unit in UseResource(resId)
           from _ in DeleteResource(resId)
           select _;

But if UseResource returns an Error, there will be an early out and DeleteResource is not called anymore. If I have to use Map and Match Functions to handle this, it will get more complicated again. I tried to use the Prelude.use function and wrap the allocate and delete function in an IDisposable, but in that case the possible Error from DeleteResource cannot be accessed anymore. So my best working state is this:

return from resId in AllocatateResource(1)
       from _ in UseResource(resId).Match(
           Succ: unit => DeleteResource(resId),
           Fail: err => DeleteResource(resId).Match(
                Succ: unit => err,
                Fail: error => error))     // Override the first error with the second one - same as with a throwing finally block in case of an "active" exception
       select _;

The Fail part in the first match clause is my main issue, because I need the Error from the UseResource call and not the success state of the DeleteResource call. In addition I dont like the two calls to DeleteResource. Is there any smarter/better way of calling the DeleteResource method if AllocatateResource was sucessful and keep the error of the UseResource call?

Thanks for any help 😃

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
louthycommented, Sep 1, 2022

I thought it would be beneficial to hide the exceptions inside those methods, thats why I changed the return type to Fin and work with the Error struct from there. Is this assumtion flawed?

No, but the way to think about the monadic types, like Fin, Either, Eff, etc. is that they all have a set of built-in capabilities, which make them what they are. Fin has the ‘alternative value’ capability, where the alternative value is an Error, but that’s all it does. Try has the ‘alternative value’ capability where the alternative value is an Exception, but it also has the ‘catch exceptions’ capability,

The trick for any part of your application is to use the monad with the smallest set of capabilities for the subdomain you’re working on. When you need to expand those you pick a ‘bigger’ monad with more built-in capabilities.

Eff for example has:

  • Alternative values (Error)
  • Try/Catch
  • Laziness
  • Pairs with Aff to support asynchrony
  • Has a declarative runtime version (Eff<RT, A> for dependency injection)

It’s our IO monad. It should be at the outer edges of your application.

You can think of the smallest capability being entirely pure functions. That’s the ‘inner’ layer. Then maybe Option, then Try, then Eff etc. Like an onion of expanding capability.

The example with the Effect monad confused me, because I cannot find any use Function for Eff or Aff, that does not take a function as second parameter

Effect and Eff are not the same. It’s an unfortunate naming conflict that I’m not super happy about, but it’s there now. Effect is part of the Pipes functionality (a composed Producer, Pipe, and Consumer fuse together into an Effect); it’s probably not appropriate for what you’re doing.

Eff (and it’s asynchronous variant Aff) are the monads for doing IO side-effects, and has lots of built-in stuff for dealing with IO (like @catch, scheduled retries, repeats, etc.)

Eff and Aff will eventually gain the capability to track resources automatically, but it’s not there today, so you need to use the use function with the second function argument to wrap up usage of the resource.

I tried the solution with the Resource.use function, but it also hides errors, that occur in the Dispose call

You can remove the catch part so exceptions are exposed normally?

1reaction
louthycommented, Aug 31, 2022

You’re using Fin<A> here, and Fin<A> is like an Either<Error, A>. What neither Fin, nor Either, do is catch exceptions. That means there no automatic way to clean up resources. And so, you have a couple of options:

  1. Use a different monad which does resource tracking and clean-up
  2. Build a simple function to do the work for you

The only monad that does 1 right now is Effect - which I wouldn’t advise using for this. So, you can write a function called use:

public static class Resource
{
    public static Fin<B> use<A, B>(Fin<A> resource, Func<A, Fin<B>> op)
        where A : IDisposable
    {
        try
        {
            return resource.Bind(op);
        }
        catch (Exception e)
        {
            return Fin<B>.Fail(e);
        }
        finally
        {
            resource.Iter(r => r?.Dispose());   
        }
    }
}

Then you can do this:

Resource.use(AllocatateResource(1), 
    resId => from unit in UseResource(resId)
             from _ in DeleteResource(resId)
             select _);

With the Effect monad it’s possible to just do this:

    from resId in use(AllocatateResource(1))
    from unit in UseResource(resId)
    select _);

It tracks the resources and cleans them up automatically. I am actually working on bringing resource tracking to every monadic type (for version 5), but for now it’s a case of wrapping up the sub-expression with your own function.

For some monadic types there are some use functions already in the Prelude, just not for the value-type monads like Fin, Option, and Either.

One other thing, seeing as it looks like you’re doing IO side-effects, I’d recommend using Eff and Aff over Fin. They also have the use functions built into the Prelude - and also have the ability to catch errors, which means you won’t have to write them yourself.

Read more comments on GitHub >

github_iconTop Results From Across the Web

c# - Functional exception handling with TryCatchFinally ...
I wrote something to the effect of a try catch finally statement helper in functional style so that I can shorten much of...
Read more >
TryCatchFinally (AWS SDK for Java - 1.12.528)
Task (or method annotated with Asynchronous ) that has not started execution is never given chance to execute after cancellation. Task which is...
Read more >
Flow control in try catch finally in Java
In this article, we'll explore all the possible combinations of try-catch-finally which may happen whenever an exception is raised and how ...
Read more >
Basic try-catch-finally Exception Handling in Java
This tutorial explains how the basic try-catch-finally exception handling mechanisms work in Java.
Read more >
Try...Catch...Finally statement - Visual Basic
If a matching Catch statement is not found, Visual Basic examines the method that called the current method, and so on up the...
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