私はPythonが大好きです。 いいえ、本当です。これは、幅広いタスクに適した素晴らしい言語です。ここでは、オペレーティングシステム、あらゆる好みのWebフレームワーク、および科学計算とデータ分析用のライブラリを操作する必要があります。 しかし、Pythonの他に、関数型プログラミングが好きです。 この点でpythonは悪くありません。クロージャー、匿名関数があり、一般に、ここの関数はファーストクラスのオブジェクトです。 あなたはさらに何を求めることができますか? そして、偶然偶然、Pythonでコンパイルされた関数型言語であるCoconutに出会いました。 catの下でPythonとFPを愛するすべての人に尋ねます。
なに? Pythonでコンパイルする関数型言語ですか? しかし、なぜ、非常に多くの機能的な機能があり、追加したい場合 倒錯 、つまりモジュールtoolz.functoolz? しかし、簡単なタスクを見てみましょう。あるリストから数値の二乗を追加する必要があります。
l = [1, 2, 3, 4, 5]
可能な解決策
「額」の必須決定:
def sum_imp(lst): s = 0 for n in lst: s += n**2 return s
mapとreduceを使用する(気味悪い):
from functools import reduce from operator import add def sum_map_reduce(lst): return reduce(add, map(lambda n: n**2, lst))
リストジェネレーターの使用(pythonic-way):
def sum_list_comp(lst): return sum([n**2 for n in lst])
後者のオプションはそれほど悪くありません。 しかし、そのような場合、私は精神で何かを書きたいです
sum_sqr(lst) = lst |> map(n -> n**2) |> sum
はい、OCamlの場合と同じですが、厳密な入力は必要ありません(言語は動的です)。 しかし、ココナッツを使えば、本当にできると言ったらどうでしょうか? それを使って書く
sum_sqr(lst) = lst |> map$(n -> n**2) |> sum
(関数から(関数から))関数呼び出しなしで問題の完全な解決策を取得します。
特徴
言語の作者は、次の機能をPythonに追加すると書いています。
- パターンマッチング
- 代数データ型
- 破壊的な割り当て
- 部分適用(部分については知っていますが、以下で詳しく説明します)
- 遅延リスト(同じヘッド:: okamlaのテール)
- 機能構成
- 改善されたラムダ式の構文
- 関数の挿入記法
- パイプライン
- 末尾再帰の最適化(この問題についてのGuidoの意見は知られていますが、場合によってはそうしたいこともあります)
- 並列実行
また、言語がインタープリターモードで動作し、Pythonソースコードにコンパイルされ、Jupyterノートブックのカーネルとして使用できることにも注意してください(私自身はまだテストしていませんが、開発者が可能なことを書いています)。
それでは、可能性のいくつかをさらに詳しく見ていきましょう。 すべての例は、ココナッツ1.2.1でテストされています。
ラムダ式の構文
Pythonでラムダ式を書くのが私にとって唯一の苦痛ではないと確信しています。 できるだけ使用されないように特別に作成されたとさえ思います。 ココナッツは、匿名関数の定義を、私が望んでいるとおりに作成します。
(x -> x*2)(a) # , (lambda x: x*2)(a)
機能構成
ここでの関数の構成は、ほとんどHaskellのように見えます。
(f .. g .. h)(x) # , f(g(h(x)))
部分適用
functoolsモジュールには、固定引数を持つ関数を作成できる部分関数があります。 重大な欠点があります:位置引数は厳密に順番に置換する必要があります。 たとえば、数値を5乗する関数が必要です。 論理的には、パーシャルを使用する必要があります(関数を取り、引数の1つを修正するだけです!)
from functools import partial from operator import pow def partial5(lst): return map(lambda x: partial(pow(x, 5)), lst) # ! def lambda5(lst): return map(lambda x: pow(x, 5), lst) #
ココナッツは何を提供できますか? そして、ここに何があります:
def coco5(lst) = map$(pow$(?, 5), lst)
関数名の直後にある記号$は、その部分的な使用を示しています。 プレースホルダーとして使用されます。
パイプライン
関数型言語やよく知られているbashでもよく使用されるもう1つの単純な概念。 合計で4種類のパイプラインがあります。
パイプライン | 役職 | 使用例 | 説明 |
|> | 簡単な直接 | x |> f | f(x) |
<| | 単純な逆 | f <| x | f(x) |
| *> | マルチ引数ダイレクト | x | *> f | f(* x) |
<* | | マルチ引数リバース | f <* | x | f(* x) |
パターンマッチングと代数型
最も単純な場合、パターンマッチングは次のようになります。
match '' in '' if ' ': '' else: ''
セキュリティおよびその他のブロックが欠落している可能性があります。 この形式では、パターンマッチングはあまりおもしろくないので、ドキュメントの例を検討してください。
data Empty() data Leaf(n) data Node(l, r) Tree = (Empty, Leaf, Node) def depth(Tree()) = 0 @addpattern(depth) def depth(Tree(n)) = 1 @addpattern(depth) def depth(Tree(l, r)) = 1 + max([depth(l), depth(r)])
ご想像のとおり、Treeはさまざまなタイプのバイナリツリーノードを含む合計タイプであり、depth関数はツリーの深さを再帰的に計算するために使用されます。 addpatternデコレータを使用すると、テンプレートを使用してディスパッチできます。
最初の適切なテンプレートに応じて結果を計算する必要がある場合は 、キーワードcaseが入力されます。 以下にその使用例を示します。
def classify_sequence(value): ''' ''' out = "" case value: match (): out += "" match (_,): out += "" match (x,x): out += " "+str(x) match (_,_): out += "" match _ is (tuple, list): out += "" else: raise TypeError() return out
並列実行
Coconutのparallel_mapおよびconcurrent_mapは、concurrent.futuresのProcessPoolExecutorおよびThreadPoolExecutorの単なるラッパーです。 シンプルであるにもかかわらず、マルチプロセス/マルチスレッド実行のためのシンプルなインターフェイスを提供します。
parallel_map(pow$(2), range(100)) |> list |> print concurrent_map(get_data_for_user, all_users) |> list |> print
おわりに
JVMの下で.NetがF#を持っていることはいつもうらやましかった-Scala、Clojure、私は通常、JSでコンパイルされた関数型言語の数について黙っています。 私はついにPythonに似たものを見つけました。 ココナッツは、私が望んでいるとしても、広く流通しないと確信しています。 結局のところ、関数型プログラミングを使用すると、多くの問題を簡潔かつエレガントに解決できます。 多くの場合、コードの可読性を損なうことさえありません。
→ 言語サイト