JavaScriptで関数型プログラミングをしてみようかな、と思う今日この頃。ざっくりいって Ramda と folktale の二つを使いこなせばいいんじゃない?って StackOverflow が教えてくれた 。あとドキュメントとして fantasy-land。
とりあえず、null の扱いがあんまり好きじゃないので、 Maybe から始めてみる。Scala でいうところの Option。まずはMaybeを作るところから。Just
, Nothing
, fromNullable()
の三つの方法を試してみた。Just(null)
は Nothing
にならないところが注意。nullを渡してNothing
にしたい場合は fromNullable()
を使おう。
const { Just, Nothing, fromNullable } = require('folktale/maybe');
const assert = require('assert');
// Create Maybe
{
const justs = [
Just(1),
Just({ hello: 'world' }),
Just(null), // you can pass any falthy. returns Just whose value is falthy
Just(undefined),
Just([]),
Just(''),
];
justs.forEach((m) => {
assert(m instanceof Just);
});
const nothings = [
Nothing(),
Nothing(null), // any argments will be ignored.
];
nothings.forEach((m) => {
assert(m instanceof Nothing);
});
const js = [
fromNullable(1),
fromNullable({ one: 1 }),
fromNullable(''),
fromNullable([]),
];
js.forEach((m) => {
assert(m instanceof Just);
});
const ns = [
fromNullable(null),
fromNullable(undefined),
];
ns.forEach((m) => {
assert(m instanceof Nothing);
});
}
Maybeを作ったら、Maybe を変換する関数を作って、map
と chain
をつなげてどんどん変換していく。map
は引数に値を返す関数をとるのに対して、chain
では引数に明示的にMaybeを返す関数をとる。自分で実装するときはchain
を使うのがいいのかなあ?と思ったりした。Maybeから値を取り出すには getOrElse
を使う。Nothing
に対して処理をしたい場合はorElse
を使う。
const { Just, Nothing, fromNullable } = require('folktale/maybe');
const assert = require('assert');
// Transform Maybe
{
const funcForChain = val => Just(val + 2);
assert.equal(3, Just(1).chain(funcForChain).getOrElse(null));
assert.equal(2, Just(null).chain(funcForChain).getOrElse(null)); // NOTE: null + 2 = 2
assert.equal(null, Nothing().chain(funcForChain).getOrElse(null));
const funcForMap = val => val + 2;
assert.equal(3, Just(1).map(funcForMap).getOrElse(null));
assert.equal(2, Just(null).map(funcForMap).getOrElse(null));
assert.equal(null, Nothing().map(funcForMap).getOrElse(null));
const getName = obj => ('name' in obj ? Just(obj.name) : Nothing());
const getDefaultName = () => Just('anon');
assert.equal(
'Alice',
Just({ name: 'Alice' }).chain(getName).orElse(getDefaultName).getOrElse(null),
);
assert.equal(
'anon',
Just({}).chain(getName).orElse(getDefaultName).getOrElse(null),
);
const toUpper = str => Just(str.toUpperCase());
assert.equal(
'ALICE',
Just({ name: 'Alice' }).chain(getName).orElse(getDefaultName)
.chain(toUpper)
.getOrElse(null),
);
assert.equal(
'ANON',
Just({}).chain(getName).orElse(getDefaultName)
.chain(toUpper)
.getOrElse(null),
);
assert.equal(
'anon',
Just({}).chain(getName).chain(toUpper)
.orElse(getDefaultName)
.getOrElse(null),
);
}
「値を Maybe に詰め込んで、 Maybe の世界で map と chain で次々と処理していく」という処理パターンは、「値を Promise に詰め込んで、Promise の世界で then で次々と処理していく」という処理パターンと類似しているという ブログ記事 があって、なるほどなあ、と思った。