歩いたら休め

なんでこんな模様をしているのですか?

【Coconut】Python互換の関数型言語Coconutはパイプ演算子が使えるよ

日頃RとPythonを使っていると、「ああ、Pythonでもパイプ演算子が使えればいいのに」「purrrライブラリみたいにラムダ式が簡単に書ければいいのに」「Pythonのジェネレーター(遅延評価するリストのようなもの)をパイプで渡せると超楽しそう」と思うことはありませんか?私は思います。

そんなニーズに応えてくれるかもしれない言語を教えてもらいました。それがCoconutです!

coconut-lang.org

Coconut is a simple, elegant, Pythonic functional programming language that compiles to Python. Since all valid Python is valid Coconut, using Coconut will only extend and enhance what you're already capable of in Python.

また、Pythonと互換性のある言語らしく、Pythonのコードがそのまま動かせる、みたいなことが書いてありました。

Rのmagrittrでおなじみのパイプ演算子も使えます。これは元ネタであるF#の書き方ですね。

"hello, world!" |> print

print関数に"hello, world!"という文字列を渡す」という意味の演算子です。

インストール

pipで簡単にインストールできました。

python -m pip install coconut

ちなみに、pyenvで入れたAnaconda 2.0.1で動かしてます。

coconut -v
# Coconut: Version 1.1.0 [Brontosaurus] running on Python 3.4.3 |Anaconda 2.0.1 (x86_64)| (default, Mar  6 2015, 12:07:41)  [GCC 4.2.1 (Apple Inc. build 5577)]

Jupyter notebookの起動

簡単なコードはjupyter notebookで試しましょう。

coconut --jupyter notebook

ブラウザ上でプログラムを試すことができます。Pythonのライブラリも呼び出すことができるようですが、jupyter notebook上から%matplotlib inlineは使えませんでした。

f:id:takeshi0406:20160625011940p:plain

Pythonからロードしたライブラリで補完はまだできないみたいです…。

パイプ演算子ラムダ式、部分適用

RとPythonプログラマーにとって気になるのが、パイプ演算子ラムダ式です。

range(10) |> map$((x) -> x**2) |> list |> print
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Pythonではlambda x: x**2と書くべきところを、Coconutでは(x) -> x**2と書けます。シンプル!

$が何なのか最初はわからなかったのですが、部分適用の記号でした(本体はfunctoolspartialみたいです)。上の例だと、map関数の第一引数の関数に2乗するラムダ式を適用し、イテレータを1つ引数とする関数を作って、それにパイプで値を渡しているようです。

関数型言語の入門書によくある感じで部分適用で遊んでみました。

print($)
# <class 'functools.partial'>

# xとyを足す関数を宣言
add = (x, y) -> x + y

# 1を部分適用する
add1 = add$(1)

# 1足す2
print(add1(2))
# 3

どうやらRのパイプ演算子みたいに、「演算子の右側の値を第一引数に自動的に突っ込む」みたいな挙動では無いようです。

演算子で折り返すときには、Pythonと同様に\を付ける必要がありました。

range(10) |> map$((x) -> x**2) |> \
 list |> print

いわゆる「関数型言語」って機能はひととおり揃ってるみたい

個人的に真新しかったのはdestructuring assignmentという機能。日本語では「分割代入」というそうです。

最近読んでいる『7つの言語 7つの世界』でPrologに似たような機能を見た気がします。また、Javascriptにも同じ機能があるみたいです。

{"list": [0] + rest} = {"list": [0, 1, 2, 3]}
print(rest)
# [1, 2, 3]

末尾最適化とか関数合成とか、「関数型言語の人がPythonに不満を持ちそうな部分がちゃんと網羅されてるなー」って感じです。

また、「関数合成が楽にできるならパイプそんなにいらなくね?」とも思うんですがどうなんでしょう?最初のパイプの例を関数合成で書くとこんな感じです。

function = print .. list .. map$((x) -> x**2) 
function(range(10))
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

雑感

私は最初、Rのdplyr + パイプ演算子 とか、Rのpurrrのような、データ分析を関数型プログラミングで楽にするようなことを期待していましたが、今のところ補完やグラフのプロットは難しく、私が現状でそのまま分析ツールとして使うことは無さそうです。

しかし、今後jupyterとの連携や、エコシステムの整備によっては素晴らしいツールになる可能性もあります。dplythonライブラリみたいに、Rと似た感覚で使いたいという機運は高まってるみたいですし。

github.com

しかし、Pythonと互換性があって、関数型の機能をフルに使ってPythonより簡潔に書ける言語と考えるとかなり遊べそうですし、使い勝手も良さそうに感じました。Pythonみたいに、再帰を使うときに再帰呼び出しの限界を気にする必要はありません。

今後もうちょっと遊んで&注目していようと思います。