関数式プログラミングの詳細


関数式プログラミングには,純粋関数,高次関数,一等関数の3つの異なる解読方式がある.本文はそれぞれこの3つの概念、応用と連絡を詳しく理解する.

じゅんかんすう


定義:
1.同じ入力で必ず同じ出力が発生する
2.計算中に副作用が発生しない
上記の2つの条件を満たすと,この関数は純粋な関数であると述べる.
純粋な関数、すなわち数学的な意味での関数であり、計算ステップの詳細ではなく、データ間の変換(マッピング)関係を表す.数学関数の定義:
関数は通常、ドメインを定義します.
X、値域
Yと、ドメインから値ドメインへのマッピングの定義
f (
f:X->Y)組成.
純粋な関数は、書かれた関数を完全に制御する能力を持っています.純粋な関数の結果は入力パラメータにのみ依存し、外部環境の影響を受けない必要があります.同時に純関数は計算結果の過程においても外部環境に影響(汚染)せず,すなわち副作用を生じない.
関数の組合せ
純粋な関数定義の2つの条件は,その(計算過程)が外部から完全に隔離されていることを保証し,これも関数の組合せの基礎である.
関数の組合せのすべての関数が純粋な関数である限り、私たちが組み合わせた新しい関数は純粋な関数です.純粋な関数を用いて組み合わせた新しい関数に対して,大量のセルテストを必要とせずに数学的にその正確性を実証(導出)することができる.
関数の組合せに非純関数を導入すると、組合せられた関数全体が非純関数になります.関数の組合せをパイプの接合にたとえると、パイプを構成する任意の小節が漏れたり外部に注入されたりすれば、パイプ全体の完全な制御を失うことになります.
関数の組合せを実現するには、連続性を満たす必要があります.
純粋な関数は定義ドメインから値ドメインへのマッピングと見なすことができるため、結合される関数では、前の関数の値ドメインが次の関数の定義ドメイン、すなわち前の関数の出力(タイプ)が次の入力(タイプ)に等しい必要があります.
f:X->Yとg:Y->Zの2つの関数があると仮定し、codomain(f) = domain(g)の場合にのみfとgを組み合わせることができます.
参照の透過性とキャッシュ
プログラム全体の動作を変更せずに、その中のコードの1つを実行結果に置き換えることができれば、このコードは透明であると言います.
したがって、透明なコード(関数)を参照するセグメントが実行され、同じパラメータに対して常に同じ結果が与えられます.このような関数(コード)も純関数と呼ぶ.
透明な一般的なアプリケーション、すなわち関数キャッシュを参照します.すでに実行した関数の入力値をキャッシュすることができます.次に呼び出すときは、入力値が同じであれば、計算プロセスを直接スキップし、計算結果の代わりにキャッシュ結果を返します.
関数キャッシュの実装は閉パケットに依存し,閉パケットの実装は高次関数に依存し,高次関数の実装は一等関数に依存する.私たちはこの依存チェーンに従って、中から外へ順番に説明します.

一等関数(First Class Functions)


プログラム言語は基本要素の使用方法を制限し、最小限の制限を持つ要素は一等公民と呼ばれ、その持つ「権利」は以下の通りである.
変数を使用して名前を付けることができます.
パラメータとして関数に提供できます.
関数を結果として返すことができます.
データ構造に含めることができます.
一見、number、array、objectなどのプログラムの基本的なデータ構造が一等公民であることをまず考えるべきだ.関数も一等公民と見なされる場合、通常のデータのように変数を使用して名前を付けたり、パラメータや戻り値として使用したり、データ構造に含めたりすることができます.ここで関数とデータの境界はそれほどはっきりしなくなり始めた.関数は一等公民と見なされた後、その能力と適用範囲は大きく拡大された.
次にJavaScriptを使用して、上の第1条と第4条の「権利」について説明します.2、3つ目は高次関数と密接に関連しており、次の節の高次関数で説明します.
変数名の使用
const square = x => x * x

上記のコードは、平方値を求める関数を定義し、square変数に割り当てます.
データ構造に含めることができます
RamdaにはAPI:evolveがあり、最初のパラメータは属性値が関数のオブジェクトです.evolve関数は「処理対象オブジェクト」の属性を再帰的に変換し、transformation内蔵関数の属性値のオブジェクトによって変換されます.例は以下の通りである(例中のR.xxxはいずれもRamdaのAPIであり、関連するAPIの機能はRamdaドキュメントを参照することができる).
var tomato  = {name: 'Tomato', data: {elapsed: 100, remaining: 1400}, id:123};
var transformations = {
  name: R.toUpper,
  data: {elapsed: R.add(1), remaining: R.add(-1)}
};

R.evolve(transformations)(tomato);
//=> {name: 'TOMATO', data: {elapsed: 101, remaining: 1399}, id:123}

高次関数


