あなたは関数型プログラマになりたい(パート4)

関数型プログラミングの概念を理解するための最初の一歩を踏み出すことは、最も重要で、時には最も困難な手順です。しかし、そうである必要はありません。正しい視点ではありません。

前のパート:パート1、パート2、パート3

カレー

パート3を思い出すと、mult5の構成と追加(in)で問題が発生した理由は、mult5が1つのパラメーターを取り、addが2を取るためです。

これは、すべての機能を1つのパラメーターのみに制限することで簡単に解決できます。

私を信じて。思ったほど悪くはありません。

2つのパラメーターを使用するが、一度に1つのパラメーターのみを受け取るadd関数を記述するだけです。カリー化された関数により、これを行うことができます。

Curried Functionは、一度に1つのパラメーターのみを受け取る関数です。

これにより、mult5で構成する前に最初のパラメーターを追加できます。その後、mult5AfterAdd10が呼び出されると、addは2番目のパラメーターを取得します。

JavaScriptでは、addを書き換えることでこれを実現できます。

var add = x => y => x + y

このバージョンのaddは、現在1つのパラメーターを受け取り、後で別のパラメーターを受け取る関数です。

詳細に説明すると、add関数は1つのパラメーターxを受け取り、1つのパラメーターyを受け取る関数を返します。この関数は、最終的にxとyを追加した結果を返します。

これで、このバージョンのaddを使用してmult5AfterAdd10の作業バージョンを作成できます。

var compose =(f、g)=> x => f(g(x));
var mult5AfterAdd10 = compose(mult5、add(10));

構成関数は、2つのパラメーターfとgを取ります。次に、1つのパラメーターxを受け取る関数を返します。このパラメーターは、呼び出されると、xにgの後にfを適用します。

それで、私たちは正確に何をしましたか?さて、単純な古いadd関数をカリー化バージョンに変換しました。これにより、最初のパラメーター10を前もって渡すことができ、mult5AfterAdd10が呼び出されたときに最終パラメーターが渡されるため、追加がより柔軟になりました。

この時点で、Elmのadd関数をどのように書き換えるか疑問に思うかもしれません。結局のところ、そうする必要はありません。 Elmおよび他の関数型言語では、すべての関数は自動的にカリー化されます。

したがって、追加機能は同じように見えます。

x y =を追加
    x + y

これが、mult5AfterAdd10がパート3で書き戻される方法です。

mult5AfterAdd10 =
    (mult5 << 10を追加)

構文的に言えば、エルムはカリー化や作曲などの機能的なものに最適化されているため、Javascriptなどの命令型言語に勝っています。

カリー化とリファクタリング

カリー化が効果を発揮するのは、リファクタリング中に、多くのパラメーターを使用して関数の一般化バージョンを作成し、それを使用してより少ないパラメーターで特殊バージョンを作成する場合です。

たとえば、文字列を角かっこと二重角かっこで囲む次の関数がある場合:

ブラケットstr =
    「{」++ str ++「}」
doubleBracket str =
    「{{」++ str ++「}}」

使用方法は次のとおりです。

括弧付きジョー=
    ブラケット「ジョー」
doubleBracketedJoe =
    doubleBracket「ジョー」

ブラケットとdoubleBracketを一般化できます:

generalBracketプレフィックスstrサフィックス=
    プレフィックス++ str ++サフィックス

しかし、今では、generalBracketを使用するたびに、かっこを渡す必要があります。

括弧付きジョー=
    generalBracket "{" "Joe" "}"
doubleBracketedJoe =
    generalBracket "{{" "Joe" "}}"

私たちが本当に欲しいのは、両方の長所です。

generalBracketのパラメーターを並べ替えると、関数がカリー化されているという事実を活用して、bracketとdoubleBracketを作成できます。

generalBracketプレフィックスサフィックスstr =
    プレフィックス++ str ++サフィックス
ブラケット=
    generalBracket "{" "}"
doubleBracket =
    generalBracket "{{" "}}"

静的である可能性が最も高いパラメーター、つまりプレフィックスとサフィックスを最初に配置し、最後に変更する可能性が最も高いパラメーター、つまりstrを配置することで、generalBracketの特殊バージョンを簡単に作成できることに注意してください。

カレーを完全に活用するには、パラメーターの順序が重要です。

また、bracketとdoubleBracketはポイントフリー表記で書かれていることに注意してください。つまり、strパラメーターが暗黙指定されています。ブラケットとdoubleBracketはどちらも、最終パラメーターを待機する関数です。

これで、前と同じように使用できます。

括弧付きジョー=
    ブラケット「ジョー」
doubleBracketedJoe =
    doubleBracket「ジョー」

しかし今回は、一般化されたカリー化された関数generalBracketを使用しています。

一般的な機能

関数型言語で使用される3つの一般的な関数を見てみましょう。

しかし、まず、次のJavascriptコードを見てみましょう。

for(var i = 0; i 

このコードには大きな問題が1つあります。バグではありません。問題は、このコードが定型コード、つまり何度も何度も記述されるコードであるということです。

Java、C#、Javascript、PHP、Pythonなどの命令型言語でコーディングすると、この定型コードを他のどの言語よりも多く書くことになります。

それが問題です。

それで殺しましょう。関数(またはいくつかの関数)に入れて、forループを二度と書きません。まあ、ほとんど決して。少なくとも関数型言語に移行するまで。

thingsという配列の変更から始めましょう。

var things = [1、2、3、4];
for(var i = 0; i 

うーん!可変性!

もう一度試してみましょう。今回は変更しません。

var things = [1、2、3、4];
var newThings = [];
for(var i = 0; i 

さて、私たちは物事を変異させませんでしたが、技術的にはnewThingsを変異させました。今のところ、これを見落とすつもりです。結局Javascriptを使用しています。関数型言語に移行すると、変更できなくなります。

ここでのポイントは、これらの関数がどのように機能するかを理解し、コードのノイズを減らすのに役立つことです。

このコードを取得して関数に入れましょう。古い配列の各値を新しい配列の新しい値にマップするため、最初の共通関数マップを呼び出します。

var map =(f、array)=> {
    var newArray = [];
    for(var i = 0; i 

関数fが渡されることに注意してください。これにより、マップ関数が配列の各項目に対して必要な処理を実行できるようになります。

これで、以前のコードを書き換えてmap​​を使用することができます。

var things = [1、2、3、4];
var newThings = map(v => v * 10、things);

ほらforループはありません。そして読みやすく、それゆえに推論します。

技術的には、map関数にはforループがあります。ただし、少なくともその定型コードを記述する必要はありません。

次に、配列から物事をフィルタリングする別の一般的な関数を書きましょう。

var filter =(pred、array)=> {
    var newArray = [];
for(var i = 0; i 

述語関数predが、アイテムを保持する場合にTRUEを返すか、アイテムをトスする場合にFALSEを返すことに注意してください。

フィルタを使用して奇数をフィルタリングする方法は次のとおりです。

var isOdd = x => x%2!== 0;
var numbers = [1、2、3、4、5];
var oddNumbers = filter(isOdd、numbers);
console.log(oddNumbers); // [1、3、5]

新しいフィルター関数の使用は、forループを使用して手動でコーディングするよりもはるかに簡単です。

最後の共通機能は、reduceと呼ばれます。通常、リストを取得して単一の値に減らすために使用されますが、実際にはもっと多くのことができます。

この関数は通常、関数型言語ではfoldと呼ばれます。

var reduce =(f、start、array)=> {
    var acc = start;
    for(var i = 0; i 

reduce関数は、reduction関数、f、初期開始値、および配列を取ります。

リダクション関数fは、2つのパラメーター、配列の現在のアイテム、およびアキュムレーターaccを取ることに注意してください。これらのパラメーターを使用して、反復ごとに新しいアキュムレーターを生成します。最終反復からのアキュムレーターが返されます。

例は、その仕組みを理解するのに役立ちます。

var add =(x、y)=> x + y;
var values = [1、2、3、4、5];
var sumOfValues = reduce(add、0、values);
console.log(sumOfValues); // 15

add関数は2つのパラメーターを受け取り、それらを追加することに注意してください。 reduce関数は、2つのパラメーターを受け取る関数を想定しているため、これらは連携して機能します。

ゼロの開始値から開始し、合計する配列の値を渡します。 reduce関数の内部では、値を反復処理するときに合計が累積されます。最終的な累積値はsumOfValuesとして返されます。

これらの関数、map、filter、reduceのそれぞれは、定型的なforループを記述することなく、配列に対する一般的な操作操作を可能にします。

しかし、関数型言語では、再帰だけのループ構造がないため、さらに便利です。反復関数は、非常に役立つだけではありません。それらは必要です。

私の脳!!!!

今のところ十分です。

この記事の以降の部分では、参照整合性、実行順序、タイプなどについて説明します。

次:パート5

これが気に入ったら、下のをクリックして、他の人がこれを中に表示するようにします。

ElmのFunctional Programmingを使用してWebアプリの開発を学び、支援するWeb開発者のコ​​ミュニティに参加したい場合は、FacebookグループのLearn Elm Programming https://www.facebook.com/groups/learnelm/をご覧ください。

私のTwitter:@cscalfani