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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Scala

ScalaはJava仮想マシンで動作を行うオブジェクト指向型プログラミング言語の1つです。静的型付けの関数型言語で、コンパイルエラーの検出に強みがあります。

Haskell

Haskellは高い機能性をもった関数型プログラミング言語で、他の手続き型プログラミング言語では難しいとされている関数でも容易に行うことができます。強い静的型付け、遅延評価などに対応しています。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

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

Q&A

解決済

2回答

663閲覧

このJavaScriptコードを副作用が無いように書き換える為には?

退会済みユーザー

退会済みユーザー

総合スコア0

Scala

ScalaはJava仮想マシンで動作を行うオブジェクト指向型プログラミング言語の1つです。静的型付けの関数型言語で、コンパイルエラーの検出に強みがあります。

Haskell

Haskellは高い機能性をもった関数型プログラミング言語で、他の手続き型プログラミング言語では難しいとされている関数でも容易に行うことができます。強い静的型付け、遅延評価などに対応しています。

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

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

0グッド

1クリップ

投稿2017/08/24 12:00

既にObject内に特定のキーが存在している場合は、そのキーの配列にデータを追加し、まだ、Object内に特定のキーが存在していない場合は、そのキーに配列をセットしてから、データを追加という処理を以下のコードで行なっているのですが、これを副作用のないコードに書き直したいです!

関数型プログラミング???

ですが、どうやったら副作用のない形に以下のコードを書き換えることが出来るかが頭を絞ってもわかりません >_<

副作用のあるコード

JavaScript

1'use strict'; 2 3const payload = [ 4 {id: '001', score: 100}, 5 {id: '001', score: 40}, 6 {id: '100', score: 132}, 7 {id: '100', score: 482} 8]; 9 10//(1)Deep Copyするには?(ライブラリで解決する方法は既に把握済みですが、ライブラリを使わない場合はどう書くのか? 11const state = { 12 '001': [ 13 {id: '001', score: 142}, 14 {id: '001', score: 112} 15 ], 16 '010': [ 17 {id: '010', score: 142}, 18 {id: '010', score: 112} 19 ] 20} 21 22//(2)forEachは値をreturn出来ないが、forEachを使わず副作用の無いように書くにはどうしたら良いのか? 23payload.forEach((data) => { 24 25 if(typeof state[data.id] === 'undefined') { 26 state[data.id] = []; 27 } 28 29 state[data.id].push(data); 30 31});

得たい結果

{ '100': [ { id: '100', score: 132 }, { id: '100', score: 482 } ], '001': [ { id: '001', score: 142 }, { id: '001', score: 112 }, { id: '001', score: 100 }, { id: '001', score: 40 } ], '010': [ { id: '010', score: 142 }, { id: '010', score: 112 } ] }

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

