33
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

関数型言語に爪先レベルで入門したので、まずはJavaScriptで値をイミュータブル(不変)に扱ってみる

Last updated at Posted at 2019-12-02

Mikatus Advent Calendar 2019 3日目の記事。

最初に

こんにちは。

普段はTypeScriptでVue.jsを書いているのですが、関数型言語を勉強しようと、最近社内ですごいHaskellたのしく学ぼう!通称すごいH本の勉強会を立ち上げました。

Haskellの特徴は色々あると思いますが、カリー化とか部分適用とか、JavaScriptに活かすのはなかなか難しそうだなと感じるものが多いので、わかりやすく効果がありそうな、値をイミュータブルに扱う方法について紹介していきます。

私自身JavaScriptも入門レベルなので、紹介する内容は基礎的なものですが、同じように最近JavaScriptに入門した人がいたら参考にしてみてください。

では参ります。

イミュータブルのメリット

まずイミュータブルとは、Wikipediaさんによると、

イミュータブル (英: immutable) なオブジェクトとは、作成後にその状態を変えることのできないオブジェクトのことである。
イミュータブル - Wikipedia

らしいです。

プログラミング関係の言葉をWikipediaで調べると難解なことが多いのですが、これは比較的わかりやすいですね。

この定義ではわかりづらいという場合でも、後に出てくる実際のコードを見ればなんとなく理解できるのではないかと思います。

で、イミュータブルにプログラミングするメリットですが、

  • 不測の値が混入するのを防ぐ
  • 値が置き換えられてないかいちいち確認する手間や心理的負荷がなくなる
  • プログラムの保守性が上がる

あたりが考えられ、特にチームで開発する上では大切なことだと感じています。

私自身独学期間を経て、今年エンジニアになったばかりなのですが、一人で開発する時には意識することもなかった、他のメンバー・将来のメンバーの負荷を減らすコードの必要性を実感しています。

というわけで、例外はあると思いますが、基本的に値はイミュータブルな方が良いのではないでしょうか。

では、これらのメリットを享受するため、JavaScriptでは実際どう書くのかを3つ見ていきましょう。

再代入しない

第一に、_一度変数を宣言したら、再代入しない_ということです。

JavaScriptでは変数を宣言する時に、varletもしくはconstが使えます。

varはよっぽどの例外がない限り、使わないという方向で問題ないでしょう。
私にはよっぽどの例外が思いつきません。

残るは、letconstということになりますが、

  • letは再代入可能
  • constは再代入不可

になります。

実際のコードで見ていきましょう。

まずはletで変数を宣言した場合です。

let.js
let firstName = 'Ai'
let lastName = 'Katou'

firstName = 'Kai'
lastName = 'Atou'

let fullName = `${firstName} ${lastName}`
console.log(fullName)
// Kai Atou
// 書き換えられている!

加藤あいが阿藤快に書き換えられてしまっています。

次にconstです。

const.js
const firstName = 'Lewis'
const lastName = 'Ann'

firstName = 'Rice'
// TypeError: Assignment to constant variable.
// 再代入しようとするとTypeErrorに

const fullName = `${firstName} ${lastName}`
console.log(fullName)
// Lewis Annが必ず表示される

const obj = { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi' }

obj.number = 100

console.log(obj)
// {number: 100, firstName: 'Hinata', lastName: 'Kashiwagi'}
// あくまで再代入の禁止で、オブジェクトのプロパティ変更は可能なため注意

アン・ルイスを半ライスに書き換えようと思ったのですが、できませんでした。

というわけで変数の宣言にはconstを使っていきましょう。
JavaScriptを本格的に書き始めて2ヶ月弱ですが、letでの宣言が必要になる機会はほぼないという印象です。

なお、constでもあくまで再代入ができなくなるだけであって、オブジェクトのプロパティは変更できてしまうので注意しましょう。

次行きます。

配列をイミュータブルに扱う

今度は配列をイミュータブルに扱う方法です。

配列の操作には破壊的操作非破壊的操作が存在します。

配列に対して破壊的操作を行うと、操作を行なった配列が直接変更されてしまい、変更前の値を参照することができなくなります。

対して、非破壊的操作は元の配列はそのままに新しい配列を返すので、変更前の値も参照することができます。

実際のコードで見ていきましょう。

array.js
const array = [3, 5, 6, 10, 11, 12]

const sortedArray = array.sort((a, b) => b - a)

console.log(array)
// [ 12, 11, 10, 6, 5, 3 ]
// 元の配列もソートされてしまっている!

sortメソッドは破壊的操作なので元の配列もソートされてしまいます。
sortメソッドは便利なので、ぜひ使いたいところですが、このままではイミュータブルな状態を実現できません。

なので下記のように書いていきましょう。

array.js
const array = [3, 5, 6, 10, 11, 12]

const copiedArray = [...array]
// 元のarrayを変更しないようにスプレッド構文で一旦コピーする

const sortedArray = copiedArray.sort((a, b) => b - a)

console.log(array)
// [ 3, 5, 6, 10, 11, 12]
// ソート前の配列も呼び出せる

console.log(sortedArray)
// [ 12, 11, 10, 6, 5, 3 ]

sortメソッドを適用する前に配列をコピーしました。
一手間かかるだけのように思えますが、こうすることで上に挙げたメリットを享受できます。

array.js
const array = [3, 5, 6, 10, 11, 12]

array.push(13)
// pushも元の配列を変更する

const addedArray = [...array, 13]
// スプレッド構文を使って要素が追加された新しい配列を作る

ここではsortpushメソッドを例に挙げていますが、破壊的操作は他にもあります。

配列にメソッドを適用しようと思ったら、それが破壊的非破壊的かを確認してみましょう。

次行きます。

オブジェクトをイミュータブルに扱う

配列に続いて今度はオブジェクトです。

再代入しないの項目でも少し触れましたが、オブジェクトについてはconstで宣言しようとプロパティの変更ができてしまいます。

ここもイミュータブルに扱いたいところです。

実際のコードで見ていきましょう。

object.js
const obj = { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi' }

obj.color = 'orange'
// obj[color] = 'orange' も同様

console.log(obj)
// { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi', color: 'orange' }
// 変更されてしまっていて、変更前の状態が参照できない

const addedObj = { ...obj, color: 'orange' }
// スプレッド構文を使って追加する

console.log(addedObj)
// { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi', color: 'orange' }

console.log(obj)
// { number: 11, firstName: 'Hinata', lastName: 'Kashiwagi' }
// 元の状態も参照できる

元のオブジェクトのプロパティを直接変更するのではなく、スプレッド構文を使って新しいオブジェクトを作っています。

スプレッド構文便利ですね。

ただ、この例については、TypeScriptを導入しているなら、型をちゃんと定義することで、後からプロパティの追加とかができないようにする方がいいと思います。

#最後に
値をイミュータブルに扱う方法を3つ紹介しました。

意識したことがなかった人がいたら、ぜひ意識して今回紹介した方法などを試してみてください。

すごいH本勉強会では、まだモナド等には触れていないので、万が一モナドを理解できたらJavaScriptを書く際にも活かせないか模索したいと思います。

それでは!

33
2
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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?