なぜ開発者は機能的なプログラミングに恋をするのですか?

機能プログラミング(FP)は60年前から存在していますが、これまでは常にかなり狭い範囲で使用されてきました。 Googleのような世界を変える企業はコアコンセプト依存していますが、平均的な現代のプログラマーは、この現象についてほとんど知りません。



しかし、それはすぐに変わるでしょう。ますます多くのFPの概念がJavaPythonなどの言語に統合されています。そして、Haskellのようなより現代的な言語は完全に機能しています。 機能的なプログラミングを簡単に言えば







、これは不変の変数を操作するための関数の作成です。対照的に、オブジェクト指向のプログラミングでは、比較的一定の関数セットが使用され、プログラマーは主に既存の変数の変更と新しい変数の作成に忙しくなります。



FPは、その性質上、データ分析機械学習などの緊急の問題を解決するのに適してます。これは、オブジェクト指向のプログラミングに別れを告げて、機能的なプログラミングに完全に切り替える必要があるという意味ではありません。現代のプログラマーがFPの基本原則を知っていることは、単に有用です。これにより、FPの基本原則を、自分に役立つ場所に適用できるようになります。



機能プログラミングとは、副作用を排除することです



機能プログラミングの原則を理解するには、まず「機能」とは何かを理解する必要があります。退屈に思えるかもしれませんが、最終的には、一見何が知覚できないかを確認できます。それでは、関数について話しましょう。



関数は、簡単に言えば、渡された入力を出力データに変換し、それを呼び出し元に返すエンティティです。確かに、実際、すべてがそれほど単純に見えるとは限りません。次のPython関数を見てください。



def square(x):
    return x*x


この機能は非常に簡単です。それxはおそらくタイプint、そしておそらくタイプfloatまたはの1つの引数を取り、それdoublex二乗した結果を返します



そして、ここに別の機能があります:



global_list = []
def append_to_list(x):
    global_list.append(x)


一見、x式がないので、なんらかの型を受け入れて何も返さないように見えますreturnしかし、結論に飛びつくのはやめましょう!



変数が事前に宣言されていないと、関数は正常に機能しませんglobal_listこの関数の結果は、に格納されている変更されたリストglobal_listです。global_list関数の入力に渡される値として宣言されていなくて、この変数は関数が呼び出された後に変更されます。



append_to_list(1)
append_to_list(2)
global_list


前の例から関数を数回呼び出した後、global_listリストは空のリストではなく、リストになり[1,2]ます。これにより、実際には、リストは関数の入力に提供された値であると言えますが、これは関数が宣言されたときに固定されることはありません。これは問題になる可能性があります。



機能を宣言するときの不正直



これらの暗黙の入力値または出力値には、正式な名前があります:副作用。ここでは非常に単純な例を使用していますが、より複雑なプログラムでは、副作用が実際の複雑化につながる可能性があります。



関数をどのようにテストするかを考えてくださいappend_to_listその宣言の最初の行を読んで、それに何らかの値を渡してテストする必要があることを見つけるだけでは十分ではありませんx代わりに、関数コード全体を読み取り、そこで何が起こっているのかを正確に把握し、変数を宣言してからglobal_list、関数をテストする必要があります。私たちの単純な例では、何千行ものコードで構成されるプログラムでは、特別な問題を引き起こさないように見えますが、まったく異なって見えます。



幸い、上記の問題は簡単に修正できます。関数の入力に正確に何を入力するかを指定するときは、正直である必要があります。関数の次のバージョンは、前のバージョンよりもはるかに良く見えます。



newlist = []
def append_to_list2(x, some_list):
    some_list.append(x)
append_to_list2(1,newlist)
append_to_list2(2,newlist)
newlist


このコードはあまり変更されていません。その結果、内の関数はnewlist、中に以前のようにglobal_list、結局のところ[1,2]、および他のすべては前と同じように見えます。



ただし、このコードに1つの重要な変更を加えました。副作用を取り除きました。そして、これはとても良いです。



つまり、関数宣言の最初の行を読み取った後、それがどの入力データを処理するかを正確に知ることができます。その結果、プログラムが期待どおりに動作しない場合、プログラムに含まれるすべての関数をテストして、正しく機能していない関数を見つけるのは簡単です。純粋な機能は保守が容易です。



機能プログラミングは純粋な関数を書いています



宣言されたときに、何が必要で何が返されるかを明確に示す関数は、副作用のない関数です。副作用のない機能は純粋な機能です。



これが機能プログラミングの非常に簡単な定義です。これは、純粋な関数のみで構成されるプログラムを作成しています。純粋な関数は、渡されたデータを変更することはなく、新しい関数を作成して返すだけです。 (前の例で少しごまかしたことに注意してください。これは機能プログラミングの精神で書かれていますが、その中で関数はグローバル変数リストを変更します。しかし、ここではFPの基本原則のみを調べているので、それを実行しました。必要に応じて、ここで実行できます。純粋な関数のより厳密な例を見つけてください。)



さらに、純粋な関数を操作する場合、入力と同じデータを受け取る関数は、常に同じ出力を生成することが期待できます。また、純粋でない関数は、ある種のグローバル変数に依存する場合があります。その結果、同じ入力を受け取った場合、グローバル変数の値に応じて異なる結果が生成される可能性があります。この事実は、コードのデバッグと保守を大幅に複雑にする可能性があります。



副作用を検出するための簡単なルールがあります。純粋な関数を宣言するときは、入力と戻りとして受け取るものを明確に定義する必要があるため、何も受け入れたり返さなかったりする関数はクリーンではありません。機能的なプログラミング手法をプロジェクトに組み込むことにした場合、おそらく最初にやりたいことは、関数の宣言を確認することです。



