Fork me on GitHub
#hoplon
<
2016-02-21
>
micha17:02:36

@levitanong: when you are building trees like that it gets tricky

micha17:02:53

especially managing allocation of resources

micha17:02:24

when i need to do that i have been switching to a function instead of defelem

micha17:02:54

like we made this orgmode app that had a dynamic tree view

micha17:02:08

that was just a function that used loop-tpl internally

micha17:02:20

like each node managed its children via loop-tpl

micha17:02:33

it's a little weird to think about at first

micha17:02:41

but once you see it it's very simple

micha17:02:03

see how on line 23 it's recursively creating node children in the loop-tpl

micha17:02:27

this approach retains all the benefits of loop-tpl's dom pooling etc

levitanong17:02:53

@micha: ah, my structure used to use loop-tpl to list out all the children of a node, but because datomic doesn't handle ordering, I decided to switch the structure to a linked list. Each node has a "next" and a "child". Because I wanted to minimize the amount of changes to the structure of the tree, I decided to look into keeping it this way and figuring out how to render it. (As opposed to, say, applying a function to traverse the linked list and convert it into a vector)

micha17:02:16

right yeah

micha17:02:21

i think the example does that too

micha17:02:30

it's traversing a zipper structure

micha17:02:41

which is built from datomic data in the client

micha17:02:12

so the client gets the stream of datomic datoms and builds a tree with a zipper on top

micha17:02:22

the node function generates the dom from that

alandipert17:02:52

micha did all the ingenious looptpl/zipper stuff, i am responsible for the miserable and broken datomic part

levitanong17:02:44

@micha: looking at the snippet. Hmm. Didn't know you could just use a defn. When would a defelem be preferred over defn? @alandipert whoa that's a lot of code. I've never used zippers before, so it'll take time for me to parse all this. :p it's 2 am, so I'll get back to you guys tomorrow. :D

alandipert17:02:04

a defelem turns into a defn + argument parsing

alandipert17:02:16

this would have worked also (defelem node [{:keys [z]} _] ... (node :z kid))

micha17:02:20

yeah i don't remember now why it was a defn

micha17:02:50

probably no reason

alandipert17:02:02

i can imagine factoring it into a defelem that defers to a private node fn for internal use

micha17:02:32

that's interesting yeah

levitanong17:02:45

Hmm. @micha mentioned that the structure was similar in that each node kept track of the 'next' node. Does zip/next add a sibling?

micha17:02:46

maybe some kind of trampolining if the tree is deep

micha17:02:05

yeah the zipper keeps track of the structure of the tree

micha17:02:11

so you can navigate around

micha17:02:17

like the dom basically

levitanong17:02:21

Fascinating. :o

micha17:02:22

but immutable data etc

levitanong18:02:20

I was trying to implement an if-tpl before you guys replied. :p I probably won't need to use it for rendering the structure if I figure out how to use zipper, but it's still something I foresee using for general control of element display. I feel like this is more semantically correct than hiding elements via css, so I'll continue building it out.

levitanong18:02:31

It's an uphill struggle though, as I'm very new to macros.

micha18:02:45

if-tpl is an interesting thing, yeah

micha18:02:12

however i think hiding things with css eliminates a lot of corner cases you run into when you add or remove things from the dom

micha18:02:43

it also might introduce some corner cases lol

micha18:02:51

so having both is good

micha18:02:49

i think you could implement if-tpl without macros perhaps

levitanong18:02:55

Haha, I foresee it interfering with certain css selectors like :first-of-type, etc...

levitanong18:02:04

And sibling selectors

micha18:02:11

yeah that could be

micha18:02:47

although i've never run into it because when you have those kinds of selectors it's always in a list of things that are not statically known

micha18:02:52

so it's in a loop-tpl

levitanong18:02:57

Haha well, I'm really just taking apart the loop-tpl code and desecrating it. :p

micha18:02:02

which maintains the sibling integrity correctly

levitanong18:02:29

And the loop-tpl code uses macros, so...

micha18:02:34

haha yeah

micha18:02:47

but the macro is just syntax sugar

micha18:02:57

you can use loop-tpl* to do it

levitanong18:02:05

^ yeah that was a realization that was dawning on me

micha18:02:05

you just would need to wrap some things in functions

micha18:02:32

none of the macros in hoplon or javlin really implement any functionality

levitanong18:02:35

Enlightenment!

levitanong18:02:43

That's a relief!

micha18:02:48

this is key because you can't use macros from js

micha18:02:01

you can only use them in cljs that will run through the cljs compiler

