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

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

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

>> Zanmemo

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

Maybe FizzBuzzの実装比較: HaskellとClojureに関して

この記事はHaskell Advent Calender 2013 13日目の記事の筈でした。

この記事は間違った記事の可能性があります (2013/12/17 追記)

Maybe Monadsが適切に使われていないというご指摘を頂いています。修正までにお時間をください。

Maybe Monadsの例は不適切だった可能性があります (2013/12/18 追記)

このようなフォロー(?)を頂きました。

ありがとうございます。以下については、間違いがあるので、気をつけてお読みください。

はじめに

 関数型言語界隈には、安易に触れると燃えるトピックというのが存在します。その一つとして「モナド」があります。そして、それが余りにも綺麗なために、他の関数型言語を標榜する言語でも、モナドを取り入れたアプローチが導入され、そして失敗します。

 来年の課題言語として、自分はClojureを選んでいます。そして、当たり前の如く、Clojureの公式グループが内包しているライブラリの一つに、algo.monadsというものが存在しています。

 自分は、何らかのパラタイムを試す時に、とりあえずFizzBuzzを書くというのをやっています。プログラミング課題におけるFizzBuzzとは何か、については検索して頂ければわかるかと思われます。今回の記事については、CLojureとHaskellのMaybe FizzBuzzの実装を通じて、モナドについての足がかりを自分なりに手に入れることができれば、とおもっています。

モナドでFizzBuzz(Haskell)

 では、まずHaskellでMaybe MonadsによるFizzBuzzを書いてみましょう。

import Control.Monad ((>=>))

maybe_fizz :: Integer -> Maybe String
maybe_fizz 0 = Just "Fizz"
maybe_fizz x = Just ""

maybe_buzz :: Integer -> String -> Maybe String
maybe_buzz 0 x = Just $ x ++ "Buzz"
maybe_buzz _ x
    | x == "" = Nothing
    | otherwise = Just "Buzz"

maybe_fizzbuzz :: Integer -> Maybe String
maybe_fizzbuzz x = maybe_fb (x `mod` 3) 
    where
        is_buzz = maybe_buzz (x `mod` 5)
        maybe_fb = maybe_fizz >=> is_buzz

maybe_process :: Integer -> String
maybe_process x = case (maybe_fizzbuzz x) of
    Nothing -> show x
    Just str -> str

main :: IO ()
main = go maybe_process

 かなり汚いコードですが、FizzBuzzの過程を副作用の連鎖として設計した場合のコードです。つまり、あるモナドが割り切れた場合においては、何らかの文字列が入り、そしてあるモナドが割り切れない場合においては、Nothingが入ったままになります。Nothingの場合、文字列がないわけですから、数字を返し、文字列がある場合は、その副作用としての文字列を取り出しています。

 さて、これをRoyという、関数型JavaScript Translaterに内包されているMonad Syntaxにおいて書き直してみましょう。

data Option a =
  Some a | None

let is_fizz x =
  if x % 3 == 0 then
     Some "Fizz"
  else
     None ()

let is_buzz x m =
  if x % 5 == 0 then 
     match m
       case (Some a) = Some (a ++ "Buzz")
       case None = Some "Buzz"
  else
     m
    
let optionMonad = {
    return: \x ->
      Some x
    bind: \x f -> match x
      case (Some a) = f a
      case None = None ()
}

let fizzbuzz x y =
    if x > y then
        console.log("")
    else
      let fizz = is_fizz x
      let result = (do optionMonad
          buzz <- is_buzz x fizz
          return buzz
      )
      match result
        case (Some a) = console.log a
        case None = console.log x
      fizzbuzz (x + 1) y

fizzbuzz 1 100

 ScalaのMore Functionalなアプローチを支援するScalazを使ったMaybe FizzBuzzも書いてみたかったのですが、誰か有志が書いてくれる気がしまず。

モナドの定義

 モナドの定義に関しては、自分が説明するよりも、以下の説明を引用するだけで十分でしょう。

