JavaScript ( 時々 TypeScript ) で学ぶ関数型プログラミングの基礎の基礎 #3 - 部分適用について

wakamsha
17

前置き ( ※ 読み飛ばしていただいても OK )

JavaScript は関数型ライクなエッセンスを一部含んではいるものの、決して Haskell のような純粋関数型言語ではありません。JavaScript では変数やオブジェクトの状態を自由に書き換えるようなプログラミングスタイルを通常としているからです。したがって JavaScript で関数型プログラミングをまともに行うのは本来ナンセンスなのかもしれません。しかし関数型の持つ要素の一部だけを取り入れたプログラミングをすることは可能であり、これらを習得することは大規模かつ堅牢な web アプリケーションを設計するのに少なからず恩恵をもたらします。また、 JavaScript には Underscore.js / LodashImmutable.jsRamda.js といった便利なリスト操作ライブラリや RxJS のような非同期処理ライブラリは、 JavaScript で関数型プログラミングをするうえで強力な手助けとなります。

当初は Ramda.js の入門エントリを書くつもりだったのですが、これを理解するには関数型プログラミングの基礎知識が求められます。そこでいきなり Ramda.js と戯れる前に関数型プログラミングの基礎の基礎について学んでみるとしましょう。

シリーズ一覧

当シリーズは関数型プログラミングの全てを習得することを目的としたものではありません。あくまで JavaScript プログラミングに関数型のエッセンスをほんの少し取り入れるところまでを目的とした入門者向けの内容を目指しています。関数型プログラミングを本格的に学びたいという方は、 素直に Haskell や Lisp などを題材に学習されることを強くおすすめします。

部分適用ってカリー化とは違うの?

カリー化された関数は、『引数が渡されるたびにパラメータがなくなるまで徐々に動作を特化された関数を返すもの』です。これに対し部分適用 ( partial application ) された関数とは、『部分的』に実行されていて、期待されてる残りの引数を渡すことで即時実行される関数のことです。

これだけ聞くと両者似た者同士という印象ですが、部分適用は可変長引数を受け取る関数であってもキレイにさばけるという特徴があります。以下、簡単なサンプルコードです。

// 1つめの引数を部分適用する関数
function partial1(fn: Function, arg1: any) {
  return fn.bind(undefined, arg1);
}

partial1関数の戻り値は、partial1 の実行時にその引数 arg1bind で確保したものです。これの実際の動作は以下になります。

// シンプルな割り算
function divide(a: number, b: number): number {
  return a / b;
}

const over10Part1 = partial1(divide, 10);

over10Part1(5);
// => 2

『関数』と『もう一つの関数と「設定用」引数』を合成することで over10Partial1関数を作成しています。

可変長引数に対応させる

引数の数を固定化せず可変長に対応できれば更に便利ですよね。ES2015 以降であればそれも簡単に実装出来ます。以下がその例です。

function partial(fn, ...args1) {
  return (...args2) => {
    return fn.apply(fn, [...args1, ...args2]);
  }
}

スプレッド演算子 ( Spread Operator ) のお陰でとても簡単に実装出来てしまいました。ここではbindを使わずに実装してますが、基本的な原理は partial1 とほぼ同じです。実際の動作は以下の通り。

const divideNoised = partial(divide, 10, 2, 4);

divideNoised(5000);
// => 5

divideNoised(9999);
// => 5

部分適用を行おうとしている関数 ( divide ) は決まった数の引数を期待しています。しかし JavaScript はいくつでも引数を受け取れてしまうため ( 可変長引数 ) 、予期せぬエラーを招くことがあります1)TypeScript のように型などで厳格に定義すれば、このようなリスクを意識せずとも回避出来ます。。上記の例では、計4つの引数が渡されていますが、divide 関数はそのうちはじめの二つだけしか受け取っておらず、残りは無視しています。このように部分適用を取り入れることで、関数呼び出しの時の予期せぬエラーを防ぐことが出来ます。

ライブラリを使った部分適用

先ほどの例では関数の部分適用化を自前で実装しましたが、 Lodash や Ramda.js といったリスト操作ライブラリにも同様の機能があります。はじめからこちらを使えばよかったですね 😇

Lodash

使い方は先ほど自前で実装した partial と全く同じです。

import * as _ from 'lodash';

function greet(greeting, name) {
  return `${greeting}, ${name}!!`;
}
 
const sayHelloTo = _.partial(greet, 'hello');

sayHelloTo('wakamsha');
// => 'hello, wakamsha!!'

こちらも部分適用化しようとしている関数が受け取る引数以上のものは無視するようになっています。

const sayHelloTo = _.partial(greet, 'hello', 'Naoki');

sayHelloTo('YAMADA');
// => 'hello, Naoki!!'

また、Lodash は『Placeholder』を使うことで指定の位置の引数を空欄にしつつ、それ以外の設定値 ( 引数 ) を固定化することも可能です。

const greetJude = _.partial(greet, _, 'Jude');

greetFred('Hey');
// => 'Hey Jude'

Ramda.js

Ramda.js も関数と「設定用」引数を渡すところまでは同じですが、「設定用」引数を配列で渡すという特徴があります。内部実装を覗いてみると分かりますが、可変長引数 ( arguments ) を concat関数で連結しやすくするために引数の型を限定しているかと思われます。

function greet(greeting, name) {
  return `${greeting}, ${name}!!`;
}
 
const sayHelloTo = R.partial(greet, ['hello']);

sayHelloTo('wakamsha');
// => 'hello, wakamsha!!'

R.partial関数で部分適用した関数を更に partial 関数に「設定用」引数と一緒に渡すことで段階的に設定値を固定化することも出来ます。

function greet(greeting, title, firstName, lastName) {
  return `${greetinng}, ${title} ${firstName} ${lastName}!`;
}

const sayHello = R.partial(greet, 'Hello');

const sayHelloToDoctor = R.partial(sayHello, ['Dr.']);

sayHelloToMs('Kenzo', 'Tenma');
//=> 'Hello, Dr. Kenzo Tenma!'

締め

カリー化も部分適用も、既存の関数から新しい関数を合成化するというアーキテクチャです。どちらも関数型に馴染みの薄い方にとってはなかなか理解しにくいものかもしれません2)僕もそのうちの一人です
('A` )
。カリー化は使う場面を選びますが、部分適用は比較的広く応用できるテクニックです。

これらは全て、ひとつの部品 ( 関数 ) を小さく定義して一つの決まった振る舞いをさせ、それらを組み合わせて大きな機能を実装する ( でも常に決まった動作をする ) ための手段です。何度も反復していくうちに、いずれそのメリットが見えてくることでしょう ( たぶん ) 。

脚注   [ + ]

1. TypeScript のように型などで厳格に定義すれば、このようなリスクを意識せずとも回避出来ます。
2. 僕もそのうちの一人です
('A` )