Lazy Monad

System.Lazy<T> is a value that is computed the first time it is asked for and remembered after that. Funcky does not add a lazy type; it adds the monad operations to the one the BCL already has, so lazy values can be combined without forcing them.

Creating lazy values

Lazy<T> Lazy.FromFunc<T>(Func<T> valueFactory)
Lazy<T> Lazy.Return<T>(T value)

FromFunc is new Lazy<T>(factory) with type inference. Return wraps a value that is already known, which is what you need at the boundary where an eager value meets a lazy computation.

Combining lazy values

Lazy<TResult> Select<T, TResult>(this Lazy<T> lazy, Func<T, TResult> selector)
Lazy<TResult> SelectMany<T, TResult>(this Lazy<T> lazy, Func<T, Lazy<TResult>> selector)
Lazy<TResult> SelectMany<T, TLazy, TResult>(this Lazy<T> lazy, Func<T, Lazy<TLazy>> selector, Func<T, TLazy, TResult> resultSelector)
Lazy<T> Flatten<T>(this Lazy<Lazy<T>> lazy)

Without these, combining two lazy values means reading .Value on both, which computes them, and wrapping the result in a new Lazy, which defeats the purpose. With them, the combination is itself lazy, and nothing is computed until someone reads the final .Value:

var config = Lazy.FromFunc(() => LoadConfiguration());         // expensive, maybe never needed
var connection = Lazy.FromFunc(() => OpenConnection());

var repository =
    from c in config
    from conn in connection
    select new Repository(c, conn);

// Nothing has been loaded or opened yet.

repository.Value;   // loads, opens, constructs; subsequent reads reuse the result

Only the lazy values that the final computation actually touches are evaluated. If selector in a SelectMany never reads its argument, the inner lazy stays unevaluated.

Lazy values over sequences

Sequence and Traverse on IEnumerable<T> turn a sequence of lazy values into a lazy sequence, and the other monads have overloads for Lazy<T> too. Note that the resulting Lazy<IEnumerable<T>> is lazy in two ways: the outer Lazy defers everything, and once forced, the inner sequence still evaluates each element's lazy only as it is enumerated.

Trimming and AOT

Lazy<T> has a constructor that creates T through its parameterless constructor by reflection, so the type parameters of these methods carry DynamicallyAccessedMembers annotations to keep that constructor alive under trimming. You do not have to do anything, but it explains the attribute in the signatures.