質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.33%

  • JavaScript

    17588questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

javaScript コード、解説をお願いします

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 609

Behemoth

score 21

 前提・実現したいこと

「codewars]というサイトを使ってjavaScriptを学習しているものです。
コードを解説していただきたく存じます。

add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10
add(1)(2)(3)(4)(5); // 15
このように、カッコでくくった数字をチェーンのようにつなげて、その合計を出力する関数addを作成せよ、という課題がありました。
https://www.codewars.com/kata/a-chain-adding-function/javascript

模範解答は

function add(n){
  var fn = function(x) {
    return add(n + x);
  };

  fn.valueOf = function() {
    return n;
  };

  return fn;
}


とのことでした。
ここで、valueOf をオブジェクトのプロパティとすると、基本型の値と結合する際に、valueOfの中身が返される、と習い、そこがポイントかと思うのですが、理解できませんでした。

どなたか解説をよろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • Lhankor_Mhy

    2018/04/26 17:52

    この模範解答って、みなさん動作してます?

    キャンセル

  • namimon

    2018/04/26 18:00

    関数が戻ってきます

    キャンセル

  • Lhankor_Mhy

    2018/04/26 18:04

    ですよね。数値で受け取るには評価しなきゃいけないから、true || add(1) みたいなおまじないを書かないと……

    キャンセル

回答 1

checkベストアンサー

+2

おー、この回答はちょっと思いつきませんでした。
素敵ですね…覚えておいて今度どっかで使おう。

function add(n){
  var fn = function(x) {
    return add(n + x);
  };

  fn.valueOf = function() {
    return n;
  };

  return fn;
}

console.log(
  Array(100000)
  .fill(0)
  .map((_, i) => i + 1)
  .reduce((sum, it) => sum(it), add)
  .valueOf()
)
// 5000050000

そして使い方はこうですか。
はい、大体理解したので早速解説に移ります。


addは関数です。
中身でfnという関数を宣言して、fn.valueOfというプロパティをくっつけてすぐに返します。

2回目以降が素晴らしくて、
2回はfnに代入された引数がxの無名関数が実行されます。
その結果はreturn add(n + x)

足し算した結果が再帰関数のようにaddに転がり込み、
fnを作成、valueOfプロパティをくっつけて帰ってきます。

3回目以降は全て2回目と同様です。
何回呼び出しても今作ったfnが帰ってきます。

でもこいつ関数じゃん、合計結果の数値が欲しいんだけど?
最終的に値を取り出す時の事を想定してvalueOfが用意されているようです。
値を取り出す時はプロパティのvalueOfを使って取り出しましょう。

console.log(add(1)(2)(5));
// function
console.log(add(1)(2)(5).valueOf());
// 8

おまけ: valueOfプロパティの効果

命題がadd(1)(2)(3)…と無限にチェーン出来るという事は、関数を返し続ける関数である必要があります。
じゃあ値を取り出したくなってもよくわかんないよね…
今回の模範解答はたまたまvalueOfが宣言されていたのだけど……

JavaScriptにはインスタンス化されたオブジェクト等、
Stringとして表せられないものには大抵valueOfというメソッドが用意されており
何時でも値に変換出来るようにするというルールがあります。

function add(n){
  var fn = function(x) {
    return add(n + x);
  };

  fn.valueOf = function() {
    return n;
  };

  return fn;
}

// console.logしたらf 6が表示されるけどなんだ?
console.log(add(1)(2)(3)) // f 6

// 足し算すると普通にNumber型っぽく計算に使える!?
console.log(10 + add(1)(2)(3)) // 16

// valueOfがあればなんでもいけんの?じゃあこれは?
console.log(10 + {
  name: 'taro',
  age: 17,
  valueOf: function(){ return this.age }
})
// 27

// 配列とかどうすんだ?
var arr = [1, 2, 3];
arr.valueOf = function(){
  var sum = 0
  for (var i = 0; i < this.length; i++) {
    sum += this[i]
  }
  return sum
}
console.log(10 + arr) // 16

こういう効果があります。
この辺も含めると、質問文のコードは模範解答となるのは間違いないでしょう。

ただ、add(1)(2)の結果が3という元ネタサイトの記述は違和感がありますけどね。
まぁconsole.logを使って「f 3」と表示されるのはChromeの機能であり
Node.jsや別のブラウザでconsole.logに放り込めばそのまま3と表示される可能性はあります。
(Node.jsでは{ [Function: fn] valueOf: [Function] }と表示されて全然駄目ですが)

問題の関数を返し続ける関数の時点でかなりイレギュラーな問題でしたね。
でも頭の体操としては楽しい問題だと思いますし、コードに深みが出る良い問題&模範解答だと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/04/26 18:14 編集

    詳しいご回答をまことにありがとうございます!
    miyabi-sun様の回答を熟読し、JavaScriptに関する理解を深めたいと思います。
    僕の鈍った頭を鍛えないと...

    キャンセル

  • 2018/04/26 22:02

    一つ質問なのですが(あまりに基本的なことならば、申し訳ありません)、たとえば、add(1)(2)(3)の場合、
    1 add(1)
    2 add(2)
    3 add(3)
    のように処理が行われているということでしょうか?
    また、
    add(1) でfn(つまり、1)が返されと思うのですが、function(x){
    return add(n + x)
    }
    の'x'とはどこから持ってくるのですか?

    キャンセル

  • 2018/04/26 22:21

    これは結構わかりにくい中級者〜上級者が意識する概念だったりするので大丈夫ですよ!
    クロージャーの概念に近い、関数生成時のスコープを利用します。

    add(1)を行うとまず引数n=1になります。
    そしてfnを生成してvalueOfをくっつけ返します。
    戻り値のfn関数は引数xを利用して実行する関数のようです。
    ここまでは楽勝ですよね。

    実はこの時、引数のn=1は宣言されっぱなしでスコープの中で生き続けます。

    続いてadd(1)(2)の解説。
    上の結果を元にfn(2)を実行します。
    fnの関数はreturn add(n + x)ですよね。
    えっ、今叩いたのは2だからx=2は分かるんだけど、nって何なの???

    このnの正体は、最初のadd(1)の引数として宣言されっぱなしで放置されていたn=1君なのです。

    結果としてfn(2)の戻り値はadd(n + x)からadd(1 + 2)になります。
    つまりadd(3)を更に評価して結果を返さなければなりません。
    add(3)の実行結果はn=3で宣言しっぱなしにしたままfn関数を作って返します。

    念のためadd(1)(2)(3)にいきましょう。
    更にfn(3)を実行すると、束縛され続けているn=3とx=3を使いadd(3 + 3)になります。
    add(3 + 3)の評価結果は引数n=6を宣言したままのfn関数です。

    4回目以降は全てこの繰り返しになります。

    キャンセル

  • 2018/04/27 09:53

    ご返事が遅くなりました。
    理解できました!丁寧なご解説、ありがとうございます。
    また、よろしくお願いいたしますm(__)m

    キャンセル

同じタグがついた質問を見る

  • JavaScript

    17588questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。