こんにちは、ほそ道です。
今回は前回のコレクションに続いて、条件分岐する場合の値代入/返却を1つの文でやっていきたいと思います。
もともとJavaScriptの条件分岐であるif文やswitch文は値を返すようにはできていないので、分岐で値を返すには三項演算子を使うことが多いと思うのですが、三項演算子の場合は入れ子がキツイなと感じています。。
そこで今回は外部モジュールを用いて分岐条件に沿った値を返すようにしていきたいと思います。
三項演算子を使わない場合、let
で値を初期化して、条件分岐の結果代入することが多いのではないかと思いますが、
今回のレシピで多くのケースの「const
定義した値に一発で代入する」に対応することができるようになると思います。
また、const総集編ということで、値の不変性を担保するために全体的にimmutable.jsを使って行きたいなと思います。
パターンマッチ
はずはパターンマッチからやっていきます。
matchesというライブラリを使います。
こちらはgithubページによるとmatchesはdeprecatedでsparklerを使ってくださいとありまして。
確かにシンタックスはよりシンプルなものになっているのですが、sweet.jsを使ってsparkler/macrosをカマしたりと環境面でいろいろやるのが面倒だったのでワタクシ的にはこちらのほうが手軽で使いやすく感じております。ほそ道が日頃ほとんどsweet.jsを使ってないというのもありますね。。
簡単な例
下記がmatches.caseOfを使用した感じになります。
const I = require('immutable');
const caseOf = require('matches').caseOf;
const matchTest = list => caseOf(list.size, {
'1': () => "one",
'2': () => "two",
'_': () => 'other'
});
console.log(matchTest(I.List([1, 2]))); // two
console.log(matchTest(I.List())); // other
caseOf
の第一引数とマッチする可能性のあるパターンを第二引数オブジェクトのキー名として文字列で記述します。
caseOf
を実行すると、マッチしたものの値関数をが返す値を返してくれます。何もマッチしなかった場合はエラーが発生するようになっているのでワイルドカードパターンとして'_'
というパターンを作っております。
型判定
先ほどの例は数値の判定を行っていましたが、型判定するケースも作ってみます。
const I = require('immutable');
const caseOf = require('matches').caseOf;
const matchTest = val => caseOf(val, {
'x@String': x => `${x} is string`,
'x@Number': x => `${x} is number`,
'x': x => `${x} is other type`
});
console.log(matchTest(1)); // 1 is number
console.log(matchTest('1')); // 1 is string
console.log(matchTest(true)); // true is other type
console.log(matchTest(I.List([1, 2, 3]))); // List [ 1, 2, 3 ] is other type
識別子@型名
で型判定ができるようになっていますね。
型指定なしのxはワイルドカード的に使えるようです。
一応書いておくと、手前のパターンに文字列型マッチパターンがいるので文字列'x'はここには来ないはずです。
反復(再起)
配列の先頭要素、先頭より後要素の取得もできるので、全配列要素を扱うような再帰関数も作れますね。
こういう関数を作るべきだとは思わないんですが、ifやforのない世界は個人的にグッと来るものがあるので、JSでこういうことができると嬉しいです。
const I = require('immutable');
const caseOf = require('matches').caseOf;
const sumList = list => caseOf(list.toJS(), {
'[head, ...tail]': (head, tail) => head + sumList(I.List(tail)),
'_' : () => 0
});
console.log(sumList(I.List([1, 2, 3, 4, 5, 6]))); // 21
matchesはここまでです。
他にも色々なメソッドやマッチング方法があるのでREADMEを見ていると面白いです。
if式
こちらはワタクシの手前味噌ですがifxというif式を作るモジュールです。
if文は値を返すものではなくルーチンを実行しますがif式は値を返します、とScalaの本で読みいいなほしいなと。。
2択の分岐ケースでは三項演算子を使うんですが、分岐が複雑になった時に可読性ががくんと落ちるのがしんどいなぁ、、、と思っていたので作りました。
const If = require('ifx');
const ifxTest = x =>
If(typeof x === 'number')(() =>
If(x > 100)(() => 'over 100').
ElseIf(x === 1000)(() => 'Just 100').
Else(() => 'under 100')
).Else(() => 'not number');
console.log(ifxTest(99)); // under 100
console.log(ifxTest(100)); // under 100
console.log(ifxTest(101)); // over 100
console.log(ifxTest('hoge')); // not number
下記のようにElseを使わない場合はGetを記述しないといけないのが自分的には直感的ではないと感じており、悔しいポイントなのですが、やむを得ず。。
const If = require('ifx');
const ifxTest = x =>
If(typeof x === 'number')(() => 'number').Get();
console.log(ifxTest(100)); // number
console.log(ifxTest('hoge')); // null
ifxは実際にほそ道が携わっているプロジェクトでも使っていますが
特に説明せずともメンバーは使えているので直感的に使えるのではないかと思います。
今回は以上です。
const
で攻めるシリーズも今回でおしまいとしようかと思います。
ほそ道は今後もconst
中心の布陣でやっていこうと思うのでまた新しい発見があれば書きたいと思います。