Skip to content

Instantly share code, notes, and snippets.

@susisu
Last active March 3, 2019 18:38
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save susisu/356c7dc8b07f478807e4 to your computer and use it in GitHub Desktop.
Save susisu/356c7dc8b07f478807e4 to your computer and use it in GitHub Desktop.
関数型 JavaScript がつらい

関数型 JavaScript がつらい

明けましておめでとうございます. @susisu2413 です.

この記事は OUCC アドベントカレンダー 2014 21日目の記事です. 昨日は @spring_raining 氏による大学のグループ開発でgitを布教する方法でした.

JavaScript での関数型処理

盛んに「関数型で脱アルゴリズム」などと叫ばれる昨今ですが, その真偽はさておき, 処理の単位として関数を用いるのは処理の一般化や再利用をする場合に役に立つことが多いです (たぶん). 関数型っぽい処理としては, 生の JavaScript では Array のメソッドに map, filter, reduce などがあります (詳しくは このへん を参照). Underscore.js とか Lo-Dash でもいいと思います (私はちゃんと使ったことがないのでよく知りません).

ここでは上記のような関数を引数にとるようなメソッド (あるいは関数) を使う場合に, 微妙に不便だったりする事例とその解決法を紹介します.

メソッドがそのまま渡せない問題

例えば, 文字列を大文字にしたい場合はこんなかんじで良いですね.

"foo".toUpperCase();
// -> "FOO"

では, 文字列が入っている配列のすべての要素に対して, map を使って toUpperCase メソッドを適用して大文字にしたいとしましょう.

["foo", "bar"].map(function (str) {
    return str.toUpperCase();
});
// -> ["FOO", "BAR"]

toUpperCasemap したいだけなのに, なんだか複雑なことになってしまいます そんなに複雑ではない.

toString メソッドの本体は String.prototype.toUpperCase という関数なのですが, これを引数の文字列を大文字にするように使うには,

String.prototype.toUpperCase.call("foo");
// -> "FOO"

のようにする必要があります. でも, map に与えるのは String.prototype.toUpperCase.call ではいけません. call もメソッド (本体は Function.prototype.call) だからですね. というわけで結局こうなります.

["foo", "bar"].map(Function.prototype.call.bind(String.prototype.toUpperCase));
// -> ["FOO", "BAR"]

逆に最初より長くなってしまったので, こんな感じで関数を用意しておきましょう.

function method(method) {
    return Function.prototype.call.bind(method);
}

こうすれば, サクッと.

["foo", "bar"].map(method(String.prototype.toUpperCase));
// -> ["FOO", "BAR"]

まあ, 結局中身は最初のやつとあまり変わらないんですけどね.

コンストラクタがそのまま渡せない問題

JavaScript のコンストラクタはただの関数なので, コンストラクタとして使う場合は new 演算子を使う必要があります.

function Hello(name) {
    this.name = name;
}
Hello.prototype.say = function () {
    console.log("Hello, " + this.name);
};

var foo = new Hello("world");
foo.say();
// -> "Hello, world"

これもメソッドと同じようにそのまま map などに渡すことが出来ません. 自前でコンストラクタの処理を実装する必要があるので, 諦めて仕様書を開いて [[Construct]] を見ましょう.

function construct(Constructor) {
    return function () {
        if (typeof Constructor !== "function") {
            throw new TypeError(typeof Constructor + " is not a function");
        }
        var proto = Constructor.prototype;
        if (proto === null || typeof proto !== "object") {
            proto = Object.prototype;
        }
        var obj = Object.create(proto);
        var result = Constructor.apply(obj, arguments);
        if (result === null || typeof result !== "object") {
            return obj;
        }
        else {
            return result;
        }
    };
}

こうすれば以下のようにできるので便利です.

["foo", "bar"].map(construct(Hello)).forEach(method(Hello.prototype.say));
// -> "Hello, foo"
// -> "Hello, bar"

追記

上記のコードはビルトインのコンストラクタなどには使えないぽいので,

function construct(Constructor) {
    return function () {
        return new (
            Function.prototype.bind.apply(
                Constructor,
                [undefined].concat(Array.prototype.slice.call(arguments))
            )
        )();
    };
}

としたほうが良さそうです.

reduce と reduceRight の callback の引数がどうの

諦めて (๑❛ᴗ❛๑)♡

var concat = function (x, y) { return x + y; };
["a", "b", "c"].reduce(concat, "");
// -> "abc"
["a", "b", "c"].reduceRight(concat, "");
// -> "cba"

まとめ

関数型 JavaScript は時折つらい.

コーホー

OUCC にはうさぎはいません.

明日

@E_Rubik さんです.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment