Freitag, 16. August 2013

East Bound Coding

Today I gave myself a little kata to work at (namely Happy numbers from the CCD-School).

That in itself is not so special. The real fun started after I technically finished the kata.

A few days ago I read blog posts about "Tell, dont ask", and a related idea called something like "east oriented [coding]". The basic idea is that all data should "travel east" in the code. This is what I liked to try and it has two main implications for the code:

  1. it effectively forbids assignments, because that lets the data travel west (var x=SomeFunction(); calculates the result, and then stores it in x - which is "west" of the call).
  2. functions return void; which only lets you get stuff done by using continuations (Note: this is an exaggeration of the original concept, which talks about object orientation and methods that should return void or the result object; However in the context of this blog let me stay without return values...).
That sounds like live will get functional - and I regret having started the kata with C#...

The initial solution consisted of a function that returned whether the number is happy, or not:

  bool IsHappy(int p) { ...
Clearly west bound; so I change it to
  void IsHappy(int p, Action<int> act) { ...
which immediately breaks all tests. No surprise.

(As a side note: I actually first had an Action<bool> in it but it turned out that the bool is not very helpful. The called action function would know that something is happy, but not which number. It seemed to make way more sense to only call the action when the number is happy; with that number as an argument)

Ok, I change the first test from

  Assert.IsTrue(IsHappy(1));
to
  IsHappy(1, x => Assert...
um, no that would not work because it would pass even if the lambda was never called. So how do you check whether a function is never called? The action needs to leave something that I can check; like this:
  var called = false;
  IsHappy(1, x => called = true;);
  Assert.IsTrue(called)
However, this is west bound again. The assignement, you know... The solution I found uses an exception. Let the test expect an exception, and the call throw one. If its never called - it fails because of the missing exception. Interestingly this is less code, although it feels odd to throw an exception when things work:
  [ExpectedException(typeof(Exception))]
...
  IsHappy(1, x => { throw new Exception(); });
The negative tests are easier, they just Assert.Fail() when the lambda is called.

The actual algorithm is pretty easy to convert: basically replace all return(s) by the call to our continuation action or dont do anything. However, what bit me first after that was that - other than the return statement - this call obviously returns. This creates a few surprises, but is easily fixed.

Next the test that checks the returned value fails - it is always one. Crap. After the first recursion the function does not know the initial value any more and as 1 is the value that stops the recusion - this is what always is passed on. This calls for a helper function to keep that value in the loop. Noteworthy that this is easier in functional languages such as F# or lisp where you can use local functions; possible in c# as well, but it looks a little ugly (and has an assignment because the function needs a name when you like to recurse; well, if you do not resort to y- or z-combinators...). No use bothering with further detail... here is the final code of my solution (without the tests):

  public void IsHappy(int value, Action act)
  {
    IsHappyHelper(value, value, act);
  }

  private void IsHappyHelper(int value, int current, Action act)
  {
    if (current == 4) return;

    if (current == 1) act(value);
    else CalcNext(0, current, x => IsHappyHelper(value, x, act));
  }

  private void CalcNext(int sumsofar, int p, Action act)
  {
    if (p <= 0) act(sumsofar);
    else CalcNext(sumsofar + (p%10)*(p%10), p/10, act);
  }

All in all:

  • I was surprised that the resulting code was not much different than the "normal" one; after some fiddling even shorter (except for the function signature which is obviously more complicated).
  • Interestingly the boolean (the return value of the original function) is completely gone! Now the function is just a filter that only lets happy numbers get through. If the false-case is also needed, the function might be extended to accept another call in that case (affects only the first if in the helper). Um, yeah, would not really help readability. In one of my tests this even made the code shorter: from
    if(IsHappy(idx) result.Add(idx)
    to
    IsHappy(idx, result.Add)
  • I did not plan for it, but the functions now only have one position where the code sends the result away, while the initial code had several "return x"'s. Don't know if this is good or bad...
  • I do not like the lambda to change the signature in IsHappyHelper. An F# version is much nicer, as it can use a local function with the correct signature (if a binding is not counted as an assignment...)
  • In post mortem I realize that an action signature of might have been closer to the original solution, and might have saved me from the trouble with the asserts. Well... maybe not.
  • IsHappy is now actually a bad name. Probably IfHappy would do now!?

Disclaimer: This was an experiment! That means I did stuff here, that I would not normally do in production code. I also know that Tell-dont-ask and east-bound are originally object oriented; in this kata I kind of used them in a functional context. DO try this at home... ;-)

2 Kommentare:

  1. // I don't think p could get below zero, right?
    if (p <= 0) act(sumsofar);

    I think it is confusing to use Action as a type for both actions, as they aren't the same. That is maybe why you did not like the change in the signature (you don't really change it, it is two different actions, right?)

    AntwortenLöschen
    Antworten
    1. Thanks for the comment!
      No, it coudn't be negative - unless someone called IsHappy with a negative number (so maybe the type should have been uint).
      The Action type... hm. The problem is that all actions are calls to functions with an int argument, so from what the functions themselves know, they are the same. A "normal" function will also have an integer type return value, and not generate a special type. But yes, I admit that there is room for improvement. Maybe the name should somehow reflect what the int means - I guess I would not go further than renaming the Action-arguments. Hm, and this is an interesting point as well: With this type of coding functions are forced to call other functions of which they do not know (and care) what they do.
      About the signature change: I only meant the lambda which changes from "int" to "int,int,Action". And this is the only readson for the existence of the lambda (And I guess because of that lambda the call is not really a tail recursive one, although it is the last the function does).

      Löschen