で、モナドって使えるの?

 インターネット上のドキュメントについて、例えば「何をモナドとしてよぶことが出来るのか」と、「モナドという考え方を通じることによってどのようなアプローチを考えることができるのか」ということについては、若干の混乱があるようにあります。

 自分が調べた限りだと、ある機能の担保自体は、モナドとは関係はなく、しかし現実問題として、例えばIO制御であったり、あるいは副作用をハンドリングする上において、モナドというパターンを利用すると静的型付き関数型言語において、上手く抽象化出来るという風に捉えてます。

 そして、さらに言うとモナドという考え方が有用である場合と有用ではないパターンがあります。

Clojureにおけるモナド

 ではClojureの場合はどうでしょうか。例えば、有名なプログラミング質問サイトにおける解答の一部を紹介します。

ベストアンサーのざっくりとした超訳

そもそもClojureって動的型付け言語だから、君が「文字列が欲しいなあ」と思ったら文字列が渡されるし、nilが欲しいなあ、と思えばnilが返ってくるんだよね。「関数型」って、別に「コンパイル時における厳密な型付け」を意味しているわけではないし、それってHaskellを書いているときのアプローチと全く逆の経験なんだと思うよ。

 何を言っているのでしょうか。

 そこで、上のことを考えるために、algo.monadsを使って、Maybe FizzBuzzを考えてみましょう。

(defn if-n [num string]
  (fn [int]
    (domonad maybe-m
      [:when (zero? (mod int num))]
      string)))

(def fizz (if-n 3 "Fizz"))
(def buzz (if-n 5 "Buzz"))

(with-monad maybe-m (def m+ (m-lift 2 str)))

(defn m-merge [m1 m2]
   (domonad maybe-m
            [has-value-m1 (not (nil? m1))
             has-value-m2 (not (nil? m2))
             :when (or has-value-m1 has-value-m2)]
            (if (and has-value-m1 has-value-m2)
              (m+ m1 m2)
              (m-plus m1 m2))))

(defn do-fb [f n]
  (domonad maybe-m
          [fizzbuzz-m (f n)
           :when (not (nil? (f n)))]
          fizzbuzz-m))

(defn fizzbuzz [n]
  (with-monad maybe-m
    (m-plus (m-merge (do-fb fizz n) (do-fb buzz n))
            (m-result n))))

 一部、Clojureのalgo.monadsは使い勝手が悪い、という印象があるようです。自分もそのように考えます。

 例えば、Maybe Monadsの役割の一つには、Null Pointerみたいな、いわゆるクリティカルな値を適切に処理するという問題意識があったかと思われますが、Clojureの場合、普通にmonadでバインドしていたとしても、各要素に対して普通にnilとして扱えてしまいます。モナドにおいて、恐らく動的型付けという部分において、有用性が一部減少してしまう事実はあるのかもしれません。それはClojureにおいても似ているかもしれません。

 Clojure Communityにおいて、今後ライブラリとして、型チェックを厳密にするライブラリを改善し、実用に耐えうるレベルに持っていく計画がされています。そのときになったら、Clojureにおけるモナドも、もっと使い勝手がよくなるのかもしれません。

もっと詳しく知りたい

 というわけで、下の本をお薦めしておきます。

関数プログラミング入門 ―Haskellで学ぶ原理と技法―

関数プログラミング入門 ―Haskellで学ぶ原理と技法―

型システム入門 −プログラミング言語と型の理論−

型システム入門 −プログラミング言語と型の理論−

  • 作者: Benjamin C. Pierce,住井英二郎,遠藤侑介,酒井政裕,今井敬吾,黒木裕介,今井宜洋,才川隆文,今井健男
  • 出版社/メーカー: オーム社
  • 発売日: 2013/03/26
  • メディア: 単行本(ソフトカバー)
  • クリック: 68回
  • この商品を含むブログ (9件) を見る