LoginSignup
33
39

More than 5 years have passed since last update.

JavaScript おじさんが教える ES2015+ と関数型プログラミング 1

Last updated at Posted at 2018-09-03

関数型プログラミングへの注目が集まっており、また私にとっても、とても刺激的なテーマであります。関数型プログラミングにのっとることで、簡潔で、宣言的にコードを書くことができ、それによってテストをすることも容易になりますし、コードを把握することも容易になります。さて、関数型プログラミングとは何でしょうか?これについては、この問題についてより深い知識を持った人物である Eric Elliot の言葉を借りることにします。

Functional programming has been on the rise and is a topic that is very exciting to me. It allows me to write terse, declarative code that is easy to test and reason about. What is functional programming? I’ll defer that answer to someone with more knowledge on the subject, Eric Elliot:

Functional programming (しばしば FP と省略される) は、ソフトウェアを pure function (純粋関数) によって構築する一連の行為で、「state (状態) の共有、データの mutable な変更、副作用」を避けることを基本とします。Functional programming は、命令型ではなく宣言型で、state (状態) が pure function (純粋関数) の中を流れていきます。これはオブジェクト指向プログラミングと対比的で、オブジェクト指向プログラミングにおいては、state は通常共有され、またオブジェクト内のメソッドと同じ場所に配置されます。

Functional programming (often abbreviated FP) is the process of building software by composing pure functions, avoiding shared state, mutable data,and side-effects. Functional programming is declarative rather than imperative, and application state flows through pure functions. Contrast with object oriented programming, where application state is usually shared and colocated with methods in objects.

(引用元) https://medium.com/dailyjs/functional-js-with-es6-recursive-patterns-b7d0813ef9e3

ということで、上記引用元の記事が JavaScript による関数型プログラミングの導入と ES2015+ シンタックスの学習にとてもいいと思うので、翻訳引用しつつ、解説も加えていきたいと思います。

配列の destructuring assignment を使ったもの

destructuring assignment というのは、配列、もしくはオブジェクトから値を取り出して、特定の変数に明示的にアサインするためのシンタックスです。これは ES2015 で標準化されたシンタックスです。

まずは配列を destructuring assignment するシンタックスを扱います。

// 配列の最初の要素だけを返す
const head = ([x]) => x

// 配列の、最初の要素「以外」を返す
const tail = ([, ...xs]) => xs

上記コードに馴染みのない人のために、細かくどんな挙動をするか以下に示します。また CodeSandbox でも動くようにしておきましたので、適宜実行してみてください。https://codesandbox.io/s/9j8wmrl82r

// 配列の先頭の要素だけを抜き出す
const head = ([x]) => x;

// 通常の使い方
console.log(head([0, 1, 2]), "head1");

// 空の配列を渡すと、undefined になる
console.log(head([]), "head2");

// array を与えないとそもそもエラーになる
///console.log(head(), "head3");

// 先頭の要素と、残りの配列に分ける
const tail = ([x, ...xs]) => xs;

// 以下のように書くこともできる(先頭部分だけをのぞいた配列 xs を作る場合)
//const tail = ([, ...xs]) => xs

console.log(tail([0, 1, 2]), "tail1");

// 先頭以外がなくても undefined にはならず、空配列がかえる
console.log(tail([0]), "tail2");
console.log(tail([]), "tail3");

// x と xs がどうなるのかわかりやすく確認するための関数
const consoleHeadAndTail = ([x, ...xs]) => console.log(x, "head", xs, "tail");

// 普通のユースケース
consoleHeadAndTail([0, 1, 2], "HAT1");

// 先頭しかない場合は、残りの部分は空配列になる
consoleHeadAndTail([0], "HAT2");

// 空配列を渡すと、先頭は undefined, 残りの部分は空配列になる
consoleHeadAndTail([], "HAT3");

注意して欲しいのは、undefined がどのような場合にかえってくるかです。詳しくは上記コードを見て欲しいのですが、要点だけお伝えすると、空配列を渡した際に、先頭要素が undefined になり、残りの部分は undefined ではなく空配列が帰ってくるということです。これが後々重要になるので、確認してください。

