読者です 読者をやめる 読者になる 読者になる

Line 1: Error: Invalid Blog('by Esehara' )

または私は如何にして心配するのを止めてバグを愛するようになったか

>> Zanmemo

あと何かあれは 「esehara あっと じーめーる」 か @esehara まで

憂鬱な手続きプログラマのためのClojure[1] ── 入り口のFizzBuzz

プログラミング言語はその私たちの考えに焼き付ける:命令型プログラミングに慣れてしまった人は、プログラムを命令型プログラミング用語で考えるようになってしまった結果、実際に関数型プログラミングより命令型プログラミングのほうが易しく思えるかもしれない。この精神的習慣は、乗り越える価値のあるものだ──そうさせてくれる言語を持っているなら。『On Lisp』より

 この記事は、Clojure Advent Calender 2013(全部俺)の3日目です。

はじめに

 どんな習慣でもそうですが、自分の見慣れないものを見ると、どうしても拒絶反応をしてしまいます。恐らくLispの場合でもそうでしょう。そして、その拒絶反応を振り払うかのように、何かに対して接したとしても、余りの取っ付きにくさに、どうしていいのかわからなくなってしまうことが多いように感じます。初心者にとって、立ちはだかる壁というのは、そもそも「わからないことがわからない」ということです。それに対して、いくら「関数型とは」ということを講釈たれたとしても、ますます混乱を極めてしまう。そのように感じます。

 そこで、あえて何らかのプログラムを、Clojureに書き直していくことによって、Lispの考え方にだんだんと馴染んでいくアプローチはどうか、というのが今回の記事です。

課題: FizzBuzz

 さて、今回の課題は、例によってFizzBuzzです。何度もこのブログで取り上げるFizzBuzzですが、あえてFizzBuzzを知らない人のために、ルールを説明しておきます。

任意の数に対して、3で割りきれる場合は、"Fizz"という文字列を、5で割り切れる場合は"Buzz"を、15で割り切れるときは"FizzBuzz"を、そして左記に該当しない場合はそのまま数字が出力する仕様のプログラムがあります。これらの結果を1から100の場合を出力しなさい。

Pythonで書いてみる

 そこで、あまりにも無骨なPythonのプログラムを書いてみましょう。

for x in range(100):
    if x % 15 == 0:
        print "FizzBuzz"
    elif x % 3 == 0:
        print "Fizz"
    elif x % 5 == 0:
        print "Buzz"
    else:
        print x

 さて、これらをClojureによって書き直してみましょう。

ポイント: 繰り返しを行いたい操作を切り出す

 まず最初に重要なこととして、例えばこのような繰り返しの場合、繰り返される行為というのは、一つの操作を複数にわたって実行しているということです。上の場合もそうです。例えば、Pythonであるならば、次のように変換することができるでしょう。

def fizzbuzz(x):
    if x % 15 == 0:
        print "FizzBuzz"
    elif x % 3 == 0:
        print "Fizz"
    elif x % 5 == 0:
        print "Buzz"
    else:
        print x
  
for x in range(100):
    fizzbuzz(x)

 さて、Pythonで定義されたfizzbuzzを、Clojureに書き直してみましょう。Clojureだと、次のように定義できるでしょう。

(defn fizzbuzz [x]
  (cond (= (mod x 15) 0) (println "FizzBuzz")
        (= (mod x 3) 0) (println "Fizz")
        (= (mod x 5) 0) (println "Buzz")
        :else (println x)))

