2008-06-26

FizzBuzzをもう少し分かりやすくした。

前回のコードは、既にMPLの素晴らしい恩恵を受けていたわけだが、やはり他人にとっては読みにくいだろう。なにが分かりにくいかといって、メタ関数、FizzBuzzSequence の引数が、3の倍数か、5の倍数か、あるいは3と5両方の倍数なのかを判断する部分が分かりにくい。最初はMPLのpush_backの中に書いたのだが、これでは他人が読みづらかろうと、外に押し出して、メタデータ、elem を定義した。しかし、それでもまだ読みづらい。

ここで気付いたのは、そもそも、3の倍数かどうかを判断する条件式自体が、読みづらいということだ。(x%3 == 0)なんて分かりにくすぎる。既にこれはメタプログラミングの問題ではない。こういう時、我々はどうするかというと、コメントを付け加えるか、別の関数を作るのだ。では早速。

#include <iostream>

#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_VECTOR_SIZE 100
#include <boost/mpl/vector.hpp>

#include <boost/cstdint.hpp>
#include <boost/type_traits.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/for_each.hpp>


namespace mpl = boost::mpl ;

struct Fizz{ static char * const value ; } ;
struct Buzz{ static char * const value ; } ;
struct FizzBuzz{static char *const value ; } ;
char * const Fizz::value = "Fizz" ;
char * const Buzz::value = "Buzz" ;
char * const FizzBuzz::value = "FizzBuzz" ;


template < int i >
struct GetFizzBuzzElement
{
    typedef typename
        // if FizzBuzz
        mpl::eval_if_c< (i % 3 == 0) && (i % 5 == 0)
            , mpl::identity< FizzBuzz >
        // if Fizz
        , mpl::eval_if_c< (i % 3 == 0)
            , mpl::identity< Fizz >
        // if Buzz
        , mpl::eval_if_c< (i % 5 == 0)
            , mpl::identity< Buzz >
        // else
        , mpl::int_< i >
        
        >// Buzz
        >// Fizz
    >::type type ;
} ;

template< int i >
struct FizzBuzzSequence
{
    typedef typename
        mpl::push_back<
             typename FizzBuzzSequence< i - 1 >::type
            , typename GetFizzBuzzElement<i>::type
        >::type type ;
} ;

template < >
struct FizzBuzzSequence<1>
{
    typedef mpl::vector< mpl::int_<1> > type ;
} ;

struct print
{
    template < typename T >
    void operator () (T) const
    {
        std::cout << T::value << "\n" ;
    }
} ;

int main()
{
    mpl::for_each< FizzBuzzSequence<100>::type >( print() ) ;
}

このように、FizzBuzz如何を判断するメタ関数、GetFizzBuzzElementを定義すると、FizzBuzzSequence メタ関数が非常に読みやすくなった。そして、GetFizzBuzzElement メタ関数は、分かりやすいインデントと、簡単なコメントを付け加えた。なお、深さだけで見れば、GetFizzBuzzElement メタ関数内のインデントは正しくないのだが、意味で考えれば、if else if else if elseなので、あえてこのようなインデントにしている。

さて、今思うに、Linus Torvaldsは正しかった。詳しい理由は後ほど、別記事で書くことにするが、重要なことは二つ、インデントは8文字で、インデントの深さは3階までである。

追記:
ここでは、メタプログラミングの用語は、Boost.MPLの作者による著書、C++ Template Metaprogrammingに拠った。コンパイル時の関数として、メタ関数、コンパイル時の変数としてメタデータである。もし、Lispを右に、Haskellを左に置くような人には、変数の呼称には、メタデータではなく、値を名前に束縛するといったほうが理解できるかもしれない。

No comments: