RecordとTupleが変えるJavaScriptのmutability
2019-12-20 @arayaryoma
at つくばexpress.js #1
自己紹介
- あらや / Ryoma Abe
- arayaryoma
- 元フラー社員, 元柏の葉在住
- フロントエンドエンジニア
- Web標準が好き
現職
- 株式会社ヘマタイト
- https://ctfkit.com
JSのmutabilityおさらい
const
で宣言された変数は再代入できない
// TypeError: Assignment to constant variable.
const num = 0;
num++;
const str = 'hello';
str = 'bye';
const isFree = false;
isFree = true;
const nullable = null;
nullable = 100;
const undef = undefined;
undef = 500;
const bigNum = 100000000000000000000000000n
bigNum = 0;
const sym = Symbol('hello');
sym = Symbol('');
const obj = {a: 'hello'};
obj = {a: 'bye'};
const arr = [1,2];
arr = [3]
object
とArray
は再代入不可だがimmutableではない
const person = {
name: 'Araya',
age: 24
}
person.age++;
console.log(person); // { name: 'Araya', age: 25 }
const stations = ['柏の葉キャンパス', '流山おおたかの森'];
stations.push('流山セントラルパーク');
stations.shift();
console.log(stations); // [ '流山おおたかの森', '流山セントラルパーク' ]
元の値を変更しないものもある
const arr1 = [1,2,[3,4,5]];
const flatten = arr1.flat();
console.log(arr1); // [ 1, 2, [ 3, 4, 5 ] ]
console.log(flatten); // [ 1, 2, 3, 4, 5 ]
現代でのmutabilityとの戦い
Object.freeze
const person = {
name: 'Araya',
age: 24
}
Object.freeze(person);
person.age++;
console.log(person); // { name: 'Araya', age: 24 }
- エラーはthrowされない
- shallow freezeであることに注意
const person = {
name: 'Araya',
age: 24,
company: {
name: 'Hematite Inc.',
place: 'Tokyo'
}
}
Object.freeze(person);
person.company.place = 'Antarctica';
console.log(person.company.place) // Antarctica
Spread syntax (スプレッド構文)
const person = {
name: 'Araya',
age: 24
}
const clone = {...person, age: person.age + 1}
console.log(person); // { name: 'Araya', age: 24 }
console.log(clone); // { name: 'Araya', age: 25 }
const stations = ['柏の葉キャンパス', '流山おおたかの森'];
const added = [...stations, '流山セントラルパーク']
console.log(stations); // [ '柏の葉キャンパス', '流山おおたかの森' ]
console.log(added) // [ '柏の葉キャンパス', '流山おおたかの森', '流山セントラルパーク' ]
objectのプロパティなどを直接書き換えるのを禁止して、必ず新しい変数を作る。
Libraries
ここまでのまとめ
- JavaScriptのobjectはmutable
Object.deepFreeze
を別途実装したり、Immutable.js のライブラリが使われている- 言語レベルでimmutableなobjectは求められている
Record & Tuple
Record & Tuple
- TC39に提出されているproposalの1つ。
- 2019年12月20日時点でStage1。
- 提案されたのは2019年4月頃
- TC39に出されるproposalとしてはかなり順調(に見える)
- 残念ながら現在babel pluginなどはなさそう・・・
Record
基本のSyntax
const record = #{
a: 1,
b: 2,
c: 3,
};
record.a === 1; // true
record['a'] === 1; //true
record.a = 100 // TypeError
スプレッド構文
const record2 = #{...record1, b: 5};
record1 !== record2; //true
record2 === #{ a: 1, c: 3, b: 5 }; // true
Iteration
const record = #{ a: 1, b: 2 };
// TypeError: record is not iterable
for (const o of record) { console.log(o); }
Recordはiterableではない
objectからRecord型のオブジェクトを作る
const obj = {a: 1, b: 2, c: 3};
const record = Record.from(obj);
Record.from({ a: {} }); // TypeError: Can't convert Object with a non-const value to Record
Tuple
基本のSyntax
const tuple = #[1, 2, 3];
tuple[0] === 1; // true
tuple[0] = 100; // TypeError
with
const tuple1 = #[1, 2, 3];
const tuple2 = tuple1.with(0, 2);
tuple1 !== tuple2; // true
tuple2 === #[2, 2, 3]; // true
スプレッド構文
const tuple1 = #[1, 2, 3];
const tuple2 = #[1, ...tuple1];
tuple2 === #[1, 1, 2, 3]; // true
push
, pop
const tuple1 = #[1, 2, 3];
const pushed = tuple1.push(4);
pushed === #[1, 2, 3, 4]; // true
const popped = tuple4.pop();
popped === #[1, 2, 3]; // true
Iteration
for (const o of tuple) { console.log(o); }
// 1
// 2
Array型のオブジェクトからTuple型のオブジェクトを作る
const tuple = Tuple.from([1, 2, 3]);
const tuple2 = Tuple.from([{}, {} , {}]); // // TypeError: Can't convert Iterable with a non-const value to Tuple
気をつけること
#{...}
はshallow freezeと同質
const person = #{
name: 'Araya',
age: 24,
company: { // この中はmutable
name: 'Hematite Inc.',
place: 'Tokyo'
}
}
const imPerson = #{
name: 'Araya',
age: 24,
company: #{ // immutable
name: 'Hematite Inc.',
place: 'Tokyo'
}
}
これについてはTupleも同様
JSON.stringify
JSON.stringify(record)
はJSON.stringify(object)
と同じ結果JSON.stringify(tuple)
はJSON.stringify(array)
と同じ結果
JSON.stringify(#{ a: #[1, 2, 3] }); // '{"a":[1,2,3]}'
JSON.stringify(#[true, #{ a: #[1, 2, 3] }]); // '[true,{"a":[1,2,3]}]'
typeof
現在のところどちらもrecord
になるのが妥当だと考えられている
typeof {a: 1} === "object";
typeof [1, 2] === "object";
typeof #{ a: 1 } === "record";
typeof #[1, 2] === "record";
まとめ
- RecordとTupleはJSのランタイムにimmutableな世界をもたらす
- すべてをimmutableにするのではなくて、mutable/immutableを明示的に書けることが重要
- まだ使うことはできないが、期待値は高め(完全に発表者の主観。みなさんはどう思いますか?)
- 詳しくはproposalを見てください
- TypeScriptのRecordとTupleは・・・RecordとTupleにコンパイルされる・・・? (しかし中身はただのUtility type...)