{"action":"created","issue":{"url":"https://api.github.com/repos/ramda/ramda/issues/1088","repository_url":"https://api.github.com/repos/ramda/ramda","labels_url":"https://api.github.com/repos/ramda/ramda/issues/1088/labels{/name}","comments_url":"https://api.github.com/repos/ramda/ramda/issues/1088/comments","events_url":"https://api.github.com/repos/ramda/ramda/issues/1088/events","html_url":"https://github.com/ramda/ramda/pull/1088","id":76649384,"number":1088,"title":"Add `deepMerge`/`set`/`extend`/`mixin`","user":{"login":"paldepind","id":521604,"avatar_url":"https://avatars.githubusercontent.com/u/521604?v=3","gravatar_id":"","url":"https://api.github.com/users/paldepind","html_url":"https://github.com/paldepind","followers_url":"https://api.github.com/users/paldepind/followers","following_url":"https://api.github.com/users/paldepind/following{/other_user}","gists_url":"https://api.github.com/users/paldepind/gists{/gist_id}","starred_url":"https://api.github.com/users/paldepind/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paldepind/subscriptions","organizations_url":"https://api.github.com/users/paldepind/orgs","repos_url":"https://api.github.com/users/paldepind/repos","events_url":"https://api.github.com/users/paldepind/events{/privacy}","received_events_url":"https://api.github.com/users/paldepind/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":27,"created_at":"2015-05-15T08:36:03Z","updated_at":"2016-07-05T23:21:28Z","closed_at":"2015-05-18T15:16:36Z","pull_request":{"url":"https://api.github.com/repos/ramda/ramda/pulls/1088","html_url":"https://github.com/ramda/ramda/pull/1088","diff_url":"https://github.com/ramda/ramda/pull/1088.diff","patch_url":"https://github.com/ramda/ramda/pull/1088.patch"},"body":"This pull request adds a function that recursively combines two supplied objects into one. This has previously been discussed at #1073. This PR uses the name `set` as a non-final placeholder.\r\n\r\nThis implementation follows the rules I outlined in the above mentioned thread with a few changes based on the discussion. These rules leads to a very simple implementation and behavior that is IMO easy to understand:\r\n\r\n1/ The function recurses into ~~arrays and~~ non-atomic objects. Non-atomic objects are objects that have keys in the eyes of `Object.keys` (i.e. they have own enumerable properties). It will for instance recurse into an object literal but not into a date object.\r\n2/ Values from objects are always copied into a new plain object.\r\n3/ ~~Value from arrays are copied into a new array. Sparse arrays are not handled.~~ Arrays are always copied by reference.\r\n\r\n__Use cases__\r\n\r\nAs I've [mentioned](https://gitter.im/ramda/ramda?at=555229f11817239c37e4e6c4) I think the most interesting way to think about such a function is as [a way to set properties](https://gitter.im/ramda/ramda?at=5552359df853e7f14c2b84c5) on objects.\r\n\r\nThis way of thinking has changed my opinions on the implementation in two ways:\r\n\r\n1. The overriding object should be first instead of last to be consistent with `assoc` and `assocPath`. `R.assoc('c', 3, {a: 1, b: 2})` matches `R.set({c: 3}, {a: 1, b: 2})`.\r\n2. I now think of the returned object as \"`b` with these changes from `a`\" instead of \"a brand new thing made up of `a` and `b`.\" With the first view I'd expect as little deep copying as possible but with the second view I'd expect everything to be deep copied. This PR implements the former.\r\n\r\nI see this function as a more powerful and useful version of `assoc` and `deepAssoc`.\r\n\r\n```javascript\r\n// Compare this\r\nvar o = {a: {b: 1, c: 2, d: 3}};\r\nvar o2 = R.set({a: {b: 4, c: 5}}, 0);\r\n// To this\r\nvar o3 = R.compose(\r\n  R.assocPath(['a', 'b'], 4)\r\n  R.assocPath(['a', 'c'], 5)\r\n)(o);\r\n```\r\n\r\nI think this is a very welcome addition to Ramda. Currently Ramda has a lot of great functions for working with arrays without mutating them but it feels a bit weaker with regards to objects.\r\n\r\n__About arrays__\r\n\r\n@ivan-kleshnin had some [great reasons](https://github.com/ramda/ramda/issues/1073#issuecomment-100600208) why we should not (unlike Lodash) attempt to merge arrays.\r\n\r\nWhile being able to do something like this is neat:\r\n\r\n```javascript\r\nvar users = {\r\n  'data': [{ 'user': 'barney' }, { 'user': 'fred' }]\r\n};\r\nvar ages = {\r\n  'data': [{ 'age': 36 }, { 'age': 40 }]\r\n};\r\n_.merge(users, ages);\r\n// → { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }\r\n```\r\n\r\nBut the same behavior can also be a disaster:\r\n\r\n```javascript\r\nvar users = {\r\n  'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }]\r\n};\r\nvar anotherUser = {\r\n  'data': [{ 'user': 'kim', 'age': 42 }] // new user, not an update\r\n};\r\n_.merge(users, anotherUser);\r\n// → { 'data': [{ 'user': 'kim', 'age': 42 }, { 'user': 'fred', 'age': 40 }] }\r\n// Barney disappeared :(\r\n```\r\n\r\n__About naming__\r\n\r\nI really don't know what to call this thing. In my head the name `merge` implies merging two conceptually equal objects. Not applying a few changes to an existing object. On the other hand @joneshf seemed to have been thinking about `merge` like this all along. I really don't know.\r\n\r\nBut I definitely think that having both this function _and_ `merge` would be too much. I choose `set` as the name in this PR just because I had to call it something (and `set` might not be a bad name).\r\n\r\n__Symmetry with `R.evolve`__\r\n\r\nWhen/if this gets merged I think `R.evolve` should be made recursive in the same way as well. I think these two functions together provides a very solid experience for updating object without mutations.\r\n\r\nHandling the before mentioned Lodash example is very easy and, importantly, free from bad surprises (the example below can be implemented with todays `evolve` and `merge` but this power does not extend to nested objects).\r\n\r\n```javascript\r\nvar users = {\r\n  'data': [{ 'user': 'barney' }, { 'user': 'fred' }]\r\n};\r\nvar ages = {\r\n  'data': [{ 'age': 36 }, { 'age': 40 }]\r\n};\r\nR.evolve({data: R.zipWith(R.set, ages.data)}, users);\r\n// { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }\r\n\r\nvar anotherUser = {\r\n  'data': [{ 'user': 'kim', 'age': 42 }] // new user, not an update\r\n};\r\nR.evolve({data: R.concat(anotherUser.data)}, users);\r\n// { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }, { 'user': 'kim', 'age': 42 }] }\r\n// Barney did not disappear :)\r\n```\r\n\r\n__Cycles__\r\n\r\nThis implementation does not detect cycles. I agrees with @ivan-kleshnin's [opinion on this](https://github.com/ramda/ramda/issues/1073#issuecomment-100601526). Also as far as I can think the only way to create data with cycles it with mutations (I could be wrong)? So not supporting them should be a minor issue. If there was a very simple way to detect cycles that would not be too inefficient I think adding it would be great though. But I can't think of one.\r\n\r\nConsider this PR to be nothing but my current suggestion for an implementation. Everything is up for discussion :)"},"comment":{"url":"https://api.github.com/repos/ramda/ramda/issues/comments/230631368","html_url":"https://github.com/ramda/ramda/pull/1088#issuecomment-230631368","issue_url":"https://api.github.com/repos/ramda/ramda/issues/1088","id":230631368,"user":{"login":"davidchambers","id":210406,"avatar_url":"https://avatars.githubusercontent.com/u/210406?v=3","gravatar_id":"","url":"https://api.github.com/users/davidchambers","html_url":"https://github.com/davidchambers","followers_url":"https://api.github.com/users/davidchambers/followers","following_url":"https://api.github.com/users/davidchambers/following{/other_user}","gists_url":"https://api.github.com/users/davidchambers/gists{/gist_id}","starred_url":"https://api.github.com/users/davidchambers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/davidchambers/subscriptions","organizations_url":"https://api.github.com/users/davidchambers/orgs","repos_url":"https://api.github.com/users/davidchambers/repos","events_url":"https://api.github.com/users/davidchambers/events{/privacy}","received_events_url":"https://api.github.com/users/davidchambers/received_events","type":"User","site_admin":false},"created_at":"2016-07-05T23:21:28Z","updated_at":"2016-07-05T23:21:28Z","body":"> Am I missing a way to achieve `deepMerge` […]?\r\n\r\nYou're not missing anything. Ramda doesn't currently provide such a function.\r\n"}}
{"actor":{"display_login":"davidchambers"}}