functional-programming関数型プログラミングを始める


備考

機能プログラミングは、数学関数の評価として計算(したがってプログラム)をモデル化するプログラミングパラダイムです。それは計算能力に関する彼の研究でアロンゾ教会によって開発されたラムダ計算に根ざしています。

機能プログラミングにはいくつか興味深い概念があります。

  • 高次関数
  • 純度
  • 再帰
  • 怠惰
  • 参照透明度
  • カッシング
  • Functors
  • モナド
  • メモ化とテールコールの最適化
  • 機能ユニットテスト

関数型プログラミング言語の例としては、 LispHaskellScalaClojureなどがありますが、 PythonRJavascriptなど他の言語でもプログラムの機能的な記述が可能です。 Javaであっても、関数型プログラミングは、Java 8で導入されたLambda ExpressionsStream APIを使っています。

カッシング

カリングとは、複数の引数をとる関数を、それぞれが単一のパラメータしか持たない一連の関数に変換するプロセスです。カレー化は、部分的な適用と関連するが、同じではない。

JavaScriptで以下の関数を考えてみましょう:

var add = (x, y) => x + y
 

curryingの定義を使ってadd関数を書き直すことができます:

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

この新しいバージョンでは、単一のパラメータx を取り、単一のパラメータy をとる関数を返します。これは最終的にxy を加算した結果を返します。

var add5 = add(5)
var fifteen = add5(10) // fifteen = 15
 

もう1つの例は、文字列のまわりに角かっこを入れる次の関数がある場合です。

var generalBracket = (prefix, str, suffix) => prefix + str + suffix
 

さて、私たちがgeneralBracket を使用するgeneralBracket に、大括弧で渡す必要があります:

var bracketedJim = generalBracket("{", "Jim", "}") // "{Jim}"
var doubleBracketedJim = generalBracket("{{", "Jim", "}}") // "{{Jim}}"
 

また、括弧ではない文字列を渡すと、関数は依然として間違った結果を返します。それを修正しよう:

var generalBracket = (prefix, suffix) => str => prefix + str + suffix
var bracket = generalBracket("{", "}")
var doubleBracket = generalBracket("{{", "}}")
 

bracketdoubleBracket 両方が、最終パラメータを待つ関数になっていることに注意してください。

var bracketedJim = bracket("Jim") // "{Jim}"
var doubleBracketedJim = doubleBracket("Jim") // "{{Jim}}"
 

高次関数

高次関数は他の関数を引数として受け取り、結果として返す。関数型プログラミングのビルディングブロックを形成します。ほとんどの関数型言語は、例えば、何らかの形のフィルタ関数を持っています。これは高次関数であり、リストと述語(真または偽を返す関数)を引数として取る。

これらの機能を持たない関数は、しばしばfirst-order functions と呼ばれfirst-order functions

function validate(number,predicate) {
    if (predicate) {    // Is Predicate defined
        return predicate(number);
    }
    return false;
}
 

ここで、「述語」は、引数を含む条件をテストし、真または偽を返す関数です。

上記の例の呼び出しは次のとおりです。

validate(someNumber, function(arg) {
    return arg % 10 == 0;
    }
);
 

一般的な要件は、範囲内の数値を追加することです。高次関数を使用することによって、この基本機能を拡張して、各数値に変換関数を適用してから合計に含めることができます。

指定した範囲内のすべての整数を追加したい(Scalaを使用)

def sumOfInts(a: Int, b: Int): Int = {
  if(a > b) 0
  else a + sumOfInts(a+1, b)
}
 

指定した範囲内のすべての整数の正方形を追加したい

def square(a: Int): Int = a * a

def sumOfSquares(a: Int, b: Int): Int = {
  if(a > b) 0
  else square(a) + sumOfSquares(a + 1, b)
}
 

これらの事柄に共通する点は1つあり、各引数に関数を適用してから追加したいということです。

両方を行う高次関数を作成しましょう:

def sumHOF(f: Int => Int, a: Int, b: Int): Int = {
  if(a > b) 0
  else f(a) + sumHOF(f, a + 1, b)
}
 

あなたは次のように呼び出すことができます:

def identity(a: Int): Int = a

def square(a: Int): Int = a * a
 

sumOfIntssumOfSquare は次のように定義できることに注意してsumOfInts

def sumOfInts(a: Int, b: Int): Int = sumHOF(identity, a, b)

def sumOfSquares(a: Int, b: Int): Int = sumHOF(square, a, b)
 

この簡単な例からわかるように、高次関数はより一般化された解を提供し、コードの重複を減らします。

私はScala By ExampleをMartin Oderskyが参考にしました。

不変性

伝統的なオブジェクト指向言語では、 x = x + 1 は単純で合法的な表現です。しかし、Functional Programmingでは、それは違法です。

Functional Programmingには変数は存在しません。保存された値は、歴史のためだけに変数と呼ばれます。実際、それらは定数です。一度x が値をとると、それは人生の価値です。

したがって、 変数定数であれば、その値をどのように変更できますか?

関数型プログラミングは、値を変更してレコードのコピーを作成することによって、レコード内の値の変更を処理します。

たとえば、以下のようにします。

var numbers = [1, 2, 3];
numbers[0] += 1; // numbers = [2, 2, 3];
 

あなたがやる:

var numbers = [1, 2, 3];
var newNumbers = numbers.map(function(number) {
    if (numbers.indexOf(number) == 0)
        return number + 1
    return number
});
console.log(newNumbers) // prints [2, 2, 3]
 

また、Functional Programmingにはループがありません。ルーピングを避けるために、 mapfilterreduce ような再帰関数または高次関数を使用します。

JavaScriptで簡単なループを作成しましょう:

var acc = 0;
for (var i = 1; i <= 10; ++i)
    acc += i;
console.log(acc); // prints 55
 

acc のライフタイムをグローバルからローカルに変えることで、もっとうまくやっていくことができます:

function sumRange(start, end, acc) {
    if (start > end)
        return acc;
    return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); // 55
 

変数やループは、よりシンプルで安全で読みやすいコードを意味しません(特に、デバッグやテストの際には、数多くのステートメントの後にx の値を心配する必要はありません。

純粋な関数

純粋な関数は自己完結型であり、副作用はありません。入力が同じである場合、純粋な関数は常に同じ出力値を返します。

次の関数は純粋です。

function pure(data) {
    return data.total + 3;
}
 

ただし、この関数は外部変数を変更するため純粋ではありません。

function impure(data) {
    data.total += 3;
    return data.total;
}
 

例:

data = {
    total: 6
};

pure(data);   // outputs: 9
impure(data); // outputs: 9 (but now data.total has changed)
impure(data); // outputs: 12