This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-12-30
Channels
- # adventofcode (11)
- # beginners (155)
- # boot (627)
- # cider (64)
- # cljs-dev (110)
- # cljsrn (36)
- # clojure (290)
- # clojure-austin (21)
- # clojure-russia (2)
- # clojure-spec (2)
- # clojure-uk (21)
- # clojurescript (81)
- # code-reviews (2)
- # core-async (33)
- # cursive (6)
- # datomic (9)
- # emacs (1)
- # hoplon (472)
- # instaparse (1)
- # lein-figwheel (4)
- # luminus (9)
- # om (2)
- # protorepl (10)
- # re-frame (10)
- # reagent (48)
- # schema (2)
- # sql (5)
- # untangled (17)
- # vim (1)
- # yada (108)
I've tried dozens of ways now to implement the idea of an edit cell that dynamically points as a cursor into different parts of a data structure. Some showed promise, but none have worked out after more thorough testing. I haven't been able to crack this yet.
I find the idea of storing a lens in a cell to be the most promising, but the way that cells are unwrapped before passing them to the formula has made it really difficult to express the notion of derefing the outer cell while leaving the lens stored inside intact. Furthermore, it's hard to make sure everything gets updated when it is supposed to in that case.
As for the idea of destructively mutating some cell into the lens-cell of choice, I haven't gotten set-lens! working well enough yet.
https://github.com/hoplon/javelin/pull/31/files#diff-a9991ccbfe69146a235cb715aa52d6feR62
When the test cell in a when-tpl changes (for example from one truthy value to another), does the body definitely get re-evaluated, or only if the test changes from a truthy value to a falsey value or vice versa?
@puzzler I don’t know, but gosh I would hope it re-evaluates. Easy to test, though: (when-tpl whatever (println “evaluating”) some-value)
pretty sure nothing happens if the value remains truthy, per https://github.com/hoplon/hoplon/blob/master/src/hoplon/core.clj#L174
since the action is formula, not watch based
Just tested this code:
(let [c (cell "hi")]
[(when-tpl c
(span c))
(button :click #(reset! c nil) "nil")
(button :click #(swap! c str "x") “x”)])
So, I’m not sure what’s happening, but it’s definitely not evaluating the body, even when the body does change from true to false. 🙂
i think i see, and yeah that's weird. the cell is in there, changing
makes sense tho
it's what cells do
when the predicate is false the element is removed from the vector and kept in the pool outside of the dom
but when the element has cell-backed attr or children, those continue to change, whether or not it's attached to dom, right?
ie it's not like cells in their are disconnected
so the element can be restored to the dom later without needing to "catch up" its state
the element returned by the body of the when-tpl continues to live even when the predicate is false
when-tpl is useful to lazily allocate dom resouces, because the body doesn't get evaluated until the predicate becomes true
Is there already a macro that causes a body to be re-evaluated whenever a given cell changes? I know how to write one in terms of formula
, I just want to know if it already exists.
(defc c 100)
(when-tpl (cell= (odd? c))
(do
(.log js/console "body evaluating")
(div "i am here!")))
(with-interval 1000 (swap! c inc))
Well, a formula cell walks the expression to figure out the sources. I want to specify the source.
I will shamelessly mention formula-of
again. https://github.com/candera/weathergen/blob/master/src/weathergen/cljs/macros.clj#L12-L36
:thumbsup: I love how I return the love of you creating Hoplon by giving you work to do.
feel free to suggest changes or whatever, it'll be marked alpha or something for a little while
re: doing something every time a cell changes value, there's always add-watch
if you want to skip the rails a bit
I have used it a lot, and it’s pretty stable in my code, but I’m a relative Hoplon/Javelin n00b.
@candera our community is small, you're probably top 20 world wide hoplon expert 😄
I assume the distinction is that watch is what you'd use if you wanted to pick up on something being set to the same value twice.
for awhile i did it just to communicate that the action was side effects
but formula/cell= are shorter to type so i usually just do those now
Here’s the one I fly: https://www.youtube.com/watch?v=QcMOOOulhME
An observation about formula-of
is that it doesn't support the lens arity with an update callback, because it allows for multiple bodies.
@micha @candera i’m building up elems as i go in a gallery and the amount of global stuff like logging and metrics i need is not huge
just read ^^
@puzzler I feel like something along the lines of (with-setter (fn …) (formula-of …)))
would keep those pieces nicely orthogonal without creating some horrible syntax nightmare.
@micha I fly with a bunch of guys who are actual current or former military pilots. Bunch of civilian pilots as well.
i wonder if formula-of should assert the things are cells?
since the point of it is to be explicit about the cells you're using
I guess it’s not terrible to have (formula-of [foo bar :set (fn …)] …)
. That matches let/for/doseq syntax, and I believe it’s unambiguous.
oh, i guess i miss the point of it
if you restrict it to cells then you end up not able to use it in a place where you are passed in the things you are depending on
yeah, so it sohuld probably retain the formula semantic and do the deref* thing i suppose
be cool with non-cells
Yea, IMO the point is to be minimal sugar over formula
. I don’t think it should have much in the way of its own semantics outside of that.
makes sense
yeah that's what i was thinking about the assert, it would happen once
seems like a useful angle... for something
oh dude yes
also doing the deref* every time
i guess deref* hardly does anything tho
sources, sinks, etc., except for the watches map, which needs to be able to have keys that aren't strings
I want to make sure I'm not missing something here. I pasted in the formula-of macro, and have something like this:
(when-tpl my-formula-cell
(formula-of [my-cell]
(div "some stuff I want to be visible when my-formula-cell is truthy and update when my-cell changes")))
but I'm getting this error:
Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
at core.cljs:94
at hoplon$core$merge_kids (core.cljs:93)
at G__9443__2 (core.cljs:4087)
at G__9443 (core.cljs:4089)
at core.cljs:48
at cljs.core.Atom.cljs$core$IWatchable$_notify_watches$arity$3 (core.cljs:4213)
at Object.cljs$core$_notify_watches [as _notify_watches] (core.cljs:673)
at Object.cljs$core$reset_BANG_ [as reset_BANG_] (core.cljs:4254)
at Function.cljs.core.swap_BANG_.cljs$core$IFn$_invoke$arity$4 (core.cljs:4273)
at cljs$core$swap_BANG_ (core.cljs:4258)
Does that mean anything to anyone?I thought it might be something like that, so I tried wrapping the second formula in a div, but I got the same error.
(when-tpl my-formula-cell
(div
(formula-of [my-cell]
(div "some stuff I want to be visible when my-formula-cell is truthy and update when my-cell changes"))))
@micha how are you doing the javelin perf tests, just time boot test-javelin
?
https://github.com/micha/javelin/blob/027a0fe27eabbb39d653844fe5656f4673fae3c6/build.boot#L39-L54
reduce comp dude
:dove_of_peace:
was there some bug with the new perf version?
it would be good to have something that measures like "cells updated per second" or something
@puzzler I think what you’re describing is exactly what I posted above, except i used the same cell for both.
I.e. this:
(let [c1 (cell "hi")
c2 (cell true)]
[(when-tpl c2
(.log js/console "Evaluated")
(span c1))
(button :click #(swap! c2 not) "toggle")
(button :click #(swap! c1 str "x") “x”)])
So, for some reason, the formula-of
macro is triggering errors for me in situations where the manual rewrite to formula
is not. I don't have a good explanation for why right now. The macro looks right to me, but I just wanted to mention that I'm getting strange results with it, since at some point you plan to put this in core.
For example, I mentioned above that wrapping div around it didn't help, but it did once I rewrote the formula-of
macro manually.
that's surprising
yes, I don't have a good explanation. Just throwing it out there as a data point for now.
(when-tpl my-formula-cell
(div
((formula
(fn [_]
(div "some stuff I want to be visible when my-formula-cell is truthy and update when my-cell changes")))
my-cell)))
^^ is similar to what you tried that worked?
These should be equivalent right?:
(formula-of [editing] (and editing (:type @edit-path)))
((formula (fn [editing] (and editing (:type @edit-path)))) editing)
i wonder if it's the capturing
i guess in both cases, the cell is editing
and the parameter is editing
in the past cljs has had bugs around situations like that
but if that theory was correct both wouldn't work
i like but think formula=
would be more consistent-ish?
so long though
i like it now
well, kinda
it's not like it support multiple arity
so i'm not sure how much like a function it actually is
So, most likely explanation is something subtle with the way the macro is capturing the global name, I guess?
yeah, my theory is
cljsc isn't resolving the outer editing
in a namespace, and so compiles it into something like window['editing']
yeah :thumbsup:
actually scratch that, if the name wasn't defined, js would complain differently
it reminds me of the original idea, the revers op
(lift a b c +)
i read a kind of related thing in the graham lisp book
he talks about why CL let
is parallel
the justification was apparently, let is lambda, and it's not like parameter know about eachother
to achieve sequential let you need to nest lambdas
iterative shadowing
as crazy as set! imo
because of things like this i find myself more attracetd to the idea
of functions just having one argument
i think the only reason lisp doesn't work that way is syntactic convenience
but it would be simpler if it did
can't do (x (inc x))
because then x
would be shadowed if it were in enclosing scope, so the best you can do seems like ((x) (inc x))
which is a thing graham proposes
sorry this is for (fn [x] (inc x))
ramblings about simpler ways of conveying it
i don't
1958 lisp worked that way too tho, i do know
err, it had symbols
@dm3 @thedavidmeister I did integrate devcards to be able to use it with Hoplon on github-client but I don't see the advantage in relation to just display the component. Is there one?
@mynomoto does it do the forwards/backwards state thing?
So devcards allow you to display the components in a page so you can have visual feedback. What I'm not seeing is the advantage in relation to just show the component.
devcards has a few features (that i’m not sure if are compatible with hoplon)
- state history - replay, forwards, backwards, etc.
@thedavidmeister do you have a link for that?
- dynamic updating of the bit that shows you the values passed in
- integration with cljs tests
the bmi calculator gives you the gist of what i’m talking about
- markdown integration
@dm3 i know that in theory cells should support something like this just fine, i mean, i’m not sure what needs to be done to make devcards “hoplon aware"
But I have the state history on the app itself so not sure if this is a big advantage...
@mynomoto because working directly on the app is more work than working on a single element
https://www.youtube.com/watch?v=G7Z_g2fnEDg @mynomoto from the devcards guy
@thedavidmeister yeah, but I don't think it's hard to plug that on the component level.
well then it should be trivial to spin up an awesome devcards clone 🙂
the advantage is having hundreds of these examples ready to go, and them being standardised/configuration driven
just to minimise friction and maximise the chance of it being used
I guess if we have some sort of component spec thing, we'd be able to pick that up from devcards to allow playing with the inputs dynamically
i don’t really understand how this “component wiring” is different to what i’m already doing 😕
could you give me more info @dm3 ?
as in, what’s the difference between that and passing in cells to defelem as attributes?
but then you want to allow devcards automatically discover what the possible inputs and their types
oh, so like a dependency injection thing?
or just like, listing possible options in docs?
no, like
(require '[clojure.spec :as s])
(s/def ::size int?)
(s/def ::width (cell-of ::size))
(s/def ::height (cell-of ::size))
(s/def ::thing-opts
(s/keys :req-un [::width ::height]))
(s/fdef thing :args (s/cat :opts ::thing-opts, :elems :hoplon/elems) :ret :hoplon/elem)
(defelem thing [{:keys [width height]} kids] ...)
Something like this, just hidden below some sugaroh, with sliders like unity?
clojure.spec is nice because it's a standard solution which might have more tooling built by other people in the future
ok, so a better version of what i do with assert
on cells atm
pre
and post
don’t work on defelem so...
a good thing is spec doesn't fail on additional stuff in the map, so in the above example if you have more stuff in the {:width, :height}
opts map it will still pass
i would very much like a standardised way to set what should be in the cells
especially as the cells are dynamic
they might be fine now but not later
would you put the spec on the cell too?
or just the elem
so i could have an “int cell"
I worry that spec don't play nice with datascript. No way to spec a db like a big map. You can always get entities but only those would be spec'ed
@mynomoto i think that would be what having a schema would do
that’s how datomic does it
but datascript is missing that
@dm3 I think that would work but you could have wrong data on db, which you could make impossible with the map in a cell approach.
@mynomoto that’s a problem right now with datascript though
don’t think hoplon can solve it
also, entities don’t play nice will cells at all, in my experience
@thedavidmeister you need some tricks. Check the javelin.datascript ns on github-client.
The problem is that entities always compare equal so you cannot put it on a cell because it will never trigger an update.
exactly
mmm ok
what about getting the conn from the entity?
Well I don't do that, I always get that from the context which is passed to every part of the app.
I guess what I'd want to do is to (defentitycell= conn eid)
=> (defc= (let [e (d/entity (d/db conn) eid)] [e (:version e)])
If you are doing on every tx what is the advantage in relation to use the db itself as key?
@dm3 @mynomoto i’m thinking of this http://docs.datomic.com/clojure/#datomic.api/entity-db
as a way to not have to pass conns and entities together
it does support it
80% sure i started using it but had to bail because of javelin
i can’t do (cell= (q […] (entity-db e)))
, etc.
if i could, i’d almost never pass conns around, i’d use entities instead
usually my UI makes more sense in context of entities than entire databases
at least, at the elem level
because e
doesn’t change
that will never recalculate
so there are 2 solutions: the one above with entity versions or making an JavelinCellEntity
which always wraps Datascript Entity
i don’t really understand how entity versions help
which is really one solution -> make a wrapper over datascript API where the entities placed into cells are always something that doesn't compare on just eid
for equality
because the eid
would still be the same, no?
even if you added a version attribute
so the only solution is a wrapper then?
is there a way to pass to a cell a different function to use for value propagation?
like, could i tell the cell to use touch
?
yeah i c
so you'd have a special cell type. I see that as a more complicated approach as we're playing with Javelin's semantics
hmmm, i think i need to think about this more when i have the bandwidth and am actually working on something that could benefit from it
If you have a source of data that’s not a cell, but you want to react to it, you’re going to have to do the work to eventually change a cell. Seems to me what you need is the adapter layer that watches not-a-cell and eventually calls reset!
. Given that you need that, I’m not sure I see the advantage to considering approaches that mess with the semantics of cells.
Of course, making that efficient is a whole other story, but that’s a problem that would need to be solved regardless.
I suspect that something like core.logic could be helpful there, as you essentially need to run the query in reverse.
this seems to have relevant discussion: https://github.com/tonsky/datascript/pull/12
There is posh https://github.com/mpdairy/posh
Yes, seems relevant. Including the part where they question whether Datascript is the right tool. 🙂
Well it helps with normalization, but it depends on your data as everything usually does. Tradeoffs.
The good news is that if you’re putting the results of your queries into cells, although you need to run the queries every time, you won’t redo any work beyond that.
But generally if you’re using touch
other than at the REPL you’re doing something wrong.
I would say pull [*]
is a similar smell, unless what you’re writing is some sort of super general grid that doesn’t know anything about the data.
yeah, I guess that rule is to just get whichever part of the entity you care about at that point in the formula cell
Yeah, for some of my entities d/touch
is fine because they have only a couple of fields that are shown on the same context but that could derail quickly.
is there a name for the "false pairs" in idiomatic clojure syntax, like the binding pairs in let
?
boot.user=> (doc for)
-------------------------
clojure.core/for
([seq-exprs body-expr])
Macro
List comprehension. Takes a vector of one or more
binding-form/collection-expr pairs, each followed by zero or more
modifiers, and yields a lazy sequence of evaluations of expr.
@micha re what you wrote yesterday regarding components/applications - did you think of a way to specify elem "schema": their input cells, etc, so that they could be inspected/wired dynamically? Would you consider clojure.spec
for this case?
which would maybe avoid production performance issues related to higher level reflection like what we'd need to do
Dynamic binding is working wonders for me in this area also