(1)Deep Copyするには?(ライブラリで解決する方法は既に把握済みですが、ライブラリを使わない場合はどう書くのか?

さりげに難問

JavaScript

1// この方法は楽だけど、正規表現やDateみたいなインスタンスが来ると困る 2JSON.parse(JSON.stringify(state)); 3 4// これは駄目 5var hoge = {a: [1, 2, 3], b: 123}; 6var piko = Object.entries(hoge).reduce((a, it) => { 7 a[it[0]] = it[1]; 8 return a; 9}. {}); 10piko.a.push(5); // こんな風に破壊的な修正を加えると… 11 12console.log(hoge); 13// {a: [1, 2, 3, 5], b: 123} ←配列の参照が残ってるのでこっちも変更されてしまう 14 15console.log(piko); 16// {a: [1, 2, 3, 5], b: 123}

再帰呼び出しでコツコツ作っていくしかない。
結論、JSON.parse(JSON.stringify(state))で解決出来なければライブラリ使うべき。

(2)forEachは値をreturn出来ないが、forEachを使わず副作用の無いように書くにはどうしたら良いのか?

関数型プログラミングにはmapがあって、JSにも[].mapが用意されている。
配列に全て同じ関数を実行させて、その結果を取り出すという効果。
なのでforEachを使う場面はそう多くない。

forEach(もしくはeach)は順番に実行するという意味を持つ。
Ajaxを飛ばすとか、console.logで出力するとか、副作用のあるものをバシバシ実行していくためのもの。

こういった処理は関数的に解決するなら畳み込み!
JSでは[].reduceというプロトタイプメソッドがあるのでそれを利用する。

to_stateの部分は関数として切り出せたから楽にテスト出来るね。
しかしpushが破壊的なメソッドだからどう扱うかがJSの課題だね。
ディープコピーをしないとすぐにstateが壊れる。

草案だとmergeの最初でシャローコピーしてたけどそれじゃ駄目で、
ディープコピーが必要になったのでcloneというメソッドを作って入り口で作って対応した。

JavaScript

1const payload = [ 2 {id: '001', score: 100}, 3 {id: '001', score: 40}, 4 {id: '100', score: 132}, 5 {id: '100', score: 482} 6]; 7 8const state = { 9 '001': [ 10 {id: '001', score: 142}, 11 {id: '001', score: 112} 12 ], 13 '010': [ 14 {id: '010', score: 142}, 15 {id: '010', score: 112} 16 ] 17} 18 19const clone = (it) => JSON.parse(JSON.stringify(it)) 20const merge = (data, it) => { 21 if (data[it.id] == null) data[it.id] = []; 22 data[it.id].push(it); 23 return data; 24} 25 26console.log(payload.reduce(merge, clone(state))); 27// Object {100: Array(2), 001: Array(4), 010: Array(2)} 28 29console.log(state); 30// Object {001: Array(2), 010: Array(2)}

投稿2017/08/24 13:21

編集2017/08/25 01:17
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/08/24 14:09

おー、まさに関数型プログラミングっぽい!参考になります!明日、コード書いて試してみます。ありがとうございました!さっきアマゾンで関数型プログラミングの本、2冊注文してみたので勉強してみます。
miyabi-sun

2017/08/24 14:10

push自体は副作用のある破壊メソッドとして用意されたものだから、仕方のない部分はあるんだよねぇ。 JSは関数が第一級オブジェクトだから関数型プログラミングで問題解決出来ますよって言ってるけど、 JSそのものがCやJavaそっくりの命令型プログラミング。 ガチの関数型プログラミング言語みたいに、関数使って全ての問題を解決する為の便利な道具が揃ってるわけじゃないんだよね…。 結局行数増えたり、実行効率増えて微妙になっちゃう泣き所も多くてJSという言語の弱さが出てると思う。
miyabi-sun

2017/08/24 14:23 編集

>明日、コード書いて試してみます。 電子辞書を何時でも持ち歩く博識な人がいるけど、 同じように簡単に書いて試せる環境用意するのをオススメするよ。 例えばChromeやFireFoxではF12押せばコンソール画面が出てきて、私のコードを丸々コピペして試せる。 色々ガチャガチャ変えながら試すなら腰を据えてになると思うけどね。 例えば私の使っているVimだったらビジュアルモードで行を選択して、 `:w !node`コマンドでファイルの部分保存をREPLコマンドに直接投げて確認という必殺技がある。 hayatomoさんの使ってるエディタやIDEでもなんかできそう。 フットワークの軽さはエンジニアの力を伸ばす事に繋がると思うから、時間が出来たら色々試してみて。
miyabi-sun

2017/08/25 01:19

シャローコピーはstate['100']に入っている配列の参照が紐付いたままだったからstateが壊れてた(´・ω・`) cloneメソッドを書き足したので見といてね。
退会済みユーザー

退会済みユーザー

2017/08/25 01:44

ありがとうございます!ネストされたstateオブジェクトはうっかり、元のオブジェクトを完コピしたつもりでもコピー先の編集と同時にコピー元も編集してしまったりして、ハマります。。。つい最近、それでハマったので!
guest

0

ベストアンサー

コード

シャローコピーで十分なような。

JavaScript

1'use strict'; 2 3const payload = [ 4 {id: '001', score: 100}, 5 {id: '001', score: 40}, 6 {id: '100', score: 132}, 7 {id: '100', score: 482} 8]; 9 10const state = { 11 '001': [ 12 {id: '001', score: 142}, 13 {id: '001', score: 112} 14 ], 15 '010': [ 16 {id: '010', score: 142}, 17 {id: '010', score: 112} 18 ] 19} 20 21for (let data of payload) { 22 const id = data.id; 23 data = Object.assign({}, data); // シャローコピー 24 25 if (!Object.prototype.hasOwnProperty.call(state, id)) { 26 state[id] = [data]; 27 } else { 28 state[id].push(data); 29 } 30} 31 32var expectedResults = { 33 '100': [ 34 { id: '100', score: 132 }, 35 { id: '100', score: 482 } 36 ], 37 38 '001': [ 39 { id: '001', score: 142 }, 40 { id: '001', score: 112 }, 41 { id: '001', score: 100 }, 42 { id: '001', score: 40 } 43 ], 44 45 '010': [ 46 { id: '010', score: 142 }, 47 { id: '010', score: 112 } 48 ] 49}; 50 51console.log(JSON.stringify(state) === JSON.stringify(expectedResults)); // true 52console.log(JSON.stringify(state)); // {"100":[{"id":"100","score":132},{"id":"100","score":482}],"001":[{"id":"001","score":142},{"id":"001","score":112},{"id":"001","score":100},{"id":"001","score":40}],"010":[{"id":"010","score":142},{"id":"010","score":112}]}

副作用がないというか、参照透過性?のある純粋関数を作りたいのです。

JavaScript

1'use strict'; 2const payload = [ 3 {id: '001', score: 100}, 4 {id: '001', score: 40}, 5 {id: '100', score: 132}, 6 {id: '100', score: 482} 7]; 8 9const state = { 10 '001': [ 11 {id: '001', score: 142}, 12 {id: '001', score: 112} 13 ], 14 '010': [ 15 {id: '010', score: 142}, 16 {id: '010', score: 112} 17 ] 18}; 19 20var expectedResults = { 21 '100': [ 22 { id: '100', score: 132 }, 23 { id: '100', score: 482 } 24 ], 25 26 '001': [ 27 { id: '001', score: 142 }, 28 { id: '001', score: 112 }, 29 { id: '001', score: 100 }, 30 { id: '001', score: 40 } 31 ], 32 33 '010': [ 34 { id: '010', score: 142 }, 35 { id: '010', score: 112 } 36 ] 37}; 38 39function sample (payload, state) { 40 const hasOwnProperty = Object.prototype.hasOwnProperty; 41 const results = JSON.parse(JSON.stringify(state)); 42 43 for (let data of payload) { 44 const id = data.id; 45 data = Object.assign({}, data); 46 47 if (!hasOwnProperty.call(state, id)) { 48 results[id] = [data]; 49 } else { 50 results[id].push(data); 51 } 52 } 53 54 return results; 55} 56 57const results = sample(payload, state); 58 59console.log(JSON.stringify(state) === JSON.stringify(expectedResults)); // true 60console.log(JSON.stringify(state)); // {"100":[{"id":"100","score":132},{"id":"100","score":482}],"001":[{"id":"001","score":142},{"id":"001","score":112},{"id":"001","score":100},{"id":"001","score":40}],"010":[{"id":"010","score":142},{"id":"010","score":112}]}

Deep Copy

//(1)Deep Copyするには?(ライブラリで解決する方法は既に把握済みですが、ライブラリを使わない場合はどう書くのか?

プリミティブ値の集合なら、

JavaScript

1JSON.parse(JSON.stringify(object));

そうでないなら、Object.assign もしくは Object.assign の互換コードを再帰処理して下さい。

完全な deep copy ではなく、変数 state 特化のコードで良ければ、次のように書けます。

JavaScript

1const state = { 2 '001': [ 3 {id: '001', score: 142}, 4 {id: '001', score: 112} 5 ], 6 '010': [ 7 {id: '010', score: 142}, 8 {id: '010', score: 112} 9 ] 10}; 11 12const stateClone = Object.entries(state).reduce((state, entry) => { 13 state[entry[0]] = entry[1].map(Object.assign.bind(null, {})); 14 return state; 15}, state); 16 17console.log(JSON.stringify(state) === JSON.stringify(stateClone)); // true

Re: hayatomo さん

投稿2017/08/24 12:36

編集2017/08/24 14:34
think49

総合スコア18164

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/08/24 12:47

ご回答ありがとうございます! しかーし!ぬぁー!!まだ、回答募集するつもりが、誤タップでベストアンサーボタン押してしまいました!
退会済みユーザー

退会済みユーザー

2017/08/24 12:48

このコードですと、stateオブジェクトを直接アップデートしてしまってるので、副作用ありますよね?
退会済みユーザー

退会済みユーザー

2017/08/24 12:50

副作用がないというか、参照透過性?のある純粋関数を作りたいのです。
think49

2017/08/24 12:52

なるほど。では、回答後半の方法を参照してディープコピーして下さい。
退会済みユーザー

退会済みユーザー

2017/08/24 12:54

stateとpayloadを引数に渡す関数を作れば良いのですよね? その関数の中でstateをディープコピーして、あとは、掲載頂いたコードを交え、最後にコピーしたstateをアップデートしたものをリターンすれば、純粋関数になりますよね?
think49

2017/08/24 12:57

payload を参照するオブジェクトは返していないので、state を deep copy すれば良いはずです。
think49

2017/08/24 14:09

コードを追記しておきました。
think49

2017/08/24 14:37

関数型プログラミングがお好きなようなので、変数 state を複製するコードを追記しておきました。
退会済みユーザー

退会済みユーザー

2017/08/25 01:42

ご丁寧にありがとうございます!!!参考にさせていただきます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問