そもそもアロー関数に慣れていない人は…

こちらの資料を確認してください。
https://super-yusuke.gitbook.io/udemy-course/meteno-react-app/es2015-noarnitsuite

ポイントとしては、()=> x とした場合には、xreturn されるということです。return を明示的に書いていなくてもです。

typeof を用いた、与えられた引数が undefined でないかどうかをチェックする関数

const def = x => typeof x !== 'undefined'

そもそも typeof とは何なのか。

開眼! JavaScript から引用します。
(ちなみにこの本は、 JS の「個性ある挙動」について理解する上で非常に有益なので、オススメです。サイの表紙で有名な JavaScript 第6版よりも圧倒的に薄く、コンテンツが JS の気にすべきポイントに絞られているので読むのに時間もかかりません。)

typeof 演算子は値の型を返します。しかし、typeof が返す値は一貫性に欠けるため気をつける必要があります。

(開眼! JavaScript, 1.16 typeof 演算子の戻り値)

もう一冊引用します。
(ちなみにこちらの本は、ES2015 を中心に扱っている点が有益です。ただし、初めて JavaScript をやるひとには難しいかなと思います。)

typeof 演算子は、被演算子のデータ型を表す文字列を返します。残念ながら、この演算子は JavaScript の 7 つのデータ型 - プリミティブな数値(Number)、文字列(String)、論理値(Boolean)、null、undefined、シンボル(Symbol)、およびオブジェクト - に正確に対応していません。このため批判と混乱が後を絶ちません。

(初めての JavaScript, 3.5 プリミティブ型とオブジェクト)

つまり typeof は「値のデータ型」を判定して、それを返してくれる演算子です。Number, Stirng 等とデータ型を返してくれるわけですが、しかし癖があると。バグといっていいほどの変わった仕様になっていると。どういうことでしょうか。実際にコードを実行して確認してみます。

typeof の不思議な挙動

注目して欲しいのは、null, array, NaN へ typeof を使用した場合です。

// プリミティブ値
const myNull = null;
console.log(typeof myNull); 
// 出力:object !? null は object じゃないのに!

const myUndefined = undefined;
console.log(typeof myUndefined);
// 出力:undefined

const primitiveString1 = "string";
console.log(typeof primitiveString1);
// 出力:string

const primitiveNumber1 = 10;
console.log(typeof primitiveNumber1);
// 出力:number

const primitiveBoolean1 = true;
console.log(typeof primitiveBoolean1);
// 出力:boolean

// obejct
const myArray = [0, 1, 2];
console.log(typeof myArray);
// 出力:object
// 少しわかりづらいですが配列はプリミティブ値ではなくオブジェクトであるため、object と返される。

const notANumber = NaN;
console.log(typeof notANumber);
// 出力:number !? 数字じゃないのに!

null はプリミティブ値なのに object が返され不便です。array は array と返ってくるかと思いきや、object と返ってきます。typeof では配列オブジェクトと非配列オブジェクトを区別することはできません。NaN = not a number は、number と返ってきます。数字ではないのに!

とにかくこういう仕様になっていることを把握する必要があります。

プリミティブとは

データ型を理解する上で、プリミティブとオブジェクトについて説明する必要があると思いましたので補足します。

JavaScript では、値は「プリミティブ(primitive)」であるか「オブジェクト(object)」であるかのいずれかです。... プリミティブを表すデータの型 (データ型) には次の6種類があります。

  • 数値(Number)
  • 文字列(String)
  • 論理値(Boolean)
  • null
  • undefined
  • シンボル(Symbol)

... 上記6種類のプリミティブを表すデータ型以外に、「オブジェクト」があります。...JavaScript にはいくつかのオブジェクトを表すデータ型(オブジェクト型)があらかじめ用意されています。...

(初めての JavaScript, 3.5 プリミティブ型とオブジェクト)

