louthy / language-ext

C# functional language extensions - a base class library for functional programming
MIT License
6.38k stars 414 forks source link

Idea: Use Source Generators to rewrite LINQ expressions #924

Open louthy opened 2 years ago

louthy commented 2 years ago

This has been going around my head for a while, I'm not sure if it's possible, but want to get it written down just in case.

Problem: LINQ expressions come with lambda allocation and contextual type allocation costs, this means their performance isn't as good as writing imperative code.

Possible solution: Use Source Generators to find LINQ expressions that evaluate to known language-ext monadic types. Replace the LINQ expression with an 'unrolled' version that runs imperatively. It won't remove all lambda usage, but would significantly reduce it.

iamim commented 2 years ago

Maybe new F# 6 resumable code feature could be useful somehow? https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1087-resumable-code.md

bmazzarol commented 2 years ago

@louthy had a think about this the other day as well and have a different idea.

How about instead of a source generator, we create 4 new effect types that have at their heart Expression instead of Func.

This is how it works in my head (does not mean it works, so please be kind if its actually a really dumb idea),

Create the new effect structs,

public readonly struct EffExpr<T>
{
    readonly Expression<Func<Fin<A>>> thunk;
    ...
    public EffExpr(Expression<Func<Fin<A>>> thunk) =>
        this.thunk = thunk;

    /// <summary>
    /// Compiles the effect
    /// </summary>
    [Pure, MethodImpl(Opt.Default)]
    public Eff<A> Compile() =>
        EffMaybe<A>(Thunk.Compile);
    ...
}

public readonly struct EffExpr<RT,T>
{
    readonly Expression<Func<RT,Fin<A>>> thunk;
    ...
}

public readonly struct AffExpr<T>
{
    readonly Expression<Func<ValueTask<Fin<A>>>> thunk;
    ...
}

public readonly struct AffExpr<RT,T>
{
    readonly Expression<Func<RT,ValueTask<Fin<A>>>> thunk;
    ...
}

They can have the same constructors and operations on them as Aff and Eff, but they can only be compiled, not run directly.

We could then implement all the operations (Map, Bind, Filter...) by building expressions.

It has the following benefits,

It has the following negatives,