DIVEX

Replace operators

The Replace operator allows you to inject a function as a value provider for a parameter of another function. The parameter is “replaced” with the parameters of the injected function.

DelFunc.DF1 log = (DateTime time, string message) =>
    Console.WriteLine($"At {time}: {message}");

DelFunc.DF2 getTime = () => DateTime.Now;

var logNow = log.Replace(time: getTime);

logNow.Invoke(message: "hello");
DelFunc.DF1 and DelFunc.DF2 are Delegate Functions. Delegate Functions is a way to support treating lambdas as functions.

In the above example, the getTime function was injected as a value provider for the time parameter of the log function using the Replace operator. The function returned by Replace, which is logNow, has a single parameter message. Whenever the logNow function is invoked, it will call getTime first to obtain the time and then call log.

Now consider this updated example:

DelFunc.DF1 log = (DateTime time, string message) =>
    Console.WriteLine($"At {time}: {message}");

DelFunc.DF2 getTime = (bool isUtc) =>
    isUtc ? DateTime.UtcNow : DateTime.Now;

var logNow = log.Replace(time: getTime);

logNow.Invoke(message: "hello", isUtc: true);

In this updated example, getTime has a parameter called isUtc. This parameter determines whether getTime returns local time or UTC time. Because of this, the logNow function now has a isUtc parameter. So, the isUtc parameter was bubbled up.

The ReplaceOne and ReplaceLast operators are similar to Replace, but they work when the parameter to replace is an array (or an ImmutableArray<T>):

[DivexCompose]
public static partial class Program
{
    public record Document(string Content, string Name);

    public interface IDocumentProcessor
    {
        void Process(Document document);
    }

    public class CompositeProcessor : IDocumentProcessor
    {
        private readonly IDocumentProcessor[] processors;

        public CompositeProcessor(IDocumentProcessor[] processors)
        {
            this.processors = processors;
        }

        public void Process(Document document)
        {
            foreach (var processor in processors)
                processor.Process(document);
        }
    }

    public class DocumentConsoleWriter : IDocumentProcessor
    {
        public void Process(Document document)
                => Console.WriteLine(document.Content);
    }

    public class DocumentToWebPoster : IDocumentProcessor
    {
        private readonly HttpClient httpClient;
        private readonly Uri uri;

        public DocumentToWebPoster(HttpClient httpClient, Uri uri)
        {
            this.httpClient = httpClient;
            this.uri = uri;
        }

        public void Process(Document document)
        {
            httpClient.PostAsync(
                uri,
                new StringContent(document.Content))
                .Result.EnsureSuccessStatusCode();
        }
    }

    static void Main(string[] args)
    {

        var createProcessor =
                CtorOf<CompositeProcessor>()
                    .ReplaceOne(processors: CtorOf<DocumentConsoleWriter>())
                    .ReplaceLast(processors: CtorOf<DocumentToWebPoster>());

        var processor = createProcessor.Invoke(
            httpClient: new HttpClient(),
            uri: new Uri("https://test.lab/document"));

    }
}

In the example above, the CompositeProcessor class takes an array of IDocumentProcessor. We can inject as many document processors into this array using ReplaceOne and ReplaceLast. ReplaceOne allows us to replace a single element of the array. The function returned by ReplaceOne still has the dependency parameter (processors). This enables us to use ReplaceOne as many times as we want. The last document processor to inject into the processors parameter is injected via the ReplaceLast operator. This operator is like ReplaceOne except that the resulting function no longer contains the processors parameter.