Fork me on GitHub
#hoplon
<
2017-01-21
>
thedavidmeister00:01:18

@flyboarder @jumblerg yeah the enter/update/exit stuff kind of got in the way of javelin, so i stopped using it, but the things like line generator functions are great

qqq02:01:45

is there any library that binds castra to google app engine / data store?

thedavidmeister04:01:25

@qqq this isn't really answering your q, but fyi you can use regular ajax/ws pretty easily by just putting the responses in a cell

qqq04:01:52

@thedavidmeister: I don't understand your response. with gae, I will be using clj on both client and server side

qqq04:01:08

so if castra is already providing rpc to my gae/clj/server side, what would I be using ajax/ws for ?

thedavidmeister04:01:20

oh i mean, i don't use castra

thedavidmeister04:01:28

if you're just getting started, there's other options 🙂

qqq04:01:34

why would I want to not use castra?

qqq04:01:52

castra is probably the main reason I'm testing a switch from re-frame to hoplon/javelin/castra

thedavidmeister04:01:04

oh, well in that case, carry on

qqq04:01:50

so with castra, I can easily call server side functions in my cljs/client side

qqq04:01:10

now my qeustion is -- does someone have a nice banding integrating the clj/server side of castra -- with the google app engine java bindings

thedavidmeister04:01:13

i have used castra for some basic stuff

qqq04:01:13

because that would be magical

thedavidmeister04:01:49

i don't personally, but that doesn't mean it's not out there

qqq04:01:29

do you konw what the hl part of *.cljs.hl provides over plain cljs? I know that boot does some .cljs.hl -> .cljs compilation, but I have yet to figure out why we can't just use plain cljs - i.e. waht addiitonal expressivitiy is .cljs.hl providing

thedavidmeister04:01:58

i just use plain .cljs

thedavidmeister04:01:17

my index file looks like this

thedavidmeister04:01:20

(page "index.html"
  (:require [bootstrap.core]
            [pages.dom]))

(pages.dom/current-page)

thedavidmeister04:01:59

there's a blog post floating around that goes into way more detail about what .hl is actually doing

thedavidmeister04:01:18

maybe @alandipert can point you in the right direction

qqq04:01:40

one mpre question: in https://github.com/hoplon/hoplon/wiki/Get-Started

(ol
  (loop-tpl :bindings [n history]
    (li (cell= (str "n was " n)))))
section "Display Sequence in DOM" is it recreating a new list and re-rendering all itmes - or is it smart enough to just add one item? i.e. is it O(n) or O(1) dom events when I conj to the history

thedavidmeister04:01:06

not recreating the list

thedavidmeister04:01:10

that's why it exists

thedavidmeister04:01:26

actually, slightly more complicated, but not much

thedavidmeister04:01:33

when you add something it gets added

thedavidmeister04:01:52

when you modify a cell it propagates values into the dom you've built

thedavidmeister04:01:14

if you remove something, it gets detached from the dom but is still there in memory, ready to be added back in, like a cache

thedavidmeister04:01:53

actually, you should know that hoplon doesn't re-render all items

thedavidmeister04:01:12

that's what certain other frameworks are attempting to do/simulate

thedavidmeister04:01:27

it's just creating dom elements and binding events so that their attributes match cell values

thedavidmeister04:01:44

that should definitely be in the wiki if it isn't already >.<

qqq04:01:36

here's the thing the history function is defined as:

(add-watch clicks :history #(swap! history conj %))
now, hoplon has no idea that we're doing append only -- on an update, hoplon only sees: (1) old history list (2) new history list so then, does hoplon take these two data structures, diffs them (i.e. gets a list of add, del, update) -- then it does the DOM magic ?

thedavidmeister05:01:56

aaah, so there's not really any "dom magic"

thedavidmeister05:01:57

that's the thing

thedavidmeister05:01:18

think of it more like, you have cells, and when their value "changes" (just checks equality of before/after)

thedavidmeister05:01:42

then hoplon will make the dom elements have the new value for whatever

qqq05:01:16

I understand the cells / frp

qqq05:01:18

WHat I don't . understand is this:

qqq05:01:24

suppose my old history has 2000 items

qqq05:01:29

and my new history has 2001 items

qqq05:01:39

now, is hoplon goign to do 2000 dom actions or just 1 dom action?

qqq05:01:49

and if it only does 1 dom action, how does it figure out what that dom action is

thedavidmeister05:01:52

well, if you're just adding 1

qqq05:01:00

this is ONE CELL, containing 2000 ITEMS in a LIST

thedavidmeister05:01:33

pretty sure that the loop tpl is making cell= under the hood

thedavidmeister05:01:43

so there will be equality checks and nothing will happen in the dom

alandipert05:01:02

the two places cells interact with the dom is for providing attribute values and for populating the children of an element

qqq05:01:09

could you point me at the code for this? I would love to read this and see what's happening uner the hood

alandipert05:01:32

the easiest way to see what's going on is to look at the html inspector, at a loop-tpl-generated section of nodes

qqq05:01:17

one more thing; just so we're on the same page in the example we're talkng about, the "history" field is ONE CELL, with a HUGE LIST (say 2000 items) right? i.e. is't NOT 2000 individual cells

alandipert05:01:21

basically, depending on how many children need to be appended... if the number of children doesn't exceed the previous maximum number of children that have been added, nodes that were already created are inserted back into the dom

alandipert05:01:06

that's correct re: history. it's a cell that contains a vector of 1k things

alandipert05:01:50

this might be useful btw. it is a cocneptual overview i have been working on the past couple weeks on and off

qqq05:01:56

suppose my old list was [1 2 3 4 5], and my new list is [0 1 2 3 4 5] // in this case, does hoplon llook at this and go "hmm, we can be smart and insert a 0 at the front", or does it try to do update 1 -> 0, update 2 -> 1, update 3-> 2, update 4 - > 3, update 5 -> 4, insert 5 ?

alandipert05:01:47

so for the first list, 5 cells have been created, and each one is pointed at an index in that vector in the cell

alandipert05:01:14

when the value changes they all look at the index they were assigned, and if it's different, they update the dom node they own

thedavidmeister05:01:41

yeah, so it's two different qs

thedavidmeister05:01:48

in the first case, just the new thing would be added

alandipert05:01:48

the idea here vs. the 'dirty check' react approach is that updates are "precise"

qqq05:01:03

assume: history = list of N elements then (1) insert 1 item at end = O(1) dom calls (2) insert 1 item at front = O(N) dom calls ^^ are both assumptionsa bove corect?

thedavidmeister05:01:07

in the second, you'll update what's different

alandipert05:01:04

there are 3 diff kinds of dom calls involved - creating a node, inserting a node, and updating an attribute or text content

qqq05:01:26

okay, give me 1 sec, let me rephrase to be more precise

alandipert05:01:28

i think (2) is correct in that there are O(n) updates

alandipert05:01:38

but no new nodes are created

thedavidmeister05:01:03

(1) is correct too, it would create a new node

thedavidmeister05:01:18

and not update everything else

qqq05:01:47

` ========== history = [1, 2, 3, .... N] question 1: if we go from [1, 2, 3, .., N] to [1, 2, 3, ..., N+1], then we have: O(1) create new cells, O(1) insert new nodes, no updates question 2: if we go from [1, 2 ,3, ..., N] to [0, 1, 2, 3, .., N}, then we have: O(n) updates // becasue everything has to change O(1) create new, O(1) insert // for the final one

qqq05:01:06

is the above correct? (I want to make sure I understand hoplon terminology / internals)

thedavidmeister05:01:12

because the ith item gets wrapped in it's own cell= with an nth pointing to the original list

thedavidmeister05:01:59

yeah i think that's right

alandipert05:01:11

an excellent summary

qqq05:01:14

cool thanks, this clarifies up a lot ! 🙂

alandipert05:01:43

the code is not big tho, def. worth the read if this interests you

alandipert05:01:50

loop-tpl is thin veneer over a few functions

qqq05:01:56

yeah; I'm going to have to delve into this

thedavidmeister05:01:01

it's about 30 LOC

alandipert05:01:12

it's interesting to think about alternative strategies, to efficiently handle prepend

alandipert05:01:26

a reverse-allocating loop-tpl

qqq05:01:37

the main thing you guys pull off vs react is that (1) you diff on the data, before ocnverting to DOM; then you converr hte "deltaon data" to DOM ops; whereas (2) in react, they take the data, render to vdom, then diff on the vdom

qqq05:01:52

one of the main things

thedavidmeister05:01:00

ith-item #(cell= (safe-nth items-seq %))

thedavidmeister05:01:10

e (or (shift! on-deck) (tpl (ith-item i)))

alandipert05:01:10

yeah that's the key thing i think

thedavidmeister05:01:14

answers your q's

thedavidmeister05:01:55

but totally, all the diffing happens in the data

thedavidmeister05:01:03

there's no diffing the dom ever

thedavidmeister05:01:50

so, like anything else, you can diff that data efficiently or inefficiently... it's just an approach 🙂

alandipert05:01:56

i think you've identified an interesting case there tho, where a vdom with the right heuristic could do a better job by prepending

alandipert05:01:13

but the vdom heuristics are also where they get extremely complex

qqq05:01:48

1) I think the same heuristics can be applied to the data rather than the vdom 2) I thikn this is O(n) time to do the 'diff' anyway

alandipert05:01:56

if i had to do it i would probably reverse the list elements in css lol

qqq05:01:00

oh wait wait; let me give something back

thedavidmeister05:01:02

well you could do something in hoplon too, but you'd need to do something like an "identity function"

qqq05:01:19

edit distance algorithms

qqq05:01:27

given two vectors, you want to the "minimum edit distance" to change one to the other

qqq05:01:33

given you can only do update, insert, delete

qqq05:01:43

this will handle the insert at front, insert in the middle, etc ...

thedavidmeister05:01:43

oooh, looks super neat

qqq05:01:02

this is ezxtremlely well studied due to bioinformatics people trying to "align" gene sequences

qqq05:01:15

to find the "minimum mutations required" to go from this to that

thedavidmeister05:01:33

yeah i've seen it for strings in spell checkers 😛

alandipert05:01:35

doesn't seem like a clear win, since calculating the edit must be at worst o(n)

qqq05:01:45

yes, but it saves # of updates

qqq05:01:07

it does not save anything on running time, it only saves on # dom updates

qqq05:01:34

sorry, i should rephrase as: (1) it does not save any running time on "diffing data" (2) but it might make thigns faster by reducing # of dom updates

thedavidmeister05:01:37

mmm, it's a conceptual shift

thedavidmeister05:01:50

moving from "this cell = this dom thingy"

alandipert05:01:51

so one extra thing is

alandipert05:01:00

even when nodes are removed from the dom

alandipert05:01:19

they are still updated. ie the cell responsible for updating them is still in action, and attached to cell graph

thedavidmeister05:01:20

to "cells happen and then something maps them to a bunch of dom edits"

alandipert05:01:45

ok i gotta go for now

qqq05:01:51

thanks for all oru time

alandipert05:01:54

super interesting discussion tho! thanks guys, goodnight

qqq05:01:56

if you don't mind, if I implement edit distance (on its own)

qqq05:01:58

can I send it to you?

alandipert05:01:14

i would enjoy seeing it

qqq05:01:19

thanks again for you rtime

alandipert05:01:24

likewise, ciao

qqq12:01:16

@alandipert @thedavidmeister : as promised, here is the code if the input is of of length $N$, and se search for edit distances up to $M$, this takes O(N + M^2) time the space usage is O(N * M), but can be trivaillyr educed to O(M^2); in practice, we expect N to be large, like > 100 and $M$ to be tiny, like < 10

(ns scratch)


(defn extend-path [[n lst] e]
  ;; (println "extend-path: ")
  ;; (println "n: " n )
  ;; (println "lst: " lst )
  ;; (println "e: " e )
  (if (nil? e)
    [n lst]
    [(+ n 1) (cons e lst)]))

(defn best-of [paths]
  (if (empty? paths)
    nil
    (first (sort-by first paths))))

(def empty-path
  [0 '()])

(defn select-opts [[cur-best i1 i2 ] & lst]
  ;; (println "\n\n\n\n\n")
  ;; (println "searching for best for: " [i1 i2])
  ;; (println "  using paths: " lst)
  (let [ans (best-of
             (filter #(not (nil? %))
                     (for [[[d1 d2] node] lst]
                       (let [old-path (get cur-best [(+ i1 d1) (+ i2 d2)])]
                         ;; (println "    old-path: " [(+ i1 d1) (+ i2 d2) old-path])
                         (if old-path
                           (extend-path old-path node))))))]
    ans))

(defn update-one-step [cur-best i1 i2 x1 x2]
  (let [best-opt (cond
                   (and (nil? x1) (nil? x2)) empty-path
                   (nil? x1) (select-opts [cur-best i1 i2]
                                          [[0 1] [:insert-before i1 x2]])
                   (nil? x2) (select-opts [cur-best i1 i2]
                                          [[1 0] [:delete i1]])
                   (= x1 x2) (select-opts [cur-best i1 i2]
                                          [[1 1] nil])
                   :else     (select-opts [cur-best i1 i2]
                                          [[1 1] [:update-value i1 x2]]
                                          [[0 1] [:insert-before i1 x2]]
                                          [[1 0] [:delete i1]]))]
    (if best-opt
      (assoc! cur-best [i1 i2] best-opt)
      cur-best)))

(defn align [v1 v2 m]
  (doseq [x v1] (assert (not (nil? x))))
  (doseq [x v2] (assert (not (nil? x))))
  (let [len1 (count v1)
        len2 (count v2)]
    (if (> (Math/abs (- len1 len2)) m)
      "unable to align within m steps"
      (persistent! (reduce (fn [cur-best [i1 i2]]
                             (update-one-step cur-best i1 i2 (nth v1 i1 nil) (nth v2 i2 nil)))
                           (transient {})
                           (for [i1 (reverse (range (+ len1 1)))
                                 i2 (reverse (range (- i1 m) (+ i1 m 1) 1))]
                             [i1 i2]))))))

;; if edit distance < m, we are guaranteedd to find opt
;; sometimes when opt edit distance > m, we can still find it
;; takes O(n + m^2) time, where
;;      n =  number of elements
;;      m = max edit distance to consider



(println (get 
          (align [0 1 2 3 4 5 6 7 8 9 10] [:batman 0 1 3 4 5 6 :foo 9 :bar] 4)
          [0 0]))

;;  [5 ([:insert-before 0 :batman] [:delete 2] [:update-value 7 :foo] [:delete 8] [:update-value 10 :bar])]



(println (get 
          (align [0 1 2 3] [:batman 0 1 :turing 3] 2)
          [0 0]))
;; [2 ([:insert-before 0 :batman] [:update-value 2 :turing])]

gryffindorstudent13:01:35

Nice discussion! Clarified my understanding of the hoplon model for sure 😄

zilvinasu14:01:10

I was searching for some nice ways to do routing in hoplon application, ended up finding Secretary project, but it seems to be abandoned, any other alternatives ?

dm314:01:43

with Hoplon people often just use a Cell though, e.g https://github.com/hoplon/ui/blob/master/lib/src/hoplon/ui.cljs#L496 look at how the route cell works

zilvinasu14:01:47

cell doesn’t seem to be enough for the particular case, will check the juxt/bidi link though, thanks! 🙂

qqq15:01:34

I'm a big fan of bidi as well. It's very nicely done.

flyboarder19:01:48

@zilvinasu: check hoplon/brew there is already a bidi adapter

flyboarder19:01:05

You will have to build locally tho