関数型プログラミングとは
参照透過性
参照透過的な関数を組み合わせ
ることで解決するべき問題に対処していく宣言型のプログラミングのスタイル
命令型プログラミング(Imperative Programming)』と『宣言型プログラミング
(Declarative Programming)』の 2 つに大別される
命令型とは、最終的な出力を得るために状態を変化させる連続した文によって記述されるプログラミングスタイルのことをいう。
宣言型プログラミングでは出力を得る方法ではなく、出力の性質・あるべき状態を文字通り宣言することでプログラムを構成する
手続き型プログラミング
// Procedural programming
const octuples = [];
for (let n = 1; n < 101; n += 1) {
if (n % 8 === 0) {
octuples.push(n);
}
}
console.log(octuples);
}
オブジェクト指向プログラミング(Object-Oriented Programming)
// Functional programming
{
const range = (start, end) => [...new Array(end - start).keys()].map((n) => n + start);
console.log(range(1, 101).filter((n) => n % 8 === 0));
}
//配列の反復処理
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(arr.map((n) => n * 2)); // [ 2, 4, 6, 8, 10, 12, 14, 16, 18 ]
console.log(arr.filter((n) => n % 3 === 0)); // [ 3, 6, 9 ]
console.log(arr.find((n) => n > 4)); // 5
console.log(arr.findIndex((n) => n > 4)); // 4
console.log(arr.every((n) => n !== 0)); // true
console.log(arr.some((n) => n >= 10)); // false
//配列の反復処理
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(arr.map((n) => n * 2)); // [ 2, 4, 6, 8, 10, 12, 14, 16, 18 ]
console.log(arr.filter((n) => n % 3 === 0)); // [ 3, 6, 9 ]
console.log(arr.find((n) => n > 4)); // 5
console.log(arr.findIndex((n) => n > 4)); // 4
console.log(arr.every((n) => n !== 0)); // true
console.log(arr.some((n) => n >= 10)); // false
・map() …… 対象の配列の要素ひとつひとつを任意に加工した新しい配列を返す
・filter() …… 与えた条件に適合する要素だけを抽出した新しい配列を返
・find() …… 与えた条件に適合した最初の要素を返す。見つからなかった場合は undefind を返す
・findIndex() …… 与えた条件に適合した最初の要素のインデックスを返す。見つからなかった場
合は -1 を返す
・every() ……「与えた条件をすべての要素が満たすか」を真偽値で返す
・some() ……「与えた条件を満たす要素がひとつでもあるか」を真偽値で返す
const arr = [1, 2, 3, 4, 5];
console.log(arr.reduce((n, m) => n + m)); // 15
console.log(arr.sort((n, m) => n > m ? -1 : 1)); // [ 5, 4, 3, 2, 1 ]
reduce
・1 回めの実行: m = 1、前回の実行がないので、結果は 1 がそのまま返る
・2 回めの実行: m = 2、前回の実行結果により n = 1、結果は 1 + 2 = 3 が返る
・3 回めの実行: m = 3、前回の実行結果により n = 3、結果は 3 + 3 = 6 が返る
・4 回めの実行: m = 4、前回の実行結果により n = 6、結果は 6 + 4 = 10 が返る
・5 回めの実行: m = 5、前回の実行結果により n = 10、結果は 10 + 5 = 15 が返る
sort
ⅰ. 第 1 引数が第 2 引数より優先度が高い(前に来る)場合、-1 を返す
ⅱ. 第 1 引数が第 2 引数より優先度が低い(後に来る)場合、1 を返す
ⅲ. 第 1 引数と第 2 引数の優先度が同じ(ソートの必要がない)場合、0 を返す
sortを使う際はsliceを挟む
const lst = [5, 7, 1, 3];
console.log(lst.slice().sort((n, m) => n < m ? -1 : 1)); // [1, 3, 5, 7]
console.log(lst); // [5, 7, 1, 3]
const arr = [1, 2, 3, 4, 5];
console.log(arr.includes(5)); // true
console.log(arr.includes(8)); // false
includes() は『指定した値の要素がひとつでも含まれているか』を真偽値で返す Array のプロト
タイプメソッド。
オブジェクトの反復処理
標準組み込みオブジェクト Object を直接継承するオブジェ
クトの反復処理
const user = {
id: 3,
name: 'Bobby Kumanov',
username: 'bobby',
email: 'bobby@maple.town',
};
console.log(Object.keys(user));
// [ 'id', 'name', 'username', 'email' ]
console.log(Object.values(user));
// [ 3, 'Bobby Bear', 'bobby', 'bobby@maple.town' ]
console.log(Object.entries(user));
// [
// [ 'id', 3 ],
// [ 'name', 'Bobby Kumanov' ],
// [ 'username', 'bobby' ],
// [ 'email', 'bobby@maple.town' ]
// ]
Object.keys() でプロパティのキーのリスト、Object.values() でプロパティ値のリストを配列で取得、Object.entries() はキーと値が対になった 2 次元配列を返す
あらためて関数型プログラミングとは何か
JavaScript では関数は第一級オブジェクト、つまり変数への代入ができ、配列の要素やオブジェクトのプロパティ値、さらには関数の引数や戻り
値にもなることができるオブジェクト
ⅰ. 名前を持たないその場限りの関数(無名関数)を定義できる
ⅱ. 変数に関数を代入できる
ⅲ. 関数の引数として関数を渡したり、戻り値として関数を返すことができる(高階関数)
ⅳ. 関数に特定の引数を固定した新しい関数を作ることができる(部分適用)
ⅴ. 複数の高階関数を合成してひとつの関数にできる(関数合成)
高階関数
引数に関数を取ったり、戻り値として関数を返したりする関数
const greeter = (target) => { const sayHello = () => {
console.log(`Hi, ${target}!`); };
return sayHello; };
const greet = greeter('Step Jun');
greet(); //Hi,StepJun!
ここで注目するべきは、返してるのが sayHello の実行結果じゃなくて関数そのものだということ。return sayHello() と記述してしまうとその場で関数 を実行して、その結果の undefined を返してしまう
const greeter = (target) => { return () => {
console.log(`Hi, ${target}!`);
};
}
その場でただ return するだけなので、わざわざその関数に sayHello という名前を つける必要はない
コンポーネントを引数にとってコンポーネントを戻り値として返す『高階コンポーネント
これは React で既存のコンポーネントに後か ら機能を付与するために用いられる。原理は高階関数
カリー化と関数の部分適用
複数の引数 を取る関数を、より少ない引数を取る関数に分割して入れ子にすること
// Pre-curried {
const multiply = (n, m) => n * m; console.log(multiply(2, 4));
// Curried {
const withMultiple = (n) => { return(m)=>n*m;};// 8
}
console.log(withMultiple(2)(4)); //8 }
// Curried with double arrow {
const withMultiple = (n) => (m) => n * m;
console.log(withMultiple(2)(4)); //8 }
multiply は引数 n と m を取り、その積を返すだけの関数
withMultiple は n を引数に 取った上で『m を引数に取り n との積を返す関数』を返す関数
このように複数の引数を取る関数を、引数が『元の関数の最初の引数』で戻り値が『引数として元の関数の残りの引数を取り、それを使って結果を返す関数』である高階関数にすることを『カリー 化』と呼ぶ
withMultiple(n) 自体が『m を引数に取って m * n を返す関数』だから、それを関数として実行す るためにもうひとつカッコが必要
次はカリー化された関数の部分適用
const withMultiple = (n) => (m) => n * m; console.log(withMultiple(3)(5)); //15
const triple = withMultiple(3);
console.log(triple(5)); // 15
カリー化された高階関数 withMultiple のひとつめの引数に 3 を渡してできた関数に triple という名前をつけてる
triple は元の関数に還元すれば multiply(3, m) になる
カリー化された関数の一部の引数を固定して新しい関数を作ることを『関数 の部分適用』っていう
閉じ込められたクロージャの秘密
Closure。日本語には『関数閉包』と訳される。関数を関数で閉じて包むってこと
閉じられてない状態
let count = 0;
const increment = () => { return count += 1;
};
$ node
> .load open-counter.js
> increment(); 1
> increment(); 2
> count 2
丸ごと関数の 中に入れてしまう
const counter = () => { let count = 0;
const increment = () => { return count += 1;
};
};
const counter = () => { let count = 0;
const increment = () => { return count += 1;
};
return increment; };
$ node
> .load closure-counter.js
> const increment = counter();
> count
Uncaught ReferenceError: count is not defined
> increment(); 1
> increment(); 2
$ node
> const counter = (count = 0) => (adds = 1) => count += adds;
> const increment = counter();
> increment(); 1
> increment(2); 3
正確にはクロージャとは、関数と『その関数が作られた環境』という 2 つのものが一体となった 特殊なオブジェクトのことを指す
閉じ込めてる外側の関数 counter は『エンクロージ ャ(Enclosure)』とも呼ばれる
メモリのライフサイクル
- 必要なメモリを割り当てる
- 割り当てられたメモリを使用する
- 必要がなくなったら、割り当てたメモリを解放する
JavaScript のような高水準言語は 3 を手動で行う必要はなく、不要になったメモリ領域を 自動的に判別し解放する機能が装備されている
これを『ガベージコレクタ(Garbage Collector、 GC)』という
React ではよくコンポーネントを関数で表現するわけなんだけど、往々にして関数コンポ ーネントはクロージャであることが多い。クロージャの原理を知っておくことは React を深く理解 するのに役立つよ
Promise ― JavaScript で非同期処理を扱う基本
Promise は ES2015 から導入された JavaScript の標準組み込みオブジェクトで、非同期処理の最終 的な処理結果の値を文字通り『約束』するもの。Promise を使うことによって、任意の非同期処理の完了を待って次の処理を行うというのが JavaScript プログラミングでもできるようになる。
const isSucceeded = true;
const promise = new Promise((resolve, reject) => { if (isSucceeded) {
resolve('Success'); }else{
reject(new Error('Failure')); }
});
promise.then((value) => {
console.log('1.', value);
return 'Succees again'; })
.then((value) => { console.log('2.', value);
})
.catch((error) => {
console.error('3.', error); })
.finally(() => { console.log('4.', 'Completed');
});
$ node 04-async/promise.js 1. Success
2. Succees again
4. Completed
最初のコンストラクタで定義したコールバック関数 resolve() に渡したものが、 then() の引数の関数で value として受け取れるんですね。そして then() 内で最後に return した値 が次の then() での value になる
Promise をハンドリングする
import fetch from 'node-fetch';
const getUser = (userId) => fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then(
(response) => {
if (!response.ok) {
throw new Error(`${response.status} Error`); }else{
return response.json(); }
}, );
console.log('-- Start --');
getUser(2) .then((user) => {
console.log(user); })
.catch((error) => { console.log(error);
})
.finally(() => {
console.log('-- Completed --');
});
「node-fetch170 はモダンブラウザに実装されてるネットワークリクエスト操作のための Fetch API171 を、ほぼ同じインターフェースで Node.js から使えるようにしたライブラリね。この fetch() 関数も、 その戻り値である Response オブジェクトのメソッド json() も Promise オブジェクトを返してくる のでこうやって then() で受け止めてあげてる」
最近の JavaScript にはいい感じのシンタックスシュガーが用意されてる。『async/ await』
async は『asynchronous(非同期な)』から来てる。 await は『待ち望む、待ち構える』って単語
import fetch from 'node-fetch';
const getUser = async (userId) => { const response = await fetch(
`https://jsonplaceholder.typicode.com/users/${userId}`, );
if (!response.ok) {
throw new Error(`${response.status} Error`);
}
return response.json(); };
console.log('-- Start --');
const main = async () => { try {
const user = await getUser(2);
console.log(user); } catch (error) {
console.error(error); } finally {
console.log('-- Completed --'); }
};
main();
関数宣言時に async キーワードを付与するとその関数は『非同期関数』となって、返される値が暗黙の内に Promise.resolve()によってラップされたものになる
そして非同期関数の中では 他の非同期関数を、await 演算子をつけて呼びだすことができる
await 式によって非同期関数を実 行すると、その Promise が resolve されるか reject されるまで文字通り待ってもらえるようになる のね。そして resolve されたら await 式は、そのラップしていた Promise から値を抽出して返してくれる
await 演算子が使えるのは非同期関数の中だけ
だから非同期処理はどうしても仕方がない場合を除いて async / await を使って 書いていきましょう
参考書籍