#概要
最近,たまたま調べていて見つけたPythonectという言語について入門したので書いてみます.
まだまだβ版の言語(環境?)なので,バグ等もあると思います.
#Pythonectって何?
Pythonect
http://docs.pythonect.org/en/latest/index.html
Pythonect is a new, experimental, general-purpose dataflow programming language based on Python. It provides both a visual programming language and a text-based scripting language. The text-based scripting language aims to combine the quick and intuitive feel of shell scripting, with the power of Python. The visual programming language is based on the idea of a diagram with “boxes and arrows”.
The Pythonect interpreter (and reference implementation) is a free and open source software written completely in Python, and is available under the BSD 3-Clause license.
PythonectはPythonをベースとした,新しい実験的な一般用途向けのデータフロープログラミング言語です.Pythonectはビジュアルプログラミング言語とテキストベースのスクリプト言語の両方を提供しています.テキストベースのスクリプト言語はPythonの力により,シェルスクリプトのように直感的で簡単に結合できることを目標としています.ビジュアルプログラミング言語は"箱"と"矢印"のダイアグラムのアイデアをもとにしています.
Pythonectのインタープリター(とリファレンス実装)は全てPythonで書かれており,自由なオープンソースで,BSD 3-Clause licenseの元で利用することができます.
ざっくりとPythonの構文を拡張してデータフロー言語向けに改造したものだと思っていただければいいです.
一応Diaというソフトでビジュアルプログラミングも出来るんですが,ここでは省略します.
#インストール方法
easy_installの場合
$ easy_install pythonect
pipの場合
$ pip install pythonect
とても簡単.
#HelloWorld!
$ pythonect
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[Pythonect 0.6.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> "HelloWorld!" -> print
<MainProcess:MainThread> : HelloWorld!
'HelloWorld!'
#基本構文
Pythonっぽいですが,Pythonじゃない要素のほうが多いです.
例えば,for,ifは使えないです.たぶん複文を構成するものは,ほぼ使えないです.
あとはpythonect専用構文がいくつかあります.
同期転送(Synchronous Forward)
>>> a=[1,2,3,4,5,6,7,8,9,10]
>>> a | print
<MainProcess:MainThread> : 1
<MainProcess:Thread-4576> : 2
<MainProcess:Thread-4577> : 3
<MainProcess:Thread-4576> : 4
<MainProcess:Thread-4577> : 5
<MainProcess:Thread-4576> : 6
<MainProcess:Thread-4577> : 7
<MainProcess:Thread-4576> : 8
<MainProcess:Thread-4577> : 9
<MainProcess:Thread-4576> : 10
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
という演算子でデータの中身を同期的に取り出します.かならず1,2,3,4,5,6,7,8,9,10という順番で取り出します.
##非同期転送(Asynchronous Forward)
>>> a -> print
<MainProcess:MainThread> : 1
<MainProcess:Thread-4797> : 3
<MainProcess:Thread-4796> : 2
<MainProcess:Thread-4797> : 4
<MainProcess:Thread-4796> : 5
<MainProcess:Thread-4796> : 7
<MainProcess:Thread-4797> : 6
<MainProcess:Thread-4796> : 8
<MainProcess:Thread-4797> : 9
<MainProcess:Thread-4796> : 10
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
->
という演算子でデータの中身を非同期で取り出します.上の同期転送の例では,
<MainProcess:MainThread> : 1
<MainProcess:Thread-4576> : 2
<MainProcess:Thread-4577> : 3
<MainProcess:Thread-4576> : 4
...
のように動作していましたが,->
で実行した場合は,
<MainProcess:MainThread> : 1
<MainProcess:Thread-4797> : 3
<MainProcess:Thread-4796> : 2
<MainProcess:Thread-4797> : 4
...
のように順不同になっていることが分かります.
##代入演算子
>>> [x=10] -> x -> print
<MainProcess:MainThread> : 10
10
pythonectの転送の演算子がついている間で,変数に値を入れることが出来ます.ただ,使いどころが結構難しいです.
##定義済み変数
###Current Value ( _ )
>>> range(3) -> print _
<MainProcess:MainThread> : 0
<MainProcess:Thread-1166> : 1
<MainProcess:Thread-1167> : 2
[0, 1, 2]
_
はフローからデータを1つだけ取り出します.
###All Current Values ( _! )
>>> xrange(3) | sum(_!) | print
<MainProcess:Thread-521> : 3
3
_!
フローから値を全部取り出します.
したがって,動作としては
>>> sum(xrange(3))
3
と同じになります.
##フロー制御
pyconnectはフロー制御の間にboolの判別式があり,その値がTrueになったときのみ,その次の処理が行われます.
したがって,True
の場合,全部出力されます.
>>> range(3) | True | print
<MainProcess:MainThread> : 0
<MainProcess:Thread-1311> : 1
<MainProcess:Thread-1312> : 2
[0, 1, 2]
逆に,False
の場合,何も出力されません.
>>> range(3) | False | print
[False, False, False]
簡単な応用の例としては,こんな感じ.フローの値が1の時だけ出力.
>>> range(3) | _ == 1 | print
<MainProcess:Thread-1491> : 1
[False, 1, False]
一本のフローを2つのフローに分解することもできます.
>>> range(3) | [[[_ == 0] -> print 'Zero' ],[ print _ ]]
<MainProcess:Thread-7596> : 0
<MainProcess:MainThread> : Zero
<MainProcess:Thread-7621> : 1
<MainProcess:Thread-7641> : 2
[0, 0, False, 1, False, 2]
一方のフローでは0かどうかを判断し,真であればZeroを出力します.あとは無条件でフローから得た数字を出力します.
あとは,珍しくswitch文っぽいものがあります.ただすごく古典的でswitchでdictのキーの一致を見てるようです.
>>> range(10) | _ % 3 | {0: 'Zero' , 1 : 'One' , 2 : 'Two'} | print
<MainProcess:MainThread> : Zero
<MainProcess:Thread-4146> : One
<MainProcess:Thread-4147> : Two
<MainProcess:Thread-4146> : Zero
<MainProcess:Thread-4147> : One
<MainProcess:Thread-4146> : Two
<MainProcess:Thread-4147> : Zero
<MainProcess:Thread-4146> : One
<MainProcess:Thread-4147> : Two
<MainProcess:Thread-4146> : Zero
##プロセス化
以下のコードを見てください.
>>> range(3) -> print
<MainProcess:MainThread> : 0
<MainProcess:Thread-437> : 2
<MainProcess:Thread-436> : 1
[0, 1, 2]
<MainProcess:MainThread>
のような表示があります.これはPythonのプロセス中のスレッドで動いていることが分かります.このようにスレッドで動作しているスクリプトを &
をつけることによってプロセス起動にすることが出来ます.
>>> range(3) -> print &
<PID #29242> : 0
<PID #29266> : 2
<PID #29268> : 1
[0, 1, 2]
#FizzBuzzを書いてみる
range(1,20)
-> [ x = _ ]
-> [ flag3 = _ % 3 == 0 ]
-> [ flag5 = _ % 5 == 0 ]
-> [
[ flag3 and flag5 -> print "{0} FizzBuzz".format(x)],
[ flag3 and not flag5 -> print "{0} Fizz".format(x) ],
[ not flag3 and flag5 -> print "{0} Buzz".format(x) ],
[ not flag3 and not flag5 -> print "{0}".format(x) ]
]
すごく,それっぽいコードだと思いますが,動きません.
実際に動くコードは,こんな感じ
$ cat ~/FizzBuzz.p2y | tr -d "\n"
range(1,20) -> [ x = _ ] -> [ flag3 = _ % 3 == 0 ] -> [ flag5 = _ % 5 == 0 ] -> [ [ flag3 and flag5 -> print "{0} FizzBuzz".format(x)], [ flag3 and not flag5 -> print "{0} Fizz".format(x) ], [ not flag3 and flag5 -> print "{0} Buzz".format(x) ], [ not flag3 and not flag5 -> print "{0}".format(x) ] ]
このあたりが,ちょっとなぁと思う点だったりします.たぶんまだパースが甘い感じです.
実際に動かすとこんな感じ.
$ cat ~/FizzBuzz.p2y | tr -d "\n" | pythonect
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[Pythonect 0.6.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> <MainProcess:Thread-41> : 1
<MainProcess:Thread-171> : 3 Fizz
<MainProcess:Thread-172> : 2
<MainProcess:Thread-374> : 5 Buzz
<MainProcess:Thread-371> : 4
<MainProcess:Thread-571> : 6 Fizz
<MainProcess:Thread-572> : 7
<MainProcess:Thread-773> : 9 Fizz
<MainProcess:Thread-770> : 8
<MainProcess:Thread-975> : 10 Buzz
<MainProcess:Thread-971> : 11
<MainProcess:Thread-1171> : 12 Fizz
<MainProcess:Thread-1172> : 13
<MainProcess:Thread-6> : 15 FizzBuzz
<MainProcess:Thread-1371> : 14
<MainProcess:Thread-1570> : 16
<MainProcess:Thread-1573> : 17
<MainProcess:Thread-1770> : 18 Fizz
<MainProcess:Thread-1773> : 19
[False, False, False, 1, False, False, False, 2, False, 3, False, False, False, False, False, 4, False, False, 5, False, False, 6, False, False, False, False, False, 7, False, False, False, 8, False, 9, False, False, False, False, 10, False, False, False, False, 11, False, 12, False, False, False, False, False, 13, False, False, False, 14, 15, False, False, False, False, False, False, 16, False, False, False, 17, False, 18, False, False, False, False, False, 19]
だいぶ雰囲気の違うコードですね.純粋なFizzBuzzは1,2,Fizz,4,Buzz...と順番に出力されるものなので,完全に一致ではないですが,まぁ...データフロー言語バージョンということで.
#良い点
- データフロー言語として動く(世の中に,あんまりきちんと動くデータフロー言語がない)
- Pythonのライブラリが使えたりするのは結構うれしい.
- あとすごく読みやすいし,綺麗で書きやすかった.
#悪い点
- 普通のPythonと親和性が悪い
- フローで処理した結果を変数に代入できない
- 他のifやfor,defの構文が使えない
- フロー処理の可読性が悪い
- 書き方自体は非常にシンプルだが,インデントできない
- listを返すフロー処理が非常に書きにくい
- フローの値をトップか,全体かしか取れない.(2個,3個とかとれない)
- 名前付きパイプが欲しい.
#感想
結構面白いなーと思いながら,やっぱりもうちょい機能が欲しい.というかほかのPython構文が使えたらなーと思っちゃいますね.普通にビルドインの機能として使えたら,ちょっと常用してみようかな?と思う.
言語としては一番シェルスクリプトに近いなーと思いながら,ちょっと前に話題にあがったStreemとも近いですね.
最近個人的な興味が強固な型システムを持つ言語なので,その辺の言語でもこういう感じの言語が出てきてくれたらなーと思います.