LoginSignup
2
1

More than 3 years have passed since last update.

Carbonの初期画面のソースコードをひもとく

Last updated at Posted at 2019-05-12

はじめに

Carbon というWebサービスをご存じでしょうか?

Carbon: https://carbon.now.sh/

ソースコードをシンタックスハイライト付きで画像化してくれる、ツイッター投稿などに便利なWebサービスです。この Carbon なのですが、初期画面には短い javascript のコードが表示されます。

const pluckDeep = key => obj => key.split(".").reduce((accum, key) => accum[key], obj);

const compose = (...fns) => res => fns.reduce((accum, next) => next(accum), res);

const unfold = (f, seed) => {
  const go = (f, seed, acc) => {
    const res = f(seed);
    return res ? go(f, res[1], acc.concat([res[0]])) : acc;
  };
  return go(f, seed, []);
};

このコードは一体何を表しているのでしょうか?

アロー関数が連続していたりと javascript では普段見慣れない構文が並んでいたので、順番にひもといてみると、関数型プログラミングというキーワードが浮かび上がってきました。

おことわり

当方勉強中につき、関数型プログラミング自体の細かい解説は避けます。

前準備

これらの関数をひもとくには、2つの知識が必要です。

  1. 関数を返す関数
  2. Array.reduce() の第2引数

関数を返す関数

javascript では関数もオブジェクトであるため、通常の値と同様に、引数で渡したり返り値で返したりできます。

このうち、関数を返す関数の動きを追ってみましょう。

const add = a => (b => a + b);

const add2 = add(2);
// add2 = b => 2 + b
const result = add2(3); // 2 + 3 = 5

add は引数 a を受け取ると関数 b => a + b を返す関数です。
add(2) は引数 2 を受け取ったので、b => 2 + b を返しました。
add2 は引数 b を受け取ると 2 + b を返す関数になります。

()とadd2を省略して、下のように書けます。

// in short
const add = a => b => a + b;
const result = add(2)(3); // 5

アロー関数が連なっている表記のナゾは解けましたね。
この書き方の利点などはさておき、仕組みは理解されたでしょうか?

補足: 「カリー化された関数」で調べると関数型プログラミングのディープな世界に足を突っ込めます

Array.reduce() の第2引数

Array.reduce() は、第1引数に関数 (reducer) を渡すことで、配列からひとつの値を作り出す関数です。

reducer の第1引数にはひとつ前の reducer の出力が、第2引数には配列の値が順々に渡されます。

// accum - accumulate: 蓄積
const result = [2, 3, 4].reduce((accum, next) => accum + next); // (2 + 3) + 4

この Array.reduce() は第2引数として、reducer の初期値を与えることができます。

Array.prototype.reduce() - JavaScript | MDN

const result = [2, 3, 4].reduce((accum, next) => accum + next, 30);
// ((30 + 2) + 3) + 4

Array.reduce() は使いこなすと、map も filter もなんでもこなす、すごいやつなのですが、それはまた別の話。

// mapの代わり
const result = [3, 4, 5].reduce((accum, next) => [...accum, next + 1], []);
// [4, 5, 6]

関数をひもとく

それでは実際に関数をひもといてみましょう。

pluckDeep

const pluckDeep = key => obj => key.split(".").reduce((accum, key) => accum[key], obj);
// .reduce の第2引数: obj
// .reduce の返り値: obj[key][key][key]...

実際の使用例を見て流れを追ってみましょう。

// How to use
const pluckFooBar = pluckDeep('foo.bar');
const result = pluckFooBar({ foo: { bar: 'result!' } });
// key = 'foo.bar'
// key.split(".") = ['foo', 'bar']
// pluckFooBar = obj => (obj[foo])[bar]
// obj = { foo: { bar: 'result!' } }
// result = 'result!'

オブジェクトのプロパティにアクセスする関数だとわかります。

compose

const compose = (...fns) => res => fns.reduce((accum, next) => next(accum), res);
// .reduce の第2引数: res
// .reduce の返り値: next(next(next(res)))...

関数を合成する関数だとわかります。

// How to use
// Sample from https://ramdajs.com/docs/#compose
const calc = compose(Math.abs, a => a + 1, b => b * 2);
const result = calc(-4); // 10
// fns = [Math.abs, a => a + 1, b => b * 2]
// calc = res => ((Math.abs(res)) + 1) * 2
// res = -4
// result = ((Math.abs(-4)) + 1) * 2

上が使用例です。複数の関数を合成して、後から値を与えているのがわかりますね。

unfold

const unfold = (f, seed) => {
  const go = (f, seed, acc) => {
    const res = f(seed);
    return res ? go(f, res[1], acc.concat([res[0]])) : acc;
  };
  return go(f, seed, []);
};
// f(seed) の返り値: [value, nextSeed] or false
// go() の返り値: [value, value, value, ...]

簡単な再帰関数だとはわかります。
しかしこのままでは用途が分からないですね。使用例を出してみましょう。

// How to use
// Sample from https://ramdajs.com/docs/#unfold
const f = n => n > 50 ? false : [-n, n + 10];
const seed = 10;
const result = unfold(f, seed);
// [-10, -20, -30, -40, -50]

シード値と関数からリストを生成する関数だとわかります。

最後に

カリー化, reduce から始まって、pluck, compose, unfold と、実はどれも関数型プログラミングの道具たち。ES6になってアロー関数が導入されたことで、とてもシンプルに書けるようになりました。初期画面のコードは、ES6で関数型プログラミングを始めませんか?という開発者からのメッセージなのでしょう。

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