1

I'm becoming addicted to writing fluently (perhaps incorrectly originally said "declaratively") and I'm taking it to possibly unwise extremes, but the more I write this way the more one thing stands out as being completely unable to write in this fashion, and that's the return statement.

Something like what I want to write (for example):

StartSomewhere
.Get<Something>()
.OrderBy(something => something.SomeProperty)
.Transform(ToSomethingElse)
.ChooseSomeOption("some value")
.Run()
.ReturnIfNull() // impossible!
.ContinueUsingNonNullValue();

What I'm stuck with instead:

if (
   StartSomewhere
   .Get<Something>()
   .OrderBy(something => something.SomeProperty)
   .Transform(ToSomethingElse)
   .ChooseSomeOption("some value")
   .Run()
   .IsNull(out var notNullValue)
)
   return;

if (
   notNullValue
   .ContinueUsingNonNullValue()
   // etc.
)
   return;

I suspect there's no way to get what I want here besides doing something really advanced and crazy like wrapping everything in an Abortable monad or something so that the chain can be turned off midway, allowing the remaining calls to just be ignored, so maybe it's pointless to ask, but who knows.

  • 2
    Is this really declarative? or is it really procedural: Step 1 Get something; Step 2 sort it; Step 3 change it; Step 4 make a selection ... – jmarkmurphy Oct 8 '18 at 19:19
  • @jmarkmurphy yes, my code may not be strictly "declarative". – Dave Cousineau Oct 8 '18 at 19:32
  • 2
    Do you mean you want a "fully fluent" interface? ... What you have here doesn't read like declarative code to me. – svidgen Oct 8 '18 at 19:49
  • @svidgen yes, maybe I mean fluent and not declarative, sorry. – Dave Cousineau Oct 8 '18 at 19:50
  • I guess you could throw an exception – Ewan Oct 8 '18 at 21:12
2

Imagine what your code would look like if you could have fluent if methods....

DoSomething()
  .If(true)
  .Step2()
  .Else()
  .Step3()
  .EndIf()
  .Step4()

Would you really be happy with this?

You could achieve it with some internal state object. But its not any more readable, and if we throw in some nesting its only going to get worse.

You can improve on your current method by keeping the object. Allowing you to move the code blocks out of the if statement.

var i = DoSomething()
    .Step1()

if(i.Result() != null) 
{
     i.Step2(); 
}
  • I think you're right, it just turns things into a mess. – Dave Cousineau Oct 9 '18 at 0:24
3

Leaving the editorial aside for a moment, two options come to mind:

1. Always operate on a "collection" or "builder".

Instead of having ChooseSomeOption(<string>) return a scalar, always return a TypeCollection. (Same for every method in the chain.) Your fluent interface just exists in some sort of TypeCollection class then. And, if a filter() or remove()-type operation reduces the Count to 0, it doesn't matter. Everything just works.

If a non-Collection return value is required, just make it your last "build" step.

And as alluded to, it doesn't strictly need to be a collection. It could just sort of be a "builder."

2. Use the Null Object pattern.

Basically, if a method like ChooseSomeOption(<string>) doesn't find a matching ChainLink object, return a new NullChainLink(), a child class of ChainLink that supports the same methods as a real ChainLink, but doesn't actually do anything.

This might get more complicated than option 1, because you'll need to create null objects for every possible nullable response and every type thereafter, which much also be "nullable."


I think I'd remiss if I didn't include the editorial:

I really like fluent interfaces. But, if I'm being honest with myself and you, they're really really really annoying to debug. A significant number of fluent interfaces I've written over the years were very pleasant to write, and very easy to read, but ultimately broken apart during debugging efforts.

If you and your team like this sorts of method chaining, two pieces of advice:

  1. Don't make sacrifices for it.
  2. Write unit tests obsessively.

Even with a lot of testing, it's probably better to sacrifice an "elegant" method chain for code that works right now than it is to restructure everything else to make your fluent interface work.

  • this kind of code mostly only exists at the topmost (UI) level. it's not working 100% on fluent objects. what I've done is written two extension methods With and SelectWith that run either an Action or a Func on the current value. this allows you to treat any Action or Func<T> as if it was a fluent method. I've sort of solved my problem with returning by instead conditionally calling a local method. the main problem is that it seems to require parameters since I can't carry everything forward. I don't think it's prone to bugs since the methods are each individually very simple. – Dave Cousineau Oct 8 '18 at 22:31
  • With and SelectWith?? ... Are you in danger of reinventing the Linq Enumerable interface??? – svidgen Oct 8 '18 at 22:45
  • No, they aren't IEnumerable methods. void With<T>(this T o, Action<T> a) => a(o); and T2 SelectWith<T1, T2>(this T1 o, Func<T1, T2> f) => f(o); SelectWith could be called Transform. Or they're kind of like .ForEach and .Select except they operate on single objects instead of sequences/enumerables. – Dave Cousineau Oct 8 '18 at 22:52
  • One example: instead of Console.WriteLine("this is some text"); now you can write "this is some text".With(Console.WriteLine);. That's a simple example that you might not like, but it's much better when "this is some text" is the result of a long chain of methods. – Dave Cousineau Oct 8 '18 at 22:57
0

What I've done is invert the condition and write some If... extension methods. Rather than say "Cancel if condition", I say "do this if not condition".

The main reason to cancel is to avoid nesting (specifically the indentation), but you can still avoid nesting by extracting the actions into named nested methods. This splits the logic up into named chunks which seems like it might actually be beneficial.

This probably wouldn't work if I needed to return a value, though.

void MyMethod() {
   StartSomewhere
   .Get<Something>()
   .OrderBy(something => something.SomeProperty)
   .Transform(ToSomethingElse)
   .ChooseSomeOption("some value")
   .Run()
   .IfNotNull(continueUsingNonNullValue);

   void continueUsingNonNullValue(object nonNullValue) =>
      nonNullValue
      .DoSomethingWithValue()
      .DoSomethingElseWithValue()
      // etc.
}

This ultimately proved a bit too messy/smelly. It was interesting, though, since it broke apart a method into smaller sections, each of which with it's own descriptive name. But it became harder to understand exactly what the function was doing as a whole. In particular it wasn't obvious where and when the logic would or wouldn't continue.

What I've done instead is make (good?) use of out var. Rather than wrap the chain itself in an if, and rather than writing the same var result =, if (result == null) return; code over and over and over, I use a method that can be used like this:

StartSomewhere
.Get<Something>()
.OrderBy(something => something.SomeProperty)
.Transform(ToSomethingElse)
.ChooseSomeOption("some value")
.Run()
.IsNull(out var value, out var isNull);

if (isNull)
   return;

// continue using non-null value

With Resharper ContractAnnotation I get warnings if the value is not known to be not-null and I try to use it anyway. I think anything more would require adding something new to the language.

-1

There's two approaches you can take here, both of which I've used successfully in the past: one using state, one not.

Using state

Assuming that your whole fluent chain used just one object, MyFluentType, then ReturnIfNull would do something like:

MyFluentType ReturnIfNull()
{
    inReturnNullState = SomeNullTest();
    return this;
}

and ContinueUsingNonNullValue does something like:

MyFluentType ContinueUsingNonNullValue()
{
    if (!inReturnNullState)
    {
        // do whatever the method does as we aren't in that state
    }
    return this;
}

So ContinueUsingNonNullValue is always called, and it uses the object's current state to determine whether it does something or just returns.

No state solution

Here, we have MyFluentType implement an interface, IFluentType. Then ReturnIfNull decides which implementation of IFluentType to return. If it's null, it returns an implementation that has an empty ContinueUsingNonNullValue:

class MyFluentType : IFluentType
{
    IFluentType ReturnIfNull() => SomeNullTest() ? new NullFluentType(this) : this;

    MyFluentType ContinueUsingNonNullValue()
    {
        // do whatever the method does as we aren't in that state
        return this;
    }
}

class NullFluentType : IFluentType
{
    private readonly MyFluentType _parent;
    NullFluentType(MyFluentType parent) => _parent = parent;        

    MyFluentType ContinueUsingNonNullValue() => _parent;
}

This way, no state is needed. Instead, a different type, with a different implementation of IFluentType is provided, depending on the null check.

Neither solution "returns" at the ReturnIfNull point; they simply bypass any further processing so effectively end the chain and return at that point, even if in reality they don't.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.