ポイントフリースタイルの歪んだ美

例えば、xを2倍してshowする関数fを考えます。
素直に書けば次のようになるでしょう。

Prelude> let f x = show (x * 2)
Prelude> f 5
"10"

こういったシンプルな処理は、ポイントフリースタイルにするのがHaskellらしいプログラミングですね。

Prelude> let f = show.(*2)
Prelude> f 5
"10"

このように書いたほうが余分な変数が無くなり、煩雑さが無くなるため可読性が高まるというわけです。(多少の慣れは必要ですが)

慣れてくると、次のような一見無理そうな関数も・・・

eqShow x y = show (x==y)

次のように(.)を組み合わせて実装できます。
(注:型クラスの関係で明確に型を指定する必要があるため、ghciではできないようです)

eqShow :: Eq a => a -> a -> String
eqShow = (show.).(==)

もっとも、このくらいになってくると、ある程度理解してないうちは読みやすいとは言えない気もしますが。

Haskellの場合、全ての関数はカリー化されていると考える事ができるため(適切な表現では無いですが)こういった、「ポイントフリースタイル」を使って仮引数を減らす事ができるわけです。

この辺が「Haskellの美しさ」だったりもするんですが、何事もほどほどが肝心とも言えますね。

とはいえ、

ポイントフリースタイルの美しさに魅せられてしまうHaskellプログラマは珍しく無いのか、Googleで「Haskell ポイントフリースタイル」などと検索すると、なんとも言いがたい奇妙なコードがあちこちで見られます。
感覚としてはIOCCCと似たような趣があるようです、もちろん実際の開発であんまり可読性の悪いコードを書くようなプログラマはそうそう居ないと思いますが、
実のところ、ある程度以上Haskellを理解していなければ実装する事はできませんし、頭の体操を兼ねて、Haskellという言語の(歪んだ)美しさを楽しんでみるのも良いんじゃないでしょうか。

というわけで、以下の三つの関数を用意しました。

a = ("A"++)
b = ("B"++)
c = ("C"++)

この関数を使って、遊んでみます。

*Main> a.b.c $ "D"
"ABCD"

次の三パターンを、それぞれポイントフリースタイルにするとどうなるでしょうか。

f x = a.b.(++) x.c
g x = a.(++) x.b.c
h x y = a.(++) x.b.(++) y.c

いずれも、二引数関数の(++)が中程にあるので、先ほどの例よりも複雑になります。
具体的なやり方については、melponさんのブログ「ポイントフリースタイル入門」を読んでいただくと良いかと思います。

一つ目、二つ目は、ある程度規則を見つければさらっと書けます。

f = (.c).(a.).(b.).(++)
g = (.b.c).(a.).(++)

三つ目は・・・けっこう悩みました。第二引数の y は普通に削れたのですが・・・

h x = (.c).(a.).((++) x.).(b.).(++)

xを消すのにけっこう試行錯誤が必要でした、結果としては・・・

h = (.(b.).(++)).((.c).).((a.).).(.).(++)

のようにして完全に仮引数を消すことができたわけですがなんだこれ。
自分で書いておいてなんですが、どうやって読んだら良いかわかりません。

このコードを見て「美しい」とか言うと倒錯してると思われそうですが、ラムダ計算を基板にした高い表現力がHaskellプログラミングの醍醐味だったりもします。(モナドとかArrowとか)
あんまり言うと新興宗教扱いされてしまう(というか半ばされている)世界なのでこの辺にしておきますが・・・

やっててもけっこう楽しいです、Haskellに関する理解も深ま(る気が)りますし、あんまり嫌わないであげてください、ポイントフリースタイル。

最後に、今回書いたコードの全体を掲載して終わります。それでは。

module Main where

a = ("A"++)
b = ("B"++)
c = ("C"++)

f x = a.b.(++) x.c
_f = (.c).(a.).(b.).(++)

g x = a.(++) x.b.c
_g = (.b.c).(a.).(++)

h x y = a.(++) x.b.(++) y.c
_h = (.(b.).(++)).((.c).).((a.).).(.).(++)

main = do
  putStrLn $ f  "x" "D"
  putStrLn $ _f "x" "D"
  putStrLn "--------------"
  putStrLn $ g  "x" "D"
  putStrLn $ _g "x" "D"
  putStrLn "--------------"
  putStrLn $ h  "x" "y" "D"
  putStrLn $ _h "x" "y" "D"

実行結果:

ABxCD
ABxCD
--------------
AxBCD
AxBCD
--------------
AxByCD
AxByCD