TDD Kata 09 – Print Diamond

This is my weekly Kata post. Read the first one to learn what it is all about.

Last week: Functions Pipeline

To the Kata description
Scroll down to this weeks Kata description

My first implementation in PHP was a Pipe class with __invoke method:

class Pipe
{
    /**
     * @var \callable[]
     */
    private $callables;

    private function __construct(callable ...$callables)
    {
        $this->callables = $callables;
    }

    public static function create(callable ...$callables) : Pipe
    {
        return new self(...$callables);
    }

    public function __invoke()
    {
        return array_reduce(
            $this->callables,
            function(array $result, callable $next) {
                return [$next(...$result)];
            },
            func_get_args()
        )[0];
    }
}

I made it possible to be created without any callables. The behavior was not specified but I choose to let the pipe return the first argument in that case.

I tested that it works with all kind of callables:

  • Function name: 'strtolower'
  • Invokable class, using a mock:
    $callable = $this->getMockBuilder(\stdClass::class)
                ->setMethods(['__invoke'])
                ->getMock();
  • Object method, with a similar mock: [$object, 'method']. In hindsight, this would have been a good case for anonymous classes.

The next time I simplified it a lot: First, I always required at least one callable and separated the first one from the rest, which made the function (no class this time) much clearer.

use function array_reduce as reduce;

function pipe(callable $f, callable ...$fs) : callable
{
    return function(...$args) use ($f, $fs) {
        return reduce(
            $fs,
            function($result, $next) {
                return $next($result);
            },
            $f(...$args)
        );
    };
}

But also the tests became much simpler. With the callable type hint I can assume that all types of callables work the same way, so I only used simple core functions. Learning: try to keep the tests simple as well!

class PipeTest extends \PHPUnit_Framework_TestCase
{
    public function testPipeSingleFunction()
    {
        $strlen = pipe('strlen');
        $this->assertEquals(5, $strlen('abcde'));
    }
    public function testPipeSingleFunctionMultipleArguments()
    {
        $explode = pipe('explode');
        $this->assertEquals(['a', 'b'], $explode('.', 'a.b'));
    }
    public function testPipeMultipleFunctions()
    {
        $wordcount = pipe('explode', 'count');
        $this->assertEquals(2, $wordcount(' ', 'hello world'));
    }
}

When I implemented the alternative version, compose(), which works from right to left, the tests turned out to be similar in the end but the implementation always ended up using recursion, which seemed to be a much better fit for this case:

use function array_shift as shift;

function compose(callable $f, callable ...$fs) : callable
{
    if (empty($fs)) {
        return $f;
    }
    return function(...$args) use ($f, $fs) {
        return $f(compose(shift($fs), ...$fs)(...$args));
    };
}

Finally, I gave the pipe a try with Ruby once more. I learned that blocks (like in map{|x| x + 1}) are not functions and cannot be passed around, instead Ruby has procs and lambdas, but it’s still a bit different from real higher order functions. From a testing perspective, it was nothing new:

def pipe f, *fs
    proc do |*x|
        fs.reduce(f.(*x)){|res,nxt| nxt.(res)}
    end
end


class PipeTest < Minitest::Test
    def test_single_function
        plus_one_pipe = pipe(
            lambda {|x| x + 1}
        )
        assert_equal 2, plus_one_pipe.(1)
    end
    def test_multiple_functions
        plus_one_to_string_pipe = pipe(
            lambda {|x| x + 1},
            lambda {|x| x.to_s * 2}
        )
        assert_equal "22", plus_one_to_string_pipe.(1)
    end
    def test_multiple_arguments
        addition_double_pipe = pipe(
            lambda {|x, y| x + y},
            lambda {|x| x.to_s * 2}
        )
        assert_equal "33", addition_double_pipe.(1,2)
    end
end

Ninth Kata: Print Diamond

The next Kata is described here as:

Given a letter print a diamond starting with ‘A’ with the supplied letter at the widest point.
For example: print-diamond ‘E’ prints

    A    
   B B   
  C   C
 D     D
E       E
 D     D
  C   C
   B B
    A

For example, print-diamond ‘C’ prints

  A
 B B
C   C
 B B
  A

Once more it is hard not to write the whole algorithm at once instead of going in small steps, test by test. Let’s see how it goes!