機能的なプログラミングではないもの



▍マッピングおよび削減機能



ループは、機能プログラミングとは何の関係もないメカニズムです。次のPythonループを見てください。



integers = [1,2,3,4,5,6]
odd_ints = []
squared_odds = []
total = 0
for i in integers:
    if i%2 ==1
        odd_ints.append(i)
for i in odd_ints:
    squared_odds.append(i*i)
for i in squared_odds:
    total += i


このコードの助けを借りて、私たちは単純な問題を解決しますが、それはかなり長いことが判明しました。さらに、グローバル変数がここで変更されるため、機能しません。



そして今-このコードの別のバージョン:



from functools import reduce
integers = [1,2,3,4,5,6]
odd_ints = filter(lambda n: n % 2 == 1, integers)
squared_odds = map(lambda n: n * n, odd_ints)
total = reduce(lambda acc, n: acc + n, squared_odds)


これは完全に機能するコードです。短いです。多くの配列要素を繰り返す必要がないため、より高速です。そして、あなたが機能を理解していればfiltermapそしてreduce、それはこのコードがはるかに難しい理解するためにループが使用される1以上であることが判明します。



これは、どの機能コードでもmapreduceおよび他のそのような機能が使用されることを意味するものではありませんそして、これは、そのような機能を扱うために、機能的なプログラミングを知る必要があるという意味ではありません。重要なのは、これらの関数はループを取り除くときによく使用されるということです。



▍ラムダ機能



人々が機能プログラミングの歴史について話すとき、彼らはしばしばラムダ関数の発明について話すことから始めます。しかし、ラムダ関数は間違いなく機能プログラミングの基礎ですが、FPの主な原因ではありません。



ラムダ関数は、機能的なスタイルでプログラムを作成するために使用できるツールです。ただし、これらの関数はオブジェクト指向のプログラミングでも使用できます。



▍静的タイピング



上記の例は静的に型付けされていません。それにもかかわらず、それは機能的なコードサンプルです。



静的型付けはコードにセキュリティの追加レイヤーを追加しますが、機能コードを作成するための要件ではありません。ただし、これは機能的なプログラミングスタイルへの優れた追加になる可能性があります。



一部の言語は、他の言語よりも機能的なスタイルでプログラミングするのが簡単であることに注意してください。



一部の言語は他の言語よりも「機能的」です



▍Perl



Perlには、他のほとんどの言語とは一線を画す副作用に対処するアプローチがあります。つまり、$_言語の主要な機能の1つのレベルに副作用をもたらす「魔法の変数」があります。Perlにはメリットがありますが、私はこの言語で機能的なプログラミングをしようとはしません。



▍Java



機能的なJavaコードを書いて頑張ってください。それはあなたを傷つけることはありません。まず、キーワードはコードの半分を占めますstatic第二に、ほとんどのJavaプログラマーはあなたのコードを誤解と呼ぶでしょう。



これは、Javaが悪い言語であることを意味するものではありません。ただし、機能プログラミングが優れているような問題を解決するようには設計されていません。たとえば、データベース管理や機械学習の分野からのアプリケーションの開発に使用されます。



▍スカラ



Scalaは興味深い言語です。その目標は、機能的プログラミングとオブジェクト指向プログラミングを統合することです。これがあなたにとって奇妙に思える場合は、あなたが一人ではないことを知ってください。結局のところ、機能プログラミングは副作用を完全に排除することを目的としています。そして、オブジェクト指向のプログラミングは、副作用をオブジェクトに限定することです。



このことを念頭に置いて、多くの開発者は、Scalaをオブジェクト指向から機能プログラミングへの移行を支援する言語と見なしていると言えます。Scalaを使用すると、将来、完全に機能するプログラミングスタイルへの移行が容易になります。



▍Python



Pythonでは機能的なプログラミングスタイルが推奨されています。これは、各関数がデフォルトで少なくとも1つのパラメーターを持っているという事実を考慮に入れると理解できます- selfこれは、多くの点で、「ZenPython」の精神に基づいています。「明示的は暗黙的よりも優れています」。



▍Clojure



言語の作成者によると、Clojureは約80%機能しています。デフォルトでは、すべての値は不変です。しかし、これはまさに機能的なコードを書くために必要なものです。ただし、不変の値が配置されている可変コンテナを使用することで、これを回避できます。そして、コンテナから値を抽出すると、再び不変になります。



▍ハスケル



これは、完全に機能し、静的に型付けされた数少ない言語の1つです。開発プロセスで使用すると、機能メカニズムの実装に時間がかかりすぎるように見えるかもしれませんが、そのような努力は、コードのデバッグ中に何度も報われるでしょう。この言語は他の言語ほど簡単に学ぶことはできませんが、学ぶことは間違いなく価値のある投資です。



結果



今はまだビッグデータの時代の始まりであることに注意する必要があります。ビッグデータは、単独ではなく、友人と、機能的なプログラミングでやってくる。



オブジェクト指向のプログラミングと比較した場合、機能的なプログラミングは依然としてニッチな現象です。ただし、Pythonや他の言語でのFP原則の統合を重要な現象と見なすと、機能プログラミングが人気を集めていると結論付けることができます。



そして、これは理にかなっています。なぜなら、機能プログラミングは、データベースの操作、並列プログラミング、機械学習の分野でうまく機能するからです。そして過去10年間で、これらすべてが増加しています。



オブジェクト指向のコードには無数の利点がありますが、機能的なコードを見落としてはなりません。プログラマーがFPの基本原則のいくつかを学んだ場合、ほとんどの場合、これで彼の専門レベルを向上させることができます。この知識は、彼が「機能的な未来」に備えるのにも役立ちます。



機能プログラミングについてどう思いますか?






All Articles