<p><a href="https://www.youtube.com/watch?v=2PxsyWqZ5dI" rel="nofollow noreferrer">The inspiration for this question</a></p>
<p>There are many advantages of using monadic types like <code>Promise&lt;T&gt;</code> or <code>Result&lt;T&gt;</code> as function return types. Both promises and results are very elegant, explicit, and convenient approaches to their respective problems.</p>
<p>However, they create one issue. Each function that uses these types will need to wrap all their types in this monad. So you will end up with functions like:</p>
<pre class="lang-rs prettyprint-override"><code>fn f() -&gt; Cancellable&lt;Promise&lt;Result&lt;T&gt;&gt;&gt; {
}
</code></pre>
<p>Perhaps with even more monads wrapped around it. Then every function that calls this function will also need to alter it's return type with to wrap it in all these monads all the way up. This has many disadvantages:</p>
<ul>
<li>Less clarity in function declarations. You will create a super long expression instead of just the type you want to return</li>
<li>Needing to use some kind of unwrapping operator every time you use the return value of these functions. This complicates every function that needs to call a function that needs monad effects.</li>
<li>Whenever you need to change a function to add some monad you need to change every function that calls it which makes some changes very difficult.</li>
</ul>
<p>Are there any approaches to gaining both the benefits of monads when you want them while allowing them to pass with little effort past functions that don't care about their inner workings? What languages use these approaches? What are their respective advantages and disadvantages?</p>

<language-comparison><async><monads>
Can I use monadic types without needing colored functions?
CC BY-SA 4.0
2023-07-05T21:07:08.217Z
I think it’s hard to answer this question at present because it’s not clear what the “benefits of monads” you’re referring to are. Monads are a pretty ridiculously general concept; they show up everywhere whether you choose to call them that or not. But I wouldn’t say that monads really provide benefits *per se*.
Alexis King
CC BY-SA 4.0
2023-07-06T04:38:23.910Z
@AlexisKing The benefits of results and promises are so different that it's hard to find anything that really ties them together
mousetail 'he-him'
<p>Type aliases:</p>
<pre class="lang-typescript prettyprint-override"><code>type MaybeLater&lt;T&gt; = Cancellable&lt;Promise&lt;Result&lt;T&gt;&gt;&gt;
</code></pre>
<p>Generally, if the problem is &quot;there are some pieces of code that are long/noisy that I have to type over and over again&quot;, the easy solution is to let the user declare a shorthand for it.</p>
<p>This doesn't solve <em>all</em> of the issues you raise, but it is a very cheap addition to the language.</p>

CC BY-SA 4.0
2023-07-06T13:46:02.137Z
2023-07-06T13:46:02.137Z
kaya3
<h2>Combining instead of Nesting</h2>
<p>To handle a similar problem, I'm implementing an effect composition mechanism for my language.</p>
<p>For such nested type functions, I envision a composite equivalent type:</p>
<pre class="lang-rust prettyprint-override"><code>fn async_call&lt;T, U&gt;(in: Future&lt;T&gt;) -&gt; Cancellable&lt;Future&lt;Result&lt;U, Error&gt;&gt;&gt; { }
</code></pre>
<p>I envision a composite equivalent type:</p>
<pre class="lang-scala prettyprint-override"><code>def call_effect&lt;T, U&gt;(): U / [Request&lt;T&gt;, MissingNameError, Cancel] { }
</code></pre>
<p>The return value is divided into <code>-&gt; L/R</code> two parts, the right part can be freely arranged and combined</p>
<p>According to your needs, for the same Error, if you write Error in the Result on the left, it will be nested, and if you write it on the right, it will be combined.</p>
<p>Use <code>match</code> for the left type and <code>catch</code> for the right type.</p>
<p>The specific usage is as follows:</p>
<pre class="lang-scala prettyprint-override"><code>struct Data { name: Option&lt;String&gt; }
effect Request() -&gt; Data;
effect Cancel() -&gt; bool;
effect MissingNameError() -&gt; !; // prohibition to resume

def call_effect&lt;T, U&gt;() -&gt; U / [Request&lt;T&gt;, MissingNameError, Cancel] {
    let cancel = raise Cancel()
    while !cancel {
        let data = raise Request();
        if data.name.is_none() {
            raise MissingNameError();
        }
        else {
            print(&quot;name is: { data.name }&quot;);
        }
        if random::&lt;bool&gt; {
            cancel = true;
            return U::default()
        }
    }
}
</code></pre>
<p>This part only needs to write the function logic and declare the various elements you need.</p>
<p>The specific processing method is determined by the caller. The caller can process all of them, or none of them, but if not processed, the continuation will be inherited.</p>
<pre class="lang-scala prettyprint-override"><code>def call_env&lt;T&gt;() -&gt; U / [MissingNameError] {
    // Variables that can be modified by other threads
    let mut busy = false;
    // Define the filling scope of the handler
    try {
        effect_call();
        effect_call();
    }
    // Select the effects that need to be filled
    catch {
        case Request() =&gt; {
            delay(1000) { 
                resume T::default() 
            }
        }
        case Cancel() =&gt; {
            resume busy
        }
        // unfilled effects will result in additional continuation parameters
    }
    match { }
}

try { call_env() }
catch {
    case MissingNameError =&gt; {
        panic(&quot;MissingNameError&quot;)
    }
}
</code></pre>
<p>It can be found that this is more flexible, and does not need to be processed one by one in the reverse order of nesting.</p>
<h2>What is the magic?</h2>
<p>Essentially this is a CPS transformation.</p>
<p>In my language, all functions have no return value at the HIR level.</p>
<p>After analysis, it will first be transformed into the following form:</p>
<pre class="lang-scala prettyprint-override"><code>def call_cps&lt;T, U&gt;(
    return: Continuation&lt;U&gt;, 
    error: Continuation&lt;MissingNameError&gt;,
    cancel: Continuation&lt;bool&gt;,
    request: Continuation&lt;T&gt;,
)
{
    // mutable reference, but not mutable variable
    let cancel = cancel()
    loop {
        if !cancel {
            break
        }
        let data = request();
        if data.name.is_none() {
            error();
        }
        else {
            print(&quot;name is: { data.name }&quot;);
        }
        if random::&lt;bool&gt; {
            cancel = true;
            return U::default()
        }
    }
}
</code></pre>
<p>The caller then optionally processes the neutralized continuation parameters:</p>
<pre class="lang-scala prettyprint-override"><code>// Wait for higher layers to handle this error
def call_env&lt;T, U&gt;(return: Continuation&lt;U&gt;, error: Continuation&lt;MissingNameError&gt;) {
    let mut busy = false;
    call_cps(
        return: return,
        request: {
            delay(1000) { 
                T::default() 
            }
        }
        calcel: {
            @reference(busy)
        }
        error: error
    )
    // another same call
    call_cps(...)
}
</code></pre>
<p>It can be found that the source of composition comes from the continuation as a parameter, and the order can be changed arbitrarily.</p>