Array はオブジェクト型に含まれます。

JavaScript では、5、'foo'、true、false や、null、undefined といった値は既約(irreducible、これ以上単純化されない)であるためプリミティブ値であるとみなされています。つまり、数値は数値そのもの、文字列は文字列そのもの、真偽値は true もしくは false、そして null と undefined はただの null と undefined である ということです。これらの値はシンプルであり、他の型の値を組み合わせて構成された値を表しません。... 文字列、数値、真偽値、null、undefined の各値がそれぞれオブジェクトになりうるかどうかを確かめてください。そしてそれをあなたがすでに知っている Object() や Array() のインスタンス、もしくは他の複雑なオブジェクトと比較してみてください。... 配列やオブジェクトといった複合型(1.12 節で詳しく解説)の値は、1 つ以上のプリミティブ値もしくはオブジェクトによって構成されており、そのため複数の値を持った複雑なセットになりうることに留意してください。...とてもシンプルに言うと、プリミティブ値は JavaScript の世界の情報における一番低いレイヤの単位です。

(開眼! JavaScript, 1.7 プリミティブ型(基本型)の値)

ということで、JavaScript の値は、プリミティブとオブジェクトがある。プリミティブは以下。

  • 数値(Number)
  • 文字列(String)
  • 論理値(Boolean)
  • null
  • undefined
  • シンボル(Symbol)

そしてこれ以外はオブジェクトである。特に Array はオブジェクトである点に留意してください。

def 関数に話を戻します…

ということで typeof に理解が深まったところで、冒頭の関数をみてみましょう。

const def = x => typeof x !== 'undefined'

与えられた引数が、定義されていれば true を、定義されていない = undefined の場合は false を返すわけです。
ただし typeof の挙動から注意して欲しいのは、配列の場合は objectnull の場合も objectNaN の場合は number と返ってくるということですね。

ちなみに配列と NaN を判定するには

ちなみに配列かどうかをチェックするいい方法は以下になります。

Array.isArray([])

また NaN を判定するには次のようにします。

Number.isNaN(x)

null を判定するには次のようにします。

null === x

オブジェクトの destructuring assignment

配列の destructuring assignment は先ほど使用しましたが、同様にオブジェクトでも可能です。以下サンプルです。
関数に引数を与える際に、順番を気にすることなく与えることができるので便利です。

const objectDestructuringAssignment = ({ name, id }) => console.log(name, id);

const obj1 = { name: "nakanishi", id: 1 };

objectDestructuringAssignment(obj1); // => 'nakanishi' 1

// 以下のような場合だと、引数に渡す順番を間違えると、望んだ挙動にならない
const functionNormal = (name, id) => console.log(name, id)
functionNormal(1, 'nakanishi')

配列のコピー

Spread シンタックスを使って配列をコピーします。これは Array.slice() と同じですね。

const copy = array => [...array]

ここでワンポイントアドバイスですが、Array.splice() は元の配列を変更してしまう = mutating なメソッドなので、明確な意図がない限りは避けたほうがいいでしょう。immutable は関数型プログラミングにおいて重要な要素の一つです。

ちなみに、次のように複数の配列を合体させるためにも使用できます。

const array1 = [1, 2, 3];
const newArray1 = [...array1, 4];
console.log(newArray1); // [1,2,3,4]

const array2 = [5, 6, 7];
const newArray2 = [...array1, ...array2];
console.log(newArray2); // [1,2,3,5,6,7]

ちなみに Object の spread syntax

Object でもこれはできます。ただしこのシンタックスは ES2018 の仕様です。

const obj1 = { name: "test" };
const obj2 = { ...obj1, id: 1 };
console.log(obj2); // { name: "test", id: 1 } プロパティを追加できる

const obj3 = { ...obj2, name: "change" }
console.log(obj3); // { name: "change" } 同じプロパティの場合には上書きされる

console.log(obj1) // 元のオブジェクトは変更されていない = immutable

引き続き 2 を書いていきます

次は再帰呼び出しです。

33
39
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
39