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

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

前のパーツ:パート1

フレンドリーリマインダー

コードをゆっくり読んでください。先に進む前に必ず理解してください。各セクションは、前のセクションの上に構築されます。

急いでいると、後で重要になるいくつかのニュアンスを逃す可能性があります。

リファクタリング

リファクタリングについて少し考えてみましょう。 Javascriptコードは次のとおりです。

関数validateSsn(ssn){
    if(/^\d{3}-\d{2}-\d{4}$/.exec(ssn))
        console.log( 'Valid SSN');
    他に
        console.log( 'Invalid SSN');
}
関数validatePhone(phone){
    if(/^\(\d{3}\)\d{3}-\d{4}$/.exec(phone))
        console.log( 'Valid Phone Number');
    他に
        console.log( 'Invalid Phone Number');
}

私たちはこのようなコードをこれまでもこれまでも作成してきましたが、これら2つの機能は実質的に同じであり、わずかな違いがあるだけであることに気付き始めています(太字で表示)。

validateSsnをコピーして貼り付けて編集してvalidatePhoneを作成する代わりに、1つの関数を作成し、貼り付け後に編集したものをパラメーター化する必要があります。

この例では、値、正規表現、印刷されるメッセージ(少なくとも印刷されるメッセージの最後の部分)をパラメーター化します。

リファクタリングされたコード:

function validateValue(value、regex、type){
    if(regex.exec(value))
        console.log( 'Invalid' + type);
    他に
        console.log( 'Valid' + type);
}

古いコードのパラメーターssnとphoneは、値で表されるようになりました。

正規表現/ ^ \ d {3}-\ d {2}-\ d {4} $ /および/ ^ \(\ d {3} \)\ d {3}-\ d {4} $ /は正規表現で表されます。

最後に、メッセージ「SSN」および「電話番号」の最後の部分はタイプ別に表されます。

1つの機能を持つことは、2つの機能を持つことよりもはるかに優れています。さらに悪いことに、3、4、または10の機能。これにより、コードがクリーンで維持可能になります。

たとえば、バグがある場合は、コードベース全体を検索して、この関数が貼り付けられて変更されている可能性のある場所を見つけるのではなく、1か所で修正するだけで済みます。

しかし、次のような状況があるとどうなりますか:

関数validateAddress(address){
    if(parseAddress(address))
        console.log( 'Valid Address');
    他に
        console.log( 'Invalid Address');
}
関数validateName(name){
    if(parseFullName(name))
        console.log( 'Valid Name');
    他に
        console.log( 'Invalid Name');
}

ここで、parseAddressおよびparseFullNameは、文字列を取得し、解析された場合にtrueを返す関数です。

これをどのようにリファクタリングしますか?

さて、以前と同じように、アドレスと名前に値を使用し、「アドレス」と「名前」に入力できますが、正規表現が使用されていた関数があります。

関数をパラメータとして渡すことができれば…

高階関数

多くの言語は、関数をパラメーターとして渡すことをサポートしていません。ある人はそうしますが、彼らはそれを簡単にしません。

関数型プログラミングでは、関数は言語の第一級市民です。つまり、関数は単なる別の値です。

関数は単なる値であるため、パラメーターとして渡すことができます。

Javascriptは純粋な関数型言語ではありませんが、それを使用していくつかの機能的な操作を行うことができます。したがって、解析関数をparseFuncというパラメーターとして渡すことにより、最後の2つの関数が単一の関数にリファクタリングされます。

関数validateValueWithFunc(value、parseFunc、type){
    if(parseFunc(value))
        console.log( 'Invalid' + type);
    他に
        console.log( 'Valid' + type);
}

新しい関数は、高階関数と呼ばれます。

高階関数は、パラメーターとして関数を受け取るか、関数を返すか、その両方を行います。

これで、前の4つの関数に対して高階関数を呼び出すことができます(一致が見つかった場合、Regex.execは真実の値を返すため、これはJavascriptで機能します)。

validateValueWithFunc( '123-45-6789'、/^\d{3}-\d{2}-\d{4}$/.exec、 'SSN');
validateValueWithFunc( '(123)456-7890'、/^\(\d{3}\)\d{3}-\d{4}$/.exec、 'Phone');
validateValueWithFunc( '123 Main St.'、parseAddress、 'Address');
validateValueWithFunc( 'Joe Mama'、parseName、 'Name');

これは、ほぼ同じ4つの機能を持つよりもはるかに優れています。

しかし、正規表現に注意してください。それらは少し冗長です。コードを整理してコードを整理しましょう。

