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:
- 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).
- 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...).
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, Actionact) { 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)
toIsHappy(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... ;-)