Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save paulkoegel/9880692 to your computer and use it in GitHub Desktop.
Save paulkoegel/9880692 to your computer and use it in GitHub Desktop.
The Functional Final Frontier - David Nolen at Clojure/West 2014

The Functional Final Frontier - David Nolen at Clojure/West 2014

(still incomplete, covers only the first 16 minutes)

Video

  • Functional programming and programming with values have proven their usefulness when building all kinds of systems, but they've been hard to apply when building user interfaces.

  • Object-oriented programming and user interfaces came up at around the same time and went hand in hand ever since. First user interfaces were, e.g., written in Smalltalk (created in 1972 by Alan Kay). This led to the development of the Model-View-Controller paradigm.

Model-View-Controller

  • First formulated by Trygve Reenskaug, Adele Golberg et al. at Xerox PARC in 1979.+ MVC has a long shadow, the basic concept is still prevalent today (in Flash, Cocoa, C++, Android etc.). Good ideas eat their children.
  • At a very abstract level, MVC is a sound separation of concerns. But implementations leave much to be desired: b/c it comes from the 70s, where hardware resources were still very scarce, it depends on stateful objects.
  • Tim Berners Lee created HTML - had no idea what we'd be using it for today. DOM wasn't created for the things and complex interfaces we're using it for today. JavaScrip (never intended for professional programming), mutable DOM, client-side MVC frameworks with stateful objects -> not a very good landscape. It's understandable that people loathe client-side programming.
  • Alan Kay analogy: modern software architecture can be compared to the pyramids - yes, it's impressive, but it's really just brute force. There's not much of a concept here, other than it's gonna need a lot of people hundreds of years to do it. Simple concepts can give radically different architectures (e.g. arch - great architectural concept with amazing potential - e.g. in Gothic cathedrals (Cologne Cathedral - https://en.wikipedia.org/wiki/File:Koelner_Dom_Innenraum.jpg)).

No significant advancement in this field. Maybe Functional Programming (FP) has some ideas?

Functional Programming

  • Functional Reactive Programming (FRP), still an active area of research. Comes out of Haskell and pure FP community.
  • Reactive Extensions (Rx) and CSP (Communicating Sequential Processes) are coordination languages and don't address rendering - at the edge of your process you need to mutate something.

What's the arch of FP when applied to user interfaces? -> Persistent data structures. Even in a single-threaded context it's a better framework for building interactive experiences.

Om

  • Started in December 2013.

  • Marries persistent data structures of ClojureScript with Facebook's client-side JavaScript rendering library React.

  • React: bases rendering on diffing.

  • Om draws heavily from old ideas and previous approaches.

  • Another influence was Mathematica, where the data and the user are very close.

  • Om has two aims:

    • create a system that can be applied to complex interactions.
    • serve as a good substrate for more symbolic systems - similar to Mathematica.
  • Four big ideas:

    1. immutable data (notation for indexes follows http://www.wjagray.co.uk/maths/ASCIIMathTutorial.html) f(D_0) = V_0 Throw stateful objects out the window, your entire app state is just one value - D_0 (D with index 0). Blog post: Local State is Poison - Awelon Blue A function is applied to it, which returns another data structure - the view V_0. If the data changes, you don't change the original thing, you just have a new value, you apply a function, you get a new view. How do you make that efficient? That problem is solved entirely by React. It computes a diff between V_0 and V_1 and from that calculates the necessary changes that have to be applied to the DOM. You no longer actually manipulate the DOM directly. diff(V_0, V_1) = CHANGES To undo a view change, simply reverse the arguments: diff(V_1, V_0) = UNDO CHANGES Why diffs?
      • views just rerender when the data changes we don't need explicit observation of the data & don't run into resource issues. one common problem in JS is that you attach event listeners to DOM elements, but when these are removed, the event listeners can cause memory leaks or hamper performance. much less logic to write around data observation & view updates (as Angular, Ember, and Backbone do). if the data changes, the view changes - because everything simply gets rerendered every time.
      • out of the box Om is faster than React
      • when there is a global app state ("one atom to rule them all"), how can we keep things modular? Objects are cool because they're naturally modular. They complect encapsulation - hide their state. We get modularity, but only at the cost of a bad idea, which is hiding state. With regards to state, objects are not actually modular. Aim: preerve component modularity, but also achieve modularity regarding state.
      • Imagine we have some root component with three subcomponents. Say we want to exchange one with a new widget that has the same interface when communicating with the root node - still can break b/c it has hidden state. If a component hides its state, there can be no global undo functionality. This will drive you crazy.
      • App State:
        • an immutable tree of associative data
        • "global" state: it's not as scary as it sounds (we all work with databases all the time and thery're also global mutable state)
        • question: we need a global/local point of view; how can we recover component modularity with a global state? How can a component get a local view on the data, so it sees only the data it needs to see? for Om, David developed his third attempt to get component modularity with global app state.

Cursors

  • Similar to zippers. Simply a wrapper around one of ClojureScript's collections. It's a triple: a consistent snapshot of your app (= data to render, which is consistent), plus a path - where does the data lie in the global app state - plus a reference to the global app state.

  • The path is tracked via normal collection access patterns, which is more natural than zippers, which allow only to move updown/left/right.

    (def app-state {:foo {:bar [{:woz ...} ..]}})
    (om/root some-view app-state
      {:target ...})
    

    We pass on the app-state to some-view. But some-view gets a cursor over that data.

    (defn some-view [data owner]
      (reify
        IRender
        (render [_]
          (let [x (:foo data)]
            ...))))
    

    Where data is:

    :value {:foo {:bar ... }}
    :path []
    :state #<Atom: {:foo ...}>
    

    And x is:

    :value {:bar [{:woz ...} ...]}
    :path [:foo]
    :state #<Atom: {:foo ...}>
    

    another sub-subview, further down the path, might get just the part of the app state it needs to know about:

    (def another-view [data owner]
      (reify
        IRender
        (render [_]
          ...)))
    

    where data would be the following cursor:

    :value {:woz ...}
    :path [:foo :bar 0]
    :state #<Atom: {:foo ...}>
    

    Components actually never know where their data comes from. They don't need to know where the data exists inside the global application state.

    -> Modularity regained! Global app state with component modularity has been achieved!

    Yet this is not enough... (to be continued, see video for the rest)

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