Skip to content

Instantly share code, notes, and snippets.

@usametov
Forked from jackrusher/maria-experts.cljs
Last active April 27, 2021 03:17
Show Gist options
  • Select an option

  • Save usametov/42619f874403900cedb6b7a8d97445e9 to your computer and use it in GitHub Desktop.

Select an option

Save usametov/42619f874403900cedb6b7a8d97445e9 to your computer and use it in GitHub Desktop.
;; # Maria for Experts
;; This is a short tour of abstractions we've made in the process of building Maria,
;; which are also available while using the system. It is meant for people with experience using
;; functional programming languages who already know how to evaluate forms in Maria.
;; If you're a beginner to Maria or to programming in general, I recommend starting [here](https://www.maria.cloud/intro).
;; (For the impatient, Command-Enter — Control-Enter on a PC — within a code block with evaluate the code before the cursor.)
;; ## Notebook interface
;;
;; In the notebook tradition exemplified by iPython Notebooks,
;; one has a mix of prose and code with the ability to visualize the results of evaluating a particular piece of code.
;; For example, `jujub-bird` evaluates to a new var containing the value `15`.
(def jujub-bird 15)
;; While `squared` evaluates to a function that returns the square of the number it receives.
(defn squared [x]
(* x x))
;; And, finally, we can see the effect of applying the `squared` function to `jujub-bird`.
(squared jujub-bird)
;; One problem with this approach is that changes made to earlier definitions
;; do not effect future references automatically. So, for example, changing `jujub-bird` will not result
;; in re-computation of `(squared jujub-bird)`.
;; This can lead to strange inconsistencies between the visible output at a particular moment
;; and the actual meaning of the code.
;; ## Dataflow notebook
;; One way to work around this situation is to use a dataflow paradigm, similar to a spreadsheet,
;; to propagate changes through the notebook based on declared dependencies between code blocks.
;; We call the mechanism we use for this purpose a "cell", after the concept of a spreadsheet cell.
;; In this case, any change to `slithy` will cause `toves` to be re-calculated, which will
;; -- assuming these three blocks have been evaluated once in the past -- result in a circle of the correct size being drawn.
(defcell slithy 4)
(defcell toves (squared @slithy))
(cell (circle @toves))
;; We use `defcell` for named cells, `cell` for anonymous cells, and the `@` sigil
;; to declare references to previously defined cells. This last convention is borrowed
;; from the syntax for clojure `atom`s, whose semantics our cells largely share.
;; Note that we use explicit declaration of cells and dependencies rather than
;; automatically calculating dependencies over all variable references
;; so the programmer has control over the evaluation strategy they prefer in a given situation.
;; ## Temporal Recursion (Sorensen, 2005)
;; We also provide a mechanism for temporal recursion using `interval`.
;; This is similar to Javascript's `setInterval` with the key difference that each invocation of the function
;; will be passed the result of the previous invocation. This can be viewed as a higher order function that
;; converts the function passed to it into an infinite recursive function that's performed at a fixed interval.
;; So, for example, one can create an infinite stream of integers -- i.e. a counter -- that updates
;; every half second by passing `inc` to `interval`:
(defcell counter (interval 500 inc))
;; Here we generate an infinite sequence of random color names at a 250ms interval:
(defcell a-color
(interval 250 #(rand-nth (seq color-names))))
;; This feature is especially useful in conjuction with the dataflow properies of cells. Here we render a circle that automatically updates its color and position as `counter` and `a-color` change:
(cell (->> (circle 20)
(position (+ 60 (* 20 (Math/sin @counter)))
(+ 60 (* 20 (Math/cos @counter))))
(colorize @a-color)))
;; ## Temporal recursion with sequences
;; Given an infinite sequence of random numbers that update once a second:
(defcell random-number
(interval 1000 #(rand-int 30)))
;; We can use the clojure's built-in sequence functions to maintain a running window of the last ten values from that stream (note that cells provide their most recent computed value at `@self`):
(defcell last-ten
(take 10 (conj @self @random-number)))
;; From which we can then generate a live bar graph using only `map` and our `rectangle` drawing primitive:
(cell (map (partial rectangle 12) @last-ten))
;; # DOM and events
;; We also have a `hiccup`/`sablono`-style templating system built in.
;; This allows someone with browser programming experience to create arbitrary HTML and SVG output that
;; will be rendered in place:
(html [:div {:style {:color "white"
:background-color "pink"
:font-size 42
:font-weight "bold"
:height 100
:width 100
:padding 20}}
"Hi!"])
;; When paired a cell to maintain state:
(defcell switch false)
;; ... simple interactions are simply specified:
(cell (listen :click
(fn [] (swap! switch not))
(if @switch
(colorize "red" (circle 40))
(colorize "black" (circle 40)))))
;; Clicking the circle thus drawn will cause the color to toggle.
;; ## A practical example
;; Using cells one can implement a pattern similar to that seen in libraries like Reagent,
;; where application state is stored in a single cell (similar in intent to a `ratom`).
;; Here we create a set of controls for a visualization:
(defcell controls {:x 0
:y 0
:param-1 30
:param-2 30
:scale 30})
;; Changes to `controls` will be carried along to dependent cells, such that these two functions that
;; will be re-generated as needed:
(defcell ->x
#(+ (:x @controls) (* (:param-1 @controls) (Math/pow (Math/sin %) 3))))
(defcell ->y
#(+ (:y @controls)
(- (* (:param-2 @controls) (Math/cos %)))
(* 5 (Math/cos (* 2 %)))
(* 2 (Math/cos (* 3 %)))
(Math/cos (* 4 %))))
;; This anonymous cell calculated ~1400 points by plotting the values returned by the above
;; two functions to x/y coordinates, then converting those points into a path drawn in red with a 3px stroke width:
(cell
(->> (mapcat #(vector (* (:scale @controls) (@->x %))
(* (:scale @controls) (@->y %)))
(range 0 2200 3.146))
polygon
(stroke "red")
(stroke-width 3)))
;; Any manual change to `controls` will trigger an immediate re-render of the above plot,
;; but it's often more convenient to be able to change values more fluidly.
;; Here we build up a HTML table directly from the `controls` map, with the name of each parameter
;; set beside a slider that updates `controls`. In these few lines of code we are able to create an _ad hoc_
;; user interface within our notebook and then use that to explore the data:
(->> @controls
(mapv (fn [[k v]]
[:tr
[:td {:style {:padding-right 10}} (name k)]
[:td [:input {:type "range" :min "1" :max "30" :default-value v
:on-change (value-to-cell! controls k)}]]]))
(into [:table])
html)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment