ploeh / ploeh.github.com

ploeh blog 'source code'
133 stars 323 forks source link

There seems to be a problem in this article (Kleisli composition) #919

Closed youthyJJ closed 1 year ago

youthyJJ commented 1 year ago

In Kleisli composition, the implementation of the Compose method seems to have a problem:

The original code is as follows:

public static Func<T, Monad<T2>> Compose<T, T1, T2>(
    this Func<T, Monad<T1>> action1,
    Func<T1, Monad<T2>> action2)
{
    return x => action1(x).SelectMany(action2);
}

However, this code encounters compilation issues. I modified it to the following to make it compile successfully:

public static Func<T, Monad<T2>> Compose<T, T1, T2>(
    this Func<T, Monad<T1>> action1,
    Func<T1, Monad<T2>> action2)
{
    return x => action1(x).SelectMany(f => f.SelectMany(action2));
}

This modification allowed it to pass compilation.

ploeh commented 1 year ago

However, this code encounters compilation issues.

What is the compilation issue?

youthyJJ commented 1 year ago

First, I created a class (Functor\<T>):

internal class Functor<T>
{
    public readonly T t;
    public Functor(T t) { this.t = t; }
}

Then, I added some extension functions to Functor\<T>:

// Pure
private static Functor<T> Pure<T>(T t) => new Functor<T>(t);
// Select
private static Functor<T2> Select<T1, T2>(
    this Functor<T1> functor,
    Func<T1, T2> f
) => Pure(f(functor.t));

Then I treated Functor\<Functor\<T>> as a Monad\<T> and added some extension functions to it:

// Flatten
private static Functor<T> Flatten<T>(this Functor<Functor<T>> monad) => monad.t;
// SelectMany
private static Functor<T2> SelectMany<T1, T2>(
    this Functor<T1> functor,
    Func<T1, Functor<T2>> f
) => functor.Select(f).Flatten();

Finally, I attempted to implement the Compose method:

private static Func<T, Functor<Functor<T2>>> Compose<T, T1, T2>(
    this Func<T, Functor<Functor<T1>>> action1,
    Func<T1, Functor<Functor<T2>>> action2
) => x => action1(x).SelectMany(action2);

My IDE (Rider) is giving me an error for SelectMany(action2) in the code:

The type arguments for method 'Functor SelectMany<T1,T2>(this Functor, Func<T1,Functor>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

I had to change the code to the following form to avoid the error:

private static Func<T, Functor<Functor<T2>>> Compose<T, T1, T2>(
    this Func<T, Functor<Functor<T1>>> action1,
    Func<T1, Functor<Functor<T2>>> action2
) => x => action1(x) .SelectMany(f => f.SelectMany(action2) );
ploeh commented 1 year ago
private static Func<T, Functor<Functor<T2>>> Compose<T, T1, T2>(
    this Func<T, Functor<Functor<T1>>> action1,
    Func<T1, Functor<Functor<T2>>> action2
) => x => action1(x).SelectMany(action2);

You have nested functors there...

youthyJJ commented 1 year ago

Oh! I see, I misunderstood!

After reading Flatting, I mistakenly thought that Monad is just nested Functor, so I used Functor<Functor<T>> as Monad<T,> which caused this issue.

I apologize for the inconvenience.

youthyJJ commented 1 year ago

By the way, I feel that reading C# extension methods can be a bit cumbersome. I tried using Kotlin to express it, and writing example code with it is not only intuitive but also very concise 🤩 !

data class Monad<T>(val t: T)

fun <T> `return`(t: T) = Monad(t)
fun <T> Monad<T>.flatten() = t
fun <A, B> Monad<A>.flatMap(f: (A) -> Monad<B>): Monad<B> = f(flatten())
fun <A, B> Monad<A>.apply(f: Monad<(A) -> B>): Monad<B> = f.flatMap { `return`(it(t)) }
fun <A, B> Monad<A>.fmap(f: (A) -> B): Monad<B> = apply(`return`(f))

fun <T, T1, T2> ((T) -> Monad<T1>).compose(
    f: (T1) -> Monad<T2>
): (T) -> Monad<T2> = { x -> this(x).flatMap(f) }