micha18:02:19

so like we use javelin in our legacy c# app

micha18:02:30

we just don't use the macros, we use the underlying functions

levitanong18:02:41

It would make sense to just implement it without macros because it would be more intuitive for the user to use if-tpl like the stock if

micha18:02:04

alan compiled all the cljs into js, so we just load the js file in our app and then we can use the functions from js

micha18:02:32

yeah i mean when you're thinking about making a macro you should first implement allthe functionality with functions

micha18:02:44

then the macro is just to fill in the boilerplate code

micha18:02:55

to emit the more verbose function level code

micha18:02:16

the only place where you really need macros is like the clojure if expression

micha18:02:36

where you have two branches, the consequent and the alternative, like for if the predicate is true or false

micha18:02:43

but you only want to evaluate one of them

levitanong18:02:57

And the "and" macro

micha18:02:06

you can't do that with a function that takes 3 arguments because functions always evaluate all of their arguments first

levitanong18:02:07

Where you short the execution

micha18:02:19

but loop-tpl isn't one of those cases

micha18:02:25

and your thing isn't either, i don't think

levitanong18:02:35

I feel like this is the kind of thing I'd be taught in a computer science class

micha18:02:38

like you do want to create both alternatives, just only show one at a time

micha18:02:06

or amybe your thing is like if, because then it would delay evaluation of the other branch until the predicate becomes rtue

micha18:02:12

this could bea very useful thing

levitanong18:02:21

That... Is a great idea

levitanong18:02:46

I suppose I should start simple though. :p

micha18:02:03

yeah start with functions, then make a macro to spit out the boilerplate

levitanong18:02:09

Thanks for the lesson, @micha! Super grateful. :D

micha18:02:09

then you can refactor

micha18:02:17

sure anytime simple_smile

levitanong18:02:15

I feel like I've learned more CS in my time working with hoplon and clojure than all my years working with JavaScript. @.@

micha18:02:45

one year of clojure taught me more about computing than 10 years of everything else

levitanong18:02:21

So this is my implementation for if-tpl so far:

(defn if-tpl* [truth true-tpl false-tpl]
  (with-let [current (with-meta (cell nil) {:preserve-event-handlers true})]
    (do-watch truth
              (fn [old-truth new-truth]
                (reset! current
                        (if new-truth
                          true-tpl
                          false-tpl))))))
And it works in the following case:
(let [truth (cell false)]
    (div
     (button :type "button"
             :click #(swap! truth not)
             "Swap it")
     (if-tpl* truth
              (div "true")
              (div "false"))))
Yay! But it still causes a stack overflow when recursion is involved:
(defn list-item [sublist]
  (if-tpl* (cell= (not-empty sublist))
           (let [item (cell= (first sublist))]
             (div (text "~{item}")
                  (list-item (cell= (rest sublist)))))
           (div "end")))

(let [herp (cell= (range 10))]
    (div
     (list-item herp)))
Any idea why this is so?

micha18:02:14

recursion always has the potential to overflow the stack

levitanong18:02:12

Yes, but this is just for a range of 10. Surely the stack isn’t so shallow.

micha18:02:37

i think the stack depth is ~300 max in most browsers

micha18:02:56

you should have a stack trace

levitanong18:02:00

When I println in the list-item, I’m seeing it count properly, and then go into a lot of nils. So somehow the if-tpl ceases to work.

micha18:02:11

i suspect you're making circular dependencies in the cell graph

micha18:02:40

but the stack trace should show you some critical debugging info

levitanong19:02:31

@micha: Haha, I see what the problem is. Turns out, it’s because I’m evaluating the recursive call before it’s being passed to if-tpl. Funnily enough, this was the very thing we were talking about a while ago.

micha19:02:07

classic use of macros: short circuit to avoid infinite loops

micha19:02:40

some languages have lazy evaluation which achieves the same kind of thing

levitanong19:02:56

And just like that, it works! All I had to do was bring the macro stuff back in! HAHA

micha19:02:45

haha the parrot never gets old

micha19:02:56

lol every time

micha19:02:11

he's syncing up perfectly with the music i'm playing

levitanong20:02:02

@micha: If I were to make a pull request, where should I set the base branch? master? experimental?

micha20:02:01

master i think

micha20:02:09

experimental has some craziness in it i think

micha20:02:42

awesome! +1 PR

micha20:02:11

i will try to merge and handle all outstanding hoplon things tonight

levitanong20:02:04

😄 😄 😄

micha20:02:48

no thank you!

levitanong20:02:24

This is the best emoji in the world