Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Immutable and immutable-js

zhangyaning
November 06, 2015

Immutable and immutable-js

immutable and immutable-js

zhangyaning

November 06, 2015
Tweet

Other Decks in Technology

Transcript

  1. Immutability iex(1)> list = [:a, :b, :c] [:a, :b, :c]

    iex(2)> List.delete(list, :b) [:a, :c] iex(3)> list [:a, :b, :c] iex(4)> list = List.delete(list, :b) [:a, :c] iex(5)> list [:a, :c]
  2. defmodule MyList do def length(the_list) do length(the_list, 0) end def

    length([], count) do count end def length([_head|tail], count) do length(tail, count + 1) end end
  3. Let’s say you have a user profile, represented as a

    JavaScript object: var profile = { name: 'Scott', user_id: 42, pic: 'https://scott.mn/me.jpg', friends: [... array of objects ...], latest_tweets: [... array of objects ...], ... }; When one piece of data, let’s say the profile pic, changes: profile.pic = 'https://scott.mn/me2.jpg';
  4. function arrayEqual(a, b) { if (a.length !== b.length) return false;

    for (var i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } var FriendsView = { render: function(profile) { if (arrayEqual(profile.friends, this_lastFriendsValue)) { return; } // use .slice() to make a copy this._lastFriendsValue = profile.friends.slice(); // ... actually update the DOM ... } }
  5. In Clojure, immutable data structures are the default and you

    have to really go out of your way to create a mutable array, so this problem doesn’t come up much. But in JavaScript …..
  6. Undo and redo ❖ Store the entire state of your

    app in a tree of immutable collections. ❖ Keep a stack of all the states you’ve ever seen, pushing the new state into the stack when something changes. ❖ Pop the stack when the user wants to undo.
  7. No defensive copying An example of where this can bite

    you is the options hashes many JavaScript APIs take: var options = {foo: true}; lib.doAThing(options); lib.doAnotherThing(options); Innocuous enough, but what if the library is doing something like this: exports.doAThing = function(options) { // prepare in some way whatever(); // then delegate to another method options.silent = true; exports.doAnotherThing(options); }; exports.doAnotherThing = function(options) { // ... behaves completely differently if options.silent is true ... };
  8. http://jsbin.com/sazupukide/edit?js,output handleChangeSchoolSameValue() { this.setState((data) => ({ user: { school: {

    high: data.user.school.high, middle: 'middleSchoolName' } } })); }, shouldComponentUpdate(nextProps, nextState){ return ( !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState) ); }
  9. If you mutate data like this: myData.x.y.z = 7; //

    or... myData.a.b.push(9); var newData = deepCopy(myData); newData.x.y.z = 7; newData.a.b.push(9);
  10. Unfortunately, deep copies are expensive, and sometimes impossible. var newData

    = extend(myData, { x: extend(myData.x, { y: extend(myData.x.y, {z: 7}), }), a: extend(myData.a, {b: myData.a.b.concat(9)}) }); var newData = React.addons.update(myData, { x: {y: {z: {$set: 7}}}, a: {b: {$push: [9]}} });
  11. var Immutable = require('immutable'); var map1 = Immutable.Map({a:1, b:2, c:3});

    var map2 = map1.set('b', 50); map1.get('b'); // 2 map2.get('b'); // 50
  12. http://jsbin.com/mujihegowi/edit?js,output handleChangeSchoolSameValue() { this.setImmState(d => d.updateIn(['user','school', 'middle'], (v)=> 'middleSchoolName2')) },

    shouldComponentUpdate(nextProps, nextState){ return ( !shallowEqualImmutable(this.props, nextProps) || !shallowEqualImmutable(this.state, nextState) ); },
  13. ❖ Fully immutable collections: once instantiated, it’s guaranteed that nothing

    about them can change. ❖ As little effort as possible needed to use them with JS libraries. ❖ No surprises; APIs follow established conventions.
  14. var obj = {foo: "original"}; var notFullyImmutable = Immutable.List.of(obj); notFullyImmutable.get(0)

    // { foo: 'original' } obj.foo = "mutated!"; notFullyImmutable.get(0) // { foo: 'mutated!' } Problem 1: Like Object.freeze, the collections are only shallowly immutable. In other words, any mutable objects we place in immutable.js collections remain mutable.
  15. Problem 2: We had to do a lot of converting

    back and forth to go from immutable.js collections to vanilla JS collections. It surely wasn’t as bad as the quick-and-dirty “serialize to JSON” hack would have been, but it was a recurring pain nevertheless. var hobbits = Immutable.fromJS([{name: "Frodo"}, {name: "Samwise"}, {name: "Meriadoc"}]) _.pluck(hobbits, "name") // Runtime exception _.pluck(hobbits.toJS(), "name") // ["Frodo", "Samwise", "Meriadoc"]
  16. https://github.com/facebook/immutable-js/issues/23 var v = Immutable.Vector(1, 2, 3) v.toString() // "Vector

    [ 1, 2, 3 ]" var v2 = v.map(function (x) { return x + 1; }) v2.toString() // "Seq [ 2, 3, 4 ]" v2.delete(0) // TypeError: undefined is not a function
  17. Integrating with Third-Party Libraries An unanticipated benefit of this was

    realizing we could use much of Underscore’s library of functions as normal. When we wanted to use _.max or _.find, we passed them a seamless-immutable array and everything just worked.
  18. There are a few ways to resolve this. One way

    is to use asMutable, which returns a vanilla JS array representation of a seamless-immutable array. Another is to use another library’s map function, such as _.map from Underscore. In CoffeeScript, you also can use a list comprehension (for…in) instead of map to similar effect. https://github.com/u2/react-alt-demo/blob/master/assets/javascripts/components/ CommentList.jsx https://github.com/facebook/immutable-js#embraces-es6
  19. Performance Three performance-related features that we lost when switching from

    immutable.js to seamless-immutable are lazy sequences, batching mutations, and Persistent data structures under the hood.