Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the fluent object model to make this work?

As practice for writing fluent APIs, I thought I'd make the following compile and run:

static void Main(string[] args)
{
    Enumerable.Range(1, 100)
        .When(i => i % 3 == 0).Then(i => Console.WriteLine("fizz"))
        .When(i => i % 5 == 0).Then(i => Console.WriteLine("buzz"))
        .Otherwise(i => Console.WriteLine(i))
        .Run();

    Console.ReadLine();
}

The idea is .When will test each element in the enumeration, and if it passes the predicate, to have the action run. If the predicate fails, the item is passed down the chain.

The graph I came up with was:

public static class EnumerableExtensions
{
    public static IConditionalEnumerable<T> When<T>(this IEnumerable<T> items, Predicate<T> test, Action<T> action)
    {
    }

    public static IResolvedEnumerable<T> Then<T>(this IConditionalEnumerable<T> items, Predicate<T> test, Action<T> action)
    {
    }

    public static void Run<T>(this IEnumerable<T> items)
    {
        foreach (var item in items) ;
    }
}

public interface IConditionalEnumerable<T> : IEnumerable<T>
{
    IResolvedEnumerable<T> Then<T>(IConditionalEnumerable<T> items, Action<T> action);
}

public interface IResolvedEnumerable<T> : IEnumerable<T>
{
    IEnumerable<T> Otherwise(Action<T> behavior);
}

I am running into an issue -- When can't foreach / yield return inside of it, because the return type isn't directly IEnumerable<T> (although it inherits from it). That has thrown a mental wrench into the gears. What would the implementation of the extension methods look like?

like image 939
Bryan Boettcher Avatar asked Oct 12 '17 22:10

Bryan Boettcher


People also ask

What is a fluent object?

In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). The term was coined in 2005 by Eric Evans and Martin Fowler.

What is fluent Page object model?

Page Object model is an object design pattern in Selenium. Web pages are represented as classes, and elements on the page are defined as variables on the class, so user interactions can then be implemented as methods on the class.

What is fluent method?

A fluent interface is an object-oriented API that depends largely on method chaining. The goal of a fluent interface is to reduce code complexity, make the code readable, and create a domain specific language (DSL). It is a type of method chaining in which the context is maintained using a chain.

What is fluent design pattern?

Fluent is an open-source, cross-platform design system that gives designers and developers the frameworks they need to create engaging product experiences—accessibility, internationalization, and performance included.


2 Answers

Don't worry about sequences. Just do it on values.

You can do this entirely with extension methods. Here's the thing:

  • Somehow Otherwise needs to know whether any previous Then executed.
  • Therefore somehow every When needs to know whether every previous Then executed.
  • Every Then needs to know whether the previous When was true or false.

Here's the skeleton:

static class X 
{
    public enum WhenState { DoIt, DoNot }
    public enum ThenState { DidIt, DidNot }
    public static (T, ThenState) Begin<T>(this T item) { ... }
    public static (T, WhenState, ThenState) When<T>(
      this (T item, ThenState then) tuple, 
      Func<T, bool> p) { ... }
    public static (T, ThenState) Then<T>(
      this (T item, WhenState when, ThenState then) tuple, 
      Action<T> a) { ... }
    public static void Otherwise<T>(
      this (T item, ThenState then) tuple, 
      Action<T> a) { ... }

Once you implement those extension methods then you can do:

3.Begin().When(x => x % 3 == 0).Then( ... )

and so on.

Once you've got that implemented then it is easy to lift the operation to sequences. We have a device which turns values into actions; what is a device which turns a sequence of values into a sequence of actions? It is built in to the language: foreach(var item in items) item.Begin()....

Similarly, it is easy to lift the operation to any other monad. Say, nullable. We have a device which turns values into actions. What is a device to turn a nullable value into an action, or no action? It is built into the language: x?.Begin()...

Say you wish to apply your operation to a Task<int>; what is a device which turns a future value into a future action? async Task DoIt(Task<T> task) { (await task).Begin()....

And so on. Implement your operation on values, not on lifted values; use the built-in lifting operations of the language to apply your operation to the lifted value to produce a lifted action.

like image 136
Eric Lippert Avatar answered Sep 23 '22 13:09

Eric Lippert


Here is my attempt. I renamed the interfaces just for my understanding as I hacked it together.

public interface IWhen<T> {
    IThen<T> Then(Action<T> action);
}

public interface IThen<T> : IRun {
    IWhen<T> When(Func<T, bool> test);
    IRun Otherwise(Action<T> action);
}

public interface IRun {
    void Run();
}

public interface IRule<T> {
    Func<T, bool> Predicate { get; }
    Action<T> Invoke { get; }
}

And created the following implementations.

public class Rule<T> : IRule<T> {
    public Rule(Func<T, bool> predicate, Action<T> action) {
        this.Predicate = predicate;
        this.Invoke = action;
    }
    public Func<T, bool> Predicate { get; private set; }
    public Action<T> Invoke { get; private set; }
}

public class Then<T> : IThen<T> {
    private Queue<IRule<T>> rules = new Queue<IRule<T>>();
    private IEnumerable<T> source;

    public Then(IEnumerable<T> source, Queue<IRule<T>> rules) {
        this.source = source;
        this.rules = rules;
    }

    public IWhen<T> When(Func<T, bool> test) {
        var temp = new When<T>(source, test, rules);
        return temp;
    }

    public void Run() {
        foreach (var item in source) {
            var rule = rules.FirstOrDefault(r => r.Predicate(item));
            if (rule == null) continue;
            rule.Invoke(item);
        }
    }

    public IRun Otherwise(Action<T> action) {
        var rule = new Rule<T>(s => true, action);
        rules.Enqueue(rule);
        return new Then<T>(source, rules);
    }
}

public class When<T> : IWhen<T> {
    private Queue<IRule<T>> rules;
    private Func<T, bool> test;
    private IEnumerable<T> source;

    public When(IEnumerable<T> source, Func<T, bool> test, Queue<IRule<T>> rules = null) {
        this.source = source;
        this.test = test;
        this.rules = rules ?? new Queue<IRule<T>>();
    }

    public IThen<T> Then(Action<T> action) {
        var rule = new Rule<T>(test, action);
        rules.Enqueue(rule);
        var then = new Then<T>(source, rules);
        return then;
    }
}

The extension method then looked like this to get started.

public static class EnumerableExtensions {
    public static IWhen<T> When<T>(this IEnumerable<T> source, Func<T, bool> test) {
        var temp = new When<T>(source, test);
        return temp;
    }
}

And with that the following example in .Net Fiddle

public class Program {
    public static void Main() {
         Enumerable.Range(1, 10)
              .When(i => i % 3 == 0).Then(i => Console.WriteLine("fizz"))
              .When(i => i % 5 == 0).Then(i => Console.WriteLine("buzz"))
              .Otherwise(i => Console.WriteLine(i))
              .Run();
    }
}

produced

1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
like image 29
Nkosi Avatar answered Sep 21 '22 13:09

Nkosi