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.

Ideas to have continuation-styled monads for Option/Either/etc?

See original GitHub issue

Hi. I’ve recently read this article http://blog.paralleluniverse.co/2015/08/07/scoped-continuations/ and I’ve come to agree with its conclusion: In imperative languages monads are better suited to be used and composed using delimited/scoped continuations. In my actual job I’ve introduced the Option and Either monads using LINQ, but we encountered problems (mainly in maintainability and readability). These issues were improved when I started using continuation-styled Options and Eithers.

Do you believe they could be incorporated into this library?

I’ll explain the proposal better:

Imagine you have this code that uses the Option monad in C# with LINQ:

public Option<string> Foo(Option<int> a, Option<int> b)
{
    return 
        from aVal in a
        from bVal in b
        let c = aVal + bVal
        select c.ToString();
}

It seems pretty straight-forward. However, what if, before returning c.ToString(), we actually need to call a procedure Bar() imperatively? It gets harder to do so in a readable way.

What if Options were defined using continuations? (using more pseudo-syntax than valid C#)

public static T Value<T,cps<K,K>>(this Option<T> opt)
{
    return shift { (Func<T, K> k) => opt.Match(
                                         None: None // return type : Option<K>
                                         Some: (T t) => Some(k(t)) // return type : Option<K> 
                                         )  };
}

public static Option<T> OptionHandle<T>(Func<T> f)
{
    return reset { f() };
}

public Option<string> Foo(Option<int> a, Option<int> b)
{
    return OptionHandle(() =>
    {
        var aVal = a.Value();
        var bVal = b.Value();
        var c = aVal + bVal;
        Bar();
        return c.ToString();
    });
}

Everything inside the delimited continuation or OptionHandle will work inside the Option monad, but you are able to use the full power of the imperative language. You maintain a relatively readable stacktrace (e.g if Bar() throws an exception), you can call imperative code from inside of it, and it can create more readable code, specially if you have to compose various different monads together (in my case it was composing both Either and Option in various ways).

C# doesn’t have continuations like the above, but since the continuations are one-shot you can simulate the above pretty well using exceptions:

public class OptionException : Exception {}

public static T Value<T>(this Option<T> opt)
{
    return opt.Match(
               None: () => { throw new OptionException();},
               Some: t => t);
}

public static Option<T> OptionHandle<T>(Func<T> f)
{
    try
    {
        return f();
    } catch (OptionException)
    {
        return None;
    }
}

Using exceptions and try-catch you get the same behavior.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
seerekcommented, Apr 23, 2019

C# doesn’t have continuations like the above.

Actually it has - it’s called async/await. It requires Option<T> to provide GetAwaiter() method + async generator to support it, but I think something like this should be possible.

public async Option<string> Foo(Option<int> a, Option<int> b)
{
    var c = await a + await b;
    Bar();
    return c.ToString();
}
0reactions
gonzawcommented, Apr 26, 2019

I don’t know how it’d work with other Awaitables that are used in the same method, like Task.

I have done small proof of concept and found the following:

  1. This is ideal (monadic) situation (easy to do):
public async Option<string> Foo(Option<int> a)
{
    var c = await a;
    return c.ToString();
}
  1. This I think should be allowed (but can be easily prevented at compile time)
public async Option<string> Foo(Task<int> a)
{
    var c = await a;
    return c.ToString();
}
  1. But this should be prevented by compiler and I don’t know how.
public async Task<string> Foo(Option<int> a)
{
    var c = await a;
    return c.ToString();
}

I cannot find a way to prevent it at compile time because when a == None the only reasonable thing I found was to throw ValueIsNoneException at runtime. But then, how does it differ from NullPointerException when one of the purposes of Option<T> is to remove it from our life.

What about this case?

public async ??? Foo(Option<int> a, Task<int> b)
{
    var aVal = await a;
    var bVal = await b;
    return aVal + bVal;
}

Which type would be inferred? Which type signature would compile (if it compiles at all)? Task<Option<int>> or Option<Task<int>>?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Basic program ideas for learning about Monads? : r/haskell
Implement the standard library monads ( List, Maybe, Cont, Error, Reader, Writer, State ) for yourself to understand them better.
Read more >
functional programming - Different ways to see a monad
Monad is endofunctor, for wich to special operation are defined - promotion and binding. Monads are numerous - a list monad is perfect...
Read more >
Demystifying the Monad in Scala
It's like wrapping a present; you take a scarf, a good book or a fancy watch, and wrap it with some shiny paper...
Read more >
No Nonsense Monad & Functor - YouTube
For more info on the next Devoxx UK https://www.devoxx.co.uk What the ƒ is a Monad ? If this question has kept you up...
Read more >
Monads in Functional Programming Explained
Monads allow users to simplify structure and control flows in functional programming, helping make code more declarative, type-safe and clear to follow. As ......
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