LoginSignup
2
3

More than 5 years have passed since last update.

JavaScript で Maybe(Option/Optional/Nullable)

Posted at

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 を変換する関数を作って、mapchainをつなげてどんどん変換していく。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 で次々と処理していく」という処理パターンと類似しているという ブログ記事 があって、なるほどなあ、と思った。

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