最初に断っておきますが、当エントリは実用的ではありません。
Haskell初心者が挫折しがち(と思われる)関数合成について、思いを共有出来ればいいなと思っています。
関数合成を書くのって、何だか難しい
Haskell初心者の鬼門、関数合成。
なんかかっこいいから使ってみたい。let
を使ったら負け、$
を使った負け・・・という思いで頑張ってみるものの、なかなかうまく書けない・・・
なぜ私はこんなに関数合成を苦手に感じるのか。整理して解決への糸口を探してみることにしました。
関数合成は思考と記述の順番が逆
私が感じるに、関数合成が分かりにくいのは 後ろから 関数が適用されるのに違和感があるからです。
例として複数行から成る文字列の各行の先頭に行番号を付けるプログラムを書いてみます。
f = unlines . map (\(f, s) -> f ++ s) . zip (map show [1..]) . lines
(合成回数を増やすためにzipWith
は使っていません)
関数適用毎の型変換を書き出してみると、やってることが理解しやすくなるかもしれません。
f = unlines -- [String] -> String
. map (\(f, s) -> f ++ s) -- [(String,String)] -> [String]
. zip (map show [1..]) -- [String] -> [(String,String)]
. lines -- String -> [String]
この関数を読むには、後ろの関数から見て行かないとデータの変換の流れが読み解けません。
書くときも読むときも、このことが思考の妨げになっている気がします。
Unixのパイプは書きやすい
Unixのパイプによる処理を書くときは、処理の順番と記述が一致していて、良いリズムでタイプすることが出来ている気がします。
例えば今動いているjavaプロセスの数は以下のように書けます。
ps aux | grep java | wc -l
psの結果をgrepで絞り込んでwcで数えてます。思考と記述の順番が一致していて、分かりやすい。
Haskellの関数合成を書く時は、後ろの関数から考えて行くので、エディタを行きつ戻りつしながら書くことになり、リズムが悪くなる。
このことが「関数合成で書くのって苦手だ」と思ってしまう原因になっている気がします。
Javaの場合は?
ここで気付いたのですが、Javaを考えてみると、実はHaskellの関数合成と同様、思考と記述の順番が逆です。
先ほどの処理を、擬似的にJavaコードを書いてみると以下のようになるでしょうか。
return unlines(concatTaple(zip(lines(arg))));
関数呼び出し結果を直接関数の引数に渡すスタイルは、ときに「読みにくい!」として忌避されます。
でも、Javaに慣れている私は、これをそれほど読みにくいとは感じませんし、もちろん書くときも特に苦手に感じているわけではありません。
結局は慣れの問題・・・?
引数にやられることも?
とは言え、他にも関数合成で書くことをあきらめてしまう原因があります。
引数に関連していることが多く、例えば
- 引数が複数ある関数を関数合成で書くのは可能なのか?
- 合成に使いたい関数の引数の順番がまずくて型が合わないときにどうすればいいんだ・・・
などですね。
これらは、真剣に考えれば解決しそうな気がするのですが、これまでそこまで突き詰めて考えたことがありませんでした。
これを機会に考えてみようと思います。
苦手を克服するには成功体験が重要ですので。
モナドは思考と記述の順番が一致
一方、もう1つのHaskellの鬼門・モナドを使うときは、思考と記述の順番が一致しています。
先ほど作った関数f
を使って、標準出力から得た文字列を加工してみます。
main = getContents >>= return . f >>= print
do記法を使うと以下のようになります。
main = do
cs <- getContents
s <- return $ f cs
print s
いずれも、思考と記述の順番が一致しています。
思うに、このことも関数合成を苦手に思う原因になっている気がします。
モナドで書く時と関数合成(これは関数適用でも同じ)で書く時とで、頭を切り替えないといけないのが、脳みその負担になっているのです。
苦手感を克服するには?
ここまでなぜ苦手なのか?を考察してきました。
最後に、解決策をいくつか考えてみました。
- モナドと関数合成(適用)とで頭を切り替えるように心がける
- 関数合成にこだわりすぎない
- Haskellに慣れる
- とにかく慣れる
いろいろ書いた割には安易なオチで恐縮です。
Haskellでもっとプログラム書かないといけないな、というお話でした。