ポイント: 繰り返しとは、配列の各要素に対して関数を適応することと考えてみる

 とりあえず、関数部分は出来ました。こんどは、この操作を繰り返し、1から100の数字に対してルールを反映させる必要があるでしょう。

 Clojureには、実は`forという構文があるので、それを利用するのもわるくないでしょう。Pythonになれているとするなら、既視感があると思われます。

(dorun (for [x (range 101)]
     (fizzbuzz x)))

 dorunは、このような配列に対する操作に対して、何らかの適応結果を保存せずに捨ててもよいというのを命令するものです。詳しい内容については、恐らく明日以降の記事に掲載されるかと思われますが、今回に関してはざっくりとこのように説明しておきます。

 さて、簡単に「配列に対する操作」といいました。実はPythonにしろClojureにしろ、rangeで表現されるものは配列です。従って、Pythonだと

list(range(101))

 とやると、0から100までのリストが生成されます。同じように、(range 100)とすると、リストが出てくるのが確認できるでしょう。

 さて、貴方がJavaScriptなどをやっている場合、このアプローチはちょっと違和感を覚えるかもしれません。というのは

for (var x = 1; x < 100; x++) {
  console.log(x);
}

 という風に、変数xを増加させていって、それを使うのではないか? とも考えられるわけです。

繰り返しの操作を考える

 そこで、足を止めて、いったい繰り返しはなにをやっているのか、抽象化してみましょう。JavaScriptの上のような表現の場合、プログラムは下のような手続きをしていると考えることが出来るでしょう。

  • xに1を代入する
  • console.logにxを渡す
  • xに1を追加する
  • console.logにxを渡す
  • xに1を追加する

 さて、この繰り返しの行為なのですが、これらは下のように書き換えることができます。Pythonの場合、雑ですが下のように言うことができるでしょう。

  • xに1を代入する
  • xにprintを適応する
  • xに2を代入する
  • xにprintを適応する
  • xに3を代入する

 さて、繰り返しの最初の説明と一緒のように見えます。が、JavaScriptの最初のような手続きは、どうしても「追加」という操作が付随しますが、今回の場合、追加という手続きは余計なものです。この場合興味あるのは、1, 2, 3 ...といった、1ずつ増えていくそれぞれの数に対して、一つの操作を適応することに興味があるわけです。代入するとか、あるいはある変数を増やしていく、ということに関しては興味ないはずです。もっというならば、下のようになるはずです。

  • 1にprintを適応する
  • 2にprintを適応する
  • 3にprintを適応する

 さて、これを「変数を変化させていく」のではなく、また変数を一つずつ取り出すわけでもない方法で表現できないでしょうか。もしプログラミングに詳しい人ならば、一つの方法が思いつくでしょう。すなわち、ある配列の要素一つずつに対して、関数を適応するという方法。

 上の方法は、あくまでもそれぞれの要素を一つずつ関数に対してわたしてあげるという、いわば関数が待ちの状態であるわけです。これらを配列に対して積極的に渡す方法として、mapというものが存在しています。

 例えば、1から100のリストに対して、2倍した要素を取り出す場合、下のように書けます。

(map #(* % 2) (range 1 101))

 そうすると、それぞれの要素が2倍になったリストが取り出せたかと思われます。mapは各要素に適応された結果を取り出せます。(実はforもまた、同じように要素が適用されたリストが生成されています)

 そこで、mapを利用して各要素に対してfizzbuzzを適応させてみましょう。

(dorun (map fizzbuzz (range 1 101)))

 無事fizzbuzzが出力されたかと思います。

(defn fizzbuzz [x]
  (cond (= (mod x 15) 0) (println "FizzBuzz")
        (= (mod x 3) 0) (println "Fizz")
        (= (mod x 5) 0) (println "Buzz")
        :else (println x)))

(dorun (map fizzbuzz (range 1 101)))

まだまだエレガンスに書ける

 さて、ここまで見てきた人にとって、このfizzbuzzの関数は、まだまだ不格好であるといえるでしょう。例えば、気になるところとしては、仕様が増えてしまった場合、わざわざ分岐を追加してあげなければならないということ、また条件判定と出力文字は分離できるのではないかということ、が挙げられるでしょう。恐らく、これはもっとClojureらしく、Lispらしく書き直すことが出来ます。

 この点に関しては、12/4のAdvent Calenderで考えてみましょう。それでは。