var parseSsn = /^\d{3}-\d{2}-\d{4}$/.exec;
var parsePhone = /^\(\d{3}\)\d{3}-\d{4}$/.exec;
validateValueWithFunc( '123-45-6789'、parseSsn、 'SSN');
validateValueWithFunc( '(123)456-7890'、parsePhone、 'Phone');
validateValueWithFunc( '123 Main St.'、parseAddress、 'Address');
validateValueWithFunc( 'Joe Mama'、parseName、 'Name');

良いですこれで、電話番号を解析するときに、正規表現をコピーして貼り付ける必要がなくなりました。

しかし、parseSsnとparsePhoneだけでなく、解析する正規表現がもっとあると想像してください。正規表現パーサーを作成するたびに、.execを最後に追加することを忘れないでください。そして私を信じてください、これは忘れがちです。

exec関数を返す高次関数を作成することにより、これを防ぐことができます。

関数makeRegexParser(regex){
    return regex.exec;
}
var parseSsn = makeRegexParser(/ ^ \ d {3}-\ d {2}-\ d {4} $ /);
var parsePhone = makeRegexParser(/ ^ \(\ d {3} \)\ d {3}-\ d {4} $ /);
validateValueWithFunc( '123-45-6789'、parseSsn、 'SSN');
validateValueWithFunc( '(123)456-7890'、parsePhone、 'Phone');
validateValueWithFunc( '123 Main St.'、parseAddress、 'Address');
validateValueWithFunc( 'Joe Mama'、parseName、 'Name');

ここで、makeRegexParserは正規表現を受け取り、文字列を受け取るexec関数を返します。 validateValueWithFuncは、文字列値を解析関数、つまりexecに渡します。

parseSsnとparsePhoneは、事実上、正規表現のexec関数である以前と同じです。

確かに、これはわずかな改善ですが、関数を返す高階関数の例を示すためにここに示されています。

ただし、makeRegexParserがはるかに複雑な場合、この変更を行うことの利点を想像できます。

関数を返す高階関数の別の例を次に示します。

関数makeAdder(constantValue){
    関数adder(value){
        return constantValue + value;
    };
}

ここには、constantValueを取り、adderを返すmakeAdderがあります。adderは、渡された値にその定数を追加する関数です。

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

var add10 = makeAdder(10);
console.log(add10(20)); // 30を印刷します
console.log(add10(30)); // 40を印刷します
console.log(add10(40)); // 50を印刷します

定数10をmakeAdderに渡して関数add10を作成します。makeAdderは、すべてに10を加算する関数を返します。

関数adderは、makeAddrが戻った後でもconstantValueにアクセスできることに注意してください。これは、adderが作成されたときにconstantValueがスコープ内にあったためです。

これがなければ、関数を返す関数はあまり役に立たないため、この動作は非常に重要です。したがって、それらがどのように機能し、この動作が何と呼ばれるかを理解することが重要です。

この動作はクロージャーと呼ばれます。

閉鎖

クロージャーを使用する関数の不自然な例を次に示します。

関数grandParent(g1、g2){
    var g3 = 3;
    return parent(p1、p2){
        var p3 = 33;
        return関数child(c1、c2){
            var c3 = 333;
            return g1 + g2 + g3 + p1 + p2 + p3 + c1 + c2 + c3;
        };
    };
}

この例では、子はその変数、親の変数、およびgrandParentの変数にアクセスできます。

親はその変数とgrandParentの変数にアクセスできます。

grandParentはその変数にのみアクセスできます。

(説明については、上記のピラミッドを参照してください。)

その使用例を次に示します。

var parentFunc = grandParent(1、2); // parent()を返します
var childFunc = parentFunc(11、22); // child()を返します
console.log(childFunc(111、222)); // 738を印刷
// 1 + 2 + 3 + 11 + 22 + 33 + 111 + 222 + 333 == 738

ここでは、grandParentが親を返すため、parentFuncは親のスコープを保持します。

同様に、childFuncは、親であるparentFuncが子を返すため、子のスコープを存続させます。

関数が作成されると、作成時のスコープ内のすべての変数は、関数の存続期間中アクセス可能です。関数への参照がある限り、関数は存在します。たとえば、childFuncがまだ参照している限り、子のスコープは存在します。

クロージャーとは、その関数への参照によって維持される関数のスコープです。

Javascriptでは、変数は変更可能であるため、クロージャーは問題があることに注意してください。つまり、変数は閉じられた時点から返された関数が呼び出される時点まで値を変更できることに注意してください。

ありがたいことに、関数型言語の変数は不変であり、この一般的なバグと混乱の原因を排除します。

私の脳!!!!

今のところ十分です。

この記事の以降の部分では、機能構成、カリー化、一般的な機能機能(マップ、フィルター、折り畳みなど)などについて説明します。

次:パート3

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

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

私のTwitter:@cscalfani