定義:
入力パラメータとして関数を使用するか、結果を関数として返す関数を高次関数と呼びます.
パラメータや戻り値の関数として、一等関数の応用の一つです.高次関数は一等関数を基礎とし,一等関数をサポートする言語のみが高次関数プログラミングを行う.
よく知られているfilter関数を例にとると、リスト内の要素をfilterでフィルタリングし、条件に合致する要素をフィルタできます.filterのタイプ署名とサンプルコードは次のとおりです.
filter :: (a  Boolean)  [a]  [a]
const isEven = n => n % 2 === 0;

const filterEven = R.filter(isEven);

filterEven([1, 2, 3, 4]); //=> [2, 4]

filterは、入力値が偶数であるか否かを判断する判断関数isEvenを受け取り、偶数をフィルタした関数filterEvenを返す.

クローズドパッケージ


定義:
閉パッケージは、関数とその関数がキャプチャしたコンテキスト内の自由変数からなるレコードです.
例:
function add(x) {
  const xIn = x;
  return function addInner(y) {
    return xIn + y;
  }
}
const inc = add(1);
inc(8); //=> 9;

const plus2 = add(2);
plus2(8); //=> 10;

上記のコードで返される関数addInnerと、そのコンテキストで定義された自由変数xInとによって取得され、閉パケットが構成される.
コードの最外層のadd関数は、1等関数addInnerを返す高次関数である.
実はadd関数のパラメータxaddInnerコンテキストの一部なので、「xIn」も存在する必要はありません.addコードは以下のように最適化されています.
function add(x) {
  return function addInner(y) {
    return x + y;
  }
}

矢印関数を使用すると、addの実装をさらに最適化できます.
const add = x => y => x + y

とても簡潔ではありませんか?これにより,関数式プログラミングの強力な表現能力を垣間見ることができる.
閉パケットは主にデータキャッシュに用いられ、データキャッシュの応用は非常に広範である:関数工場モード、プライベート変数を持つオブジェクトのシミュレーション、関数キャッシュ、そして有名なコリー化を含む.
実は上記のコードのadd関数はコリー化形式の関数です.
上記コードのconst inc = add(1);およびconst plus2 = add(2);は、add関数に異なるパラメータを入力することによって、機能の異なる関数を生成する関数ファクトリモードである.関数ファクトリは、関数の抽象化と多重化能力を向上させることができます.
たとえば、Ajaxリクエスト関数には次のような形式があります.
const ajax = method => type => query => { ... };

const get = ajax('GET');
const post = ajax('POST');

const getJson = get('json');
const getHtml = ajax('GET')('text/html') = get('text/html');

最も一般的なajax要求関数を抽象化し,具体的な応用では関数工場を通じて異なる役割を果たすことができる関数を用いた.
上のいくつかの小節を通じて、純関数(数学的な意味での関数)、一等関数、高次関数、そして閉包について説明し、以下では上記のすべての概念を一体化した関数キャッシュを通じて、関数式プログラミングにおける「関数たち」の論述を終了する.

関数キャッシュmemoize


関数の実装:
const memoize = pureFunc => {
  const cache = {};
  return function() {
    const argStr = JSON.stringify(arguments);
    cache[argStr] = cache[argStr] || pureFunc.apply(pureFunc, arguments);
    return cache[argStr];
  };
};
memoizeの機能は、入力関数pureFuncをキャッシュし、キャッシュバージョンのpureFuncを返す.パラメータを使用してキャッシュされた関数を呼び出すと、キャッシュされた関数はcacheに検索され、キャッシュがある場合は再計算する必要はなく、キャッシュされた値を直接返す必要はありません.そうしないと、今回入力したパラメータを計算し、計算結果をキャッシュして後で使用し、結果を返します.memoizeは、純粋な関数のキャッシュにのみ意味があります.純粋な関数は透明であるため、出力は入力にのみ依存し、計算プロセスは外部環境に影響しません.
極端な例を挙げると、ランダム数値生成関数random()があれば、キャッシュされます.
const memoizedRandom = memoize(random);
memoizedRandomは、最初にランダム値を生成した以外は、その後の呼び出しが最初のキャッシュの値を返し、randomの意味を失う.さらに,終端文字入力関数getchar()をキャッシュし,呼び出すたびに初めて取得したアルファベットである.memoizeの内部では、閉パッケージの作成が実現されています.返されるキャッシュ関数は、フリー変数cacheと共に閉パケットを構成する.フリー変数cachedは、計算されたデータ(パラメータ)のキャッシュに使用される.閉パケット自体は高次関数と一等関数によって実現される.

まとめ


本文は関数式のプログラミングの中の“関数達”に対して詳しく説明しました:純粋な関数、1等の関数、高次の関数、そしてそれらの応用を展示しました.純粋な関数は関数の組み合わせの基礎である.一等関数は高次関数の実現基礎であり,一等関数と高次関数はまた閉パケットの実現基礎である.
最後に,関数キャッシュ関数memoizeにより純関数,一等関数,高次関数,閉包を結びつけ,関数式プログラミングにおける「関数たち」(関数式三ダーツ客)の一次「連合行動」で本稿を終了する.

リファレンスドキュメント


What is a Function?.
Functional Programming.
Referential Transparency.