関数型言語 Coconut に触れてみた

Coconut とは functional-style で書きやすいように Python を拡張した言語で, Pythonコンパイルされます.

特に関数型言語を書いた後で, Python でコードを書くとき綺麗に書けないなぁと思うことが多く, awsome-python を眺めていたときに気になった Coconut に触れてみました.

Python で感じる壁

Python で functional-style で書こうとしたときに,個人的には以下のような壁をよく感じます.

無名関数が使いづらい

使いづらい,というと語弊があるかもしれません. Python では以下のように無名関数を定義できます.

f = lambda x, y: x**2 + y**2

が,まず lambda というのが冗長... できれば (x, y) => x**2 + y**2 というように書きたいところです.

そして,中に文が書けません. なので,簡単な関数しか表現できないです.(そこまで複雑な関数を無名にするなと言うことかもしれない...) 文が書けないと言うのは, Javascript で言うと以下の y = 1 /x; みたいなことができない,と言うことです.

const f = (x) => {
  y = 1 / x;
  return y * (1 - y);
}

最後に,型がつけられません.

f = lambda x: int, y: int: x**2 + y**2

としようもんなら SyntaxError となります.

map や filter を使うと見通しが悪くなりがち

Python には map や filter など関数型言語でよく使うような関数が標準で備わっていますが,これを使うと見通しが悪くなることが多いです.

どう言うことかと言うと,例えば,整数リストの中から偶数だけを抽出して,各値を2乗したリストを生成することを考えましょう. これを map と filter で書こうとすると,以下のようになります.

list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, range(10))))

Lambda 式も相まって,まぁ見通しが悪い.なので,こう言うケースでは,リスト内包表記を使うことが多いです.

[x**2 for x in range(10) if x % 2 == 0]

だいぶましになりますが,リスト内包表記でも filter が複数回されたりすると見通し悪くなるなるので限界はあります.

そこで Coconut

これらの壁を Coconut が(ある程度)打ち砕いてくれる訳です.

まず,無名関数ですが,以下のように書けます.

f = (x, y) -> x**2 + y**2

文を書きたければ,こう書けばよく,

f = x -> y = 1 / x; y * (1-y)

or

f = (x ->
  y = 1 / x;
  y * (1-y)
)

型をつけたければ def を前に付ければよい.(戻り値には型付けれないっぽい...?)

f = def (x: int, y: int) -> x**2 + y**2

そして, map や filter は以下のように書けます.

range(10) |> filter$(x -> x % 2 == 0) |> map$(x -> x**2) |> list

|> は shell script の pipe のような演算子です. そして,関数の後に $ をつけ,部分適用をすることで,このように書くことが可能となります.

さらに,

range(10) |> filter$(-> _ % 2 == 0) |> map$(-> _**2) |> list

Scala のように無名引数を使って書くこともできます.いいですね.

他にも長くなるので紹介しませんが,パターンマッチが使えたり, None Coalescing が使えたり,関数合成が簡単にできたり,いい感じです.

中でも一番驚いたのは, Maybe 型がこれだけで定義できてしまうことです.

class Maybe
data Nothing() from Maybe
data Just(n) from Maybe

Just(3) |> fmap$(x -> x*2) == Just(6)
Nothing() |> fmap$(x -> x*2) == Nothing()

Either も同じようにこんな感じで定義できます.(型とか適当ですが)

class Either
data Right(x) from Either
data Left(s) from Either:
    def __fmap__(self, func):
        return self

Right(0.1) |> fmap$(x -> x*2) == Right(0.2)
Left("error!!") |> fmap$(x -> x*2) == Left("error!!")

まとめ

functional-style で書きやすいように Python を拡張した言語 Coconut の紹介をしました.

概念がわかりやすいので習得しやすく,綺麗に書けるので個人的には気に入りました. が,やはりエディタの拡張機能とかはまだまだ整備されていない(VSCode でしか試してないですが)ので,流石に素の Python を書いているときほどの心地よさはないですし,そう言うこともあって,本番環境で使うのは少し抵抗ありますね.

ただ,コンセプトは面白いのでこれからも趣味で使ってみたり watch したりしていきたいと思います.