CC BY-SA 4.0
2023-07-06T02:29:26.173Z
2023-07-06T02:38:34.017Z
2023-07-06T02:38:34.017Z
Aster
CC BY-SA 4.0
2023-11-21T16:27:14.953Z
This presentation is essentially the [mother-of-all-monads](http://blog.sigfpe.com/2008/12/mother-of-all-monads.html) approach.
Corbin
# Can I use monadic types without needing colored functions?
Posted by: username_0 on 2023-07-05
The inspiration for this question
There are many advantages of using monadic types like Promise or Result as function return types. Both promises and results are very elegant, explicit, and convenient approaches to their respective problems.
However, they create one issue. Each function that uses these types will need to wrap all their types in this monad. So you will end up with functions like:
fn f() -> Cancellable>> {
}

Perhaps with even more monads wrapped around it. Then every function that calls this function will also need to alter it's return type with to wrap it in all these monads all the way up. This has many disadvantages:

Less clarity in function declarations. You will create a super long expression instead of just the type you want to return
Needing to use some kind of unwrapping operator every time you use the return value of these functions. This complicates every function that needs to call a function that needs monad effects.
Whenever you need to change a function to add some monad you need to change every function that calls it which makes some changes very difficult.

Are there any approaches to gaining both the benefits of monads when you want them while allowing them to pass with little effort past functions that don't care about their inner workings? What languages use these approaches? What are their respective advantages and disadvantages?
**Tags:** language-comparison async monads
> **Comments:**
> **username_1**: I think it’s hard to answer this question at present because it’s not clear what the “benefits of monads” you’re referring to are. Monads are a pretty ridiculously general concept; they show up everywhere whether you choose to call them that or not. But I wouldn’t say that monads really provide benefits *per se*.  
> **username_0**: @AlexisKing The benefits of results and promises are so different that it's hard to find anything that really ties them together  
---
## 2 answers
### Answer by username_3 on 2023-07-06. Score: 3
Combining instead of Nesting
To handle a similar problem, I'm implementing an effect composition mechanism for my language.
For such nested type functions, I envision a composite equivalent type:
fn async_call(in: Future) -> Cancellable>> { }

I envision a composite equivalent type:
def call_effect(): U / [Request, MissingNameError, Cancel] { }

The return value is divided into -> L/R two parts, the right part can be freely arranged and combined
According to your needs, for the same Error, if you write Error in the Result on the left, it will be nested, and if you write it on the right, it will be combined.
Use match for the left type and catch for the right type.
The specific usage is as follows:
struct Data { name: Option }
effect Request() -> Data;
effect Cancel() -> bool;
effect MissingNameError() -> !; // prohibition to resume

def call_effect() -> U / [Request, MissingNameError, Cancel] {
    let cancel = raise Cancel()
    while !cancel {
        let data = raise Request();
        if data.name.is_none() {
            raise MissingNameError();
        }
        else {
            print("name is: { data.name }");
        }
        if random:: {
            cancel = true;
            return U::default()
        }
    }
}

This part only needs to write the function logic and declare the various elements you need.
The specific processing method is determined by the caller. The caller can process all of them, or none of them, but if not processed, the continuation will be inherited.
def call_env() -> U / [MissingNameError] {
    // Variables that can be modified by other threads
    let mut busy = false;
    // Define the filling scope of the handler
    try {
        effect_call();
        effect_call();
    }
    // Select the effects that need to be filled
    catch {
        case Request() => {
            delay(1000) { 
                resume T::default() 
            }
        }
        case Cancel() => {
            resume busy
        }
        // unfilled effects will result in additional continuation parameters
    }
    match { }
}

try { call_env() }
catch {
    case MissingNameError => {
        panic("MissingNameError")
    }
}

It can be found that this is more flexible, and does not need to be processed one by one in the reverse order of nesting.
What is the magic?
Essentially this is a CPS transformation.
In my language, all functions have no return value at the HIR level.
After analysis, it will first be transformed into the following form:
def call_cps(
    return: Continuation, 
    error: Continuation,
    cancel: Continuation,
    request: Continuation,
)
{
    // mutable reference, but not mutable variable
    let cancel = cancel()
    loop {
        if !cancel {
            break
        }
        let data = request();
        if data.name.is_none() {
            error();
        }
        else {
            print("name is: { data.name }");
        }
        if random:: {
            cancel = true;
            return U::default()
        }
    }
}

The caller then optionally processes the neutralized continuation parameters:
// Wait for higher layers to handle this error
def call_env(return: Continuation, error: Continuation) {
    let mut busy = false;
    call_cps(
        return: return,
        request: {
            delay(1000) { 
                T::default() 
            }
        }
        calcel: {
            @reference(busy)
        }
        error: error
    )
    // another same call
    call_cps(...)
}

It can be found that the source of composition comes from the continuation as a parameter, and the order can be changed arbitrarily.
> **Comments:**
> **username_4** (2023-11-21): This presentation is essentially the [mother-of-all-monads](http://blog.sigfpe.com/2008/12/mother-of-all-monads.html) approach.  
---
### Answer by username_2 on 2023-07-06. Score: 2
Type aliases:
type MaybeLater = Cancellable>>

Generally, if the problem is "there are some pieces of code that are long/noisy that I have to type over and over again", the easy solution is to let the user declare a shorthand for it.
This doesn't solve all of the issues you raise, but it is a very cheap addition to the language.
---
