This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
@mynomoto: i dont have a strong opinion on IRC. i never liked it because of the hassle with the history and bouncers and logger bots, but one undebatable upside is that the protocol is free and the freenode network is independent from political or commercial influence... (at least thats how i imagine it)
@onetom: I think it's more of a practical matter than a political one. There are more people and conversations here than there and having one "official" place is better than be scattered over many.
I don't want people to leave irc, only not sending new people there if there is more talk going on here.
btw, i actually have gitter running too; primarily to follow the https://github.com/red/red project...
Maybe we should just say something along those lines, like: Most of the community is on [Slack], so that's the most active place for discussions and the primary place to get Hoplon support. There are alternative/fallback channels too: [Gitter] & [IRC].
hi all - is this or Discourse the right place to ask questions?
I have a bunch of newbie/clarity questions which I posted to http://hoplon.discoursehosting.net/t/newbie-clarity-questions-around-cells/552 - in general is it sufficient to post to discourse?
Hi. I was recently trying to test some castra calls using ring mock. It took me a little time to get it working, and this is because castra frontend pass some castra specific header like transport method and csrf. I am now wondering if there should be support for mock castra requests for testing on backend ? So that it is not necessery e.g. for newbie to look into castra code to be able to construct request. Or maybe it should be sufficient to only do clojurescript tests ?
@micha - the man himself
I am still googling around different use-cases - I posted a few newbie Qs at discourse which would be great if you could take a look at (you know, your time being mine to spend :-)).
but yeah, I think javelin is exactly the right model for how I view the model creation (as a sequence of data transformations)
thanks .
that would be great
the propagator is a function that given a cell where the change originates, computes the dependency graph from the sources and sinks of each cell, walks the graph doing a sort of topological sorting of the cells in the graph
right
the change is computed by calling a thunk with the sources as arguments, dereferencing any sources that are cells
if the result of recomputing the thunk is the same as the existing value in the formula, then propagation is skipped for all of the cells that depend on this cell
when you make a new cell it will have a rank that is higher than any previously created cell's rank
and when it's time to propagate we walk the sinks and build a priority queue with the rank as the priority
perfect
(and great explanation!)
if the formula starts to get complex it's usually more productive to move the logic into a function
because usually complex formulas indicates some other absrtaction you need, which you can't really do very well in formulas
it can't know what the value of the formula will be without evaluating the thunk, right?
the value of the formula could involve non-pure functions, too, like (cell= (or (even? clicks) (rand-int clicks)))
right
got it - I see
but remember that the REPL and clojure itself have a lot of hacks built in around reference types
and cljs doesn't have real vars yet, which are the foundation of all reference types, the most primitive one (because of the relationship between vars and evaluation)
so if I change the defn that a cell= delegates to I can just use (set-formular! ..) on that cell?
can I ask a few more Qs then about cells?
thanks . So in terms of garbage collection if I have a
(defelem top-panel-page-1)
and (defelem top-panel-page-2..)
and in those I create various (cell= (transform some-top-level-cell))
inside each elem...
.. if I show page-1 then those cells and formula get inserted into the graph. If I then show page-2 (and I am (condp = page :page 1 (top-panel-page-1) :page 2 (top-level-page-2))
then I assume all those cells created in page-1 are removed as the page-1 dom tree is removed?
If I then go back to page-1 is assume those page-1 cells are recreated?
we statically allocate those wherever possible and just hide them when they're not in use
I see
without weakmaps in js i don't know if there is an automatic way to handle garbage collection
right - I don’t see what happens to cells that were created in the deallocated template rows though?
sorry, I mean the ‘inactive pool'
initially that will be called 3 times, and you'll have an unordered list with 3 things in it
(parsing)
I think so. Can I sanity check an example closer to my intention?:
(defc people {1 “Bob” 2 “Jo”})
(defc locations {1 “Home” 2 “Work”})
(defn denormalise-people [i] (cell= (get people i)))
(defn denormalise-locations [i] (cell= (get locations i)))
(defc records [{:person 1 :location 1} {:person 2: location 1}])
(defn denormalise-record [{:keys [person location] :as record]
(tr
(td (denormalise-people person))
(td (denormalise-locations location))))
(defn render-table [records]
(table
(tbody
(loop-tcl :bindings [record records]
(denormalise-record record))))
where any of the defc’s might change (and will themselves be formula cells on a stream of incoming domain events)
(again it is made up, but representative of my intentions)
right, we allow the user to chose the number (10, 20, 50, 100, etc.)
but because it is ordinally indexed, going from 100 to 10 is fine because the 90 ‘orphans’ in the pool will be nil
changing the ‘window’ will fire an event over a web-socket to the server to return more results which will then (reset! records …) in this example
for the ones that are removed from the dom when you decrease the number of items in records
, that is
right.
This is just great - thanks Micha.
I need to run unfortunately but my last assumption is that because of the mechanics of the cells, changing something like the name of a ‘person’ will result in a trivial amount of recalculations and then hoplon will simply update a ‘td’ for example
there usually is
you can then make formula cells for those which you can bind to the text content of the td
elements
why does line 13 and 14 need to wrap the result of (denormalise…) in a cell=? as the denormalise- fns return a cell=?
micha, u have just written up a great "how hoplon & javelin works" tutorial. can we just chuck it into the hoplon wiki until we have a more distilled version?
just to be very explicit, denormalise-person may actually be really expensive which is why I want to ensure it is only run once - I think we are on the same page and I am looking forward to this.
colin.yates: do u mind if i leave ur name in there too (just so i dont have to edit it too much)?
not at all - go for it
i find it tends to work better to let functions return values and build cells around them anonymously in place
in my use-case though I would want to access the result of the very expensive calculation that (denormalise-person) does everywhere … and make sure that two calls to denormalise-person for the same person get cached…
I don’t think I understood what you are proposing?
or are you saying that denormalise-person can be very expensive and consume a cell, but where-ever I call it I should wrap the call in a (cell= (denormalise-person 1))
relying on multiple (cell= (denormalise-person 1))
s?
ah OK - phew
I really have to run - thanks a bunch micha - I will be back later and may ping you then if you are on-line. My manic-job-of-the-afternoon is to re-write my current re-frame app which uses immutant and web-sockets and prototype with hoplon, so many questions will undoubtedly arise . May as well throw boot in instead of lein for fun.
micha: i was wondering about loop-tpl today... is there a reason why is it expecting just 1 body expression: https://github.com/hoplon/hoplon/blob/master/src/hoplon/core.clj#L158
similarly im not sure why would the bindings should be constrained to just 1 pair of destructuring form and sequence?
in my experience to my colleagues it was more intuitive to expect the ability to throw in multiple dom elements into the body and have them repeated...
if we wanted some debugging, we just wrap the (dom-elem ...)
as (let [_ (print ...)] (dom-elem ...))
which leads back to the multiple binding question... if it would be possible to have multiple bindings then side-effects could be just thrown in there
u have a substantial code base, so maybe u can do a quick grep to see how often r u doing side effecting from withing a loop-tpl
i was trying to create a generic menu and sub-menu builder and it became a monster quite quickly
so as u can see i had to throw in an extra let
which could have just gone under :bindings
the aggressive indentation in the sublime lispindent plugin prompted me to think about how to be a bit less verbose and that's why i looked into the loop-tpl implementation
as u can see i even broke the :bindings
attributes into a separate line to save a bit on the indentation
so my 3rd question/idea is: do we really need this :bindings
attribute and such an eccentric name as loop-tpl
?
Since im not expecting to have such an invasive change, I was thinking how can I have such a custom (defmacro x ... loop-tpl* ...)
automatically compiled into every .hl
file?
I haven't found any extension hook in boot-hoplon
at 1st glance....
btw, i came up w the name x
because it symbolizes multiplication...
loop
is already taken
stamp
is quite short but suggest a very imperative approach
foreach
? every
? ... something-let
or let-something
?
i know the (loop-tpl :bindings ...)
was invented to blend in to the rest of the dom element constructors but it's so long an unorthodox, it's just not very practical...
now that hoplon v6 is about to come out, it think it makes sense to add such practical polish to it before it gets announced to a broader audience...
so (splice :for ...
is already nicer, though as a non-native speaker i still don't understand really what splice mean, what does it have to do with ropes and still confuse it with slice
imean my 1st exposure to splice
was the javascript function: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
and it simply makes no fckin sense even today to me...
yeah splicing means like interweaving or patching one thing as a section of another thing
the idea it expresses is that the collection is being altered by adding or removing a contiguous section from the middle somewhere
just to demonstrate my personal (or rather european's) confusion, if i translate back the meanings of splice from hungarian to english, i get the following: twist, interweave, twine, furl, cockle, kink
(insert :every [thing some-list]
(elem thing))
or alternatively inlay
instead of insert
, though insert
doesn't really collide with anything...and as i was reading your explanations even pool
crossed my mind.
of course my own 1st reaction was "it's too low-level and implementation detail revealing/bound"
but actually, if i assume i don't know that there is a pooling mechanism behind it, it still works quite well:
(pool :of [thing some-list]
(elem thing))
very concise and expressive. it doesn't suggest some kind of nesting level imho. doesnt collide with anything built in
ah, playing w letters and moving along this splicing of ropes/wires, there is the very famous loom
https://en.wikipedia.org/wiki/Loom_(video_game)
https://upload.wikimedia.org/wikipedia/en/3/3b/Loom.png
but honestly this attribute thing is really unnecessary.
it won't return just 1 dom element but a vector, so i don't see a need for making it look like dom elements...
it's actually more confusing than explanatory.
just the other week i made some element returning a loop-tpl
and i tried to call it as a function to extend it with further kids...
(defelem thingies [attrs kids]
(loop-tpl ...))
(let [default-thingies (thingies :class "blah")]
(default-thingies :click #(...)))
something like this...hmmm now the only problem is loom has nothing to do with weaving when it's a verb (as i thought in all my life
these would work for me too:
(*** [thing some-list]
(elem thing))
(... [thing some-list]
(elem thing))
we didn't do it initially and have resisted it so far because we wanted to be able to support html syntax
okay, so i wouldn't bore everyone with this naming if there would be a way to hook in some customization into the hoplon compiler, so some project-wide macros and functions could be required in automatically into every .hl
file.
is there a way to do that or it's actually better not to do that?
May I ask a Q about castra?
At the moment I use websockets which push a ‘new-state:N’ to each client, the client then immediately asks the server (via a websocket) to get the new state since last seen. The server then pushes all that new state with a :new-state N checksum.
I think I can do the same in castra by the following - have the state returned by castra always include the latest checksum and have the client do an rpc call every second and when it pushes anything to the client check the :new-state flag (does that make sense)?
micha: not sure though how to hook macros in, but i think i can figure it out on my own
effectively, the state that is synchronised between the server and the front-end is the batch of state last-seen since that client last checked.
thats how i understood the source too but it didnt work on 1st try. but no worries, im on track
@colin.yates: parsing...
how could I go about creating a new element? not using defelem but something that will print <new-elem> as html?
if I can put it more simply - how do I wire up castra to only show the client the most recent stream of events since they last connected
@flyboarder: look in hoplon.core namespace
those are private are they not?
you can use css to have it display as an inline element or table cell or whatever you want
@colin.yates: you already have an event driven backend?
doesnt sound like it fits the castra model which was made for unidirectional data-flow, no?
@colin.yates would be interesting to gather your insights in comparing re-frame+lein w/ hoplon+boot — would make a for great blog post (see that you are working on your blog site with jekyll — you should check out https://github.com/hashobject/perun 😉.
@donmullen: yeah - I have so many great things to put on there, but time 😞
2. since we're using websockets we can keep track of the last thing we sent to the client, so we can compute diffs and patch on the client
it does, but I think I am almost there already except I don’t want the fully-realised model on the server.
so i think with castra if you want to get a response that is "as of" the last time they asked
I want the equivalent of /events?from=10 which would return something like {:latest-event 51 :events [10, 11,12 ….51]}
so the client passes {:last-seen 10} to the rpc, the server constructs a view which is the latest events which castra syncs to the server - I see.
exactly, I want the backend to be stateless really
another similar use-case would be tailing a server-side log
micha: normally im quite skeptical about polling, but what u described with those 3 points add up into a pretty strong argument
+1 - it really does. It also handles lost clients very well as well
you don’t need datomic for this though. My back-end is pure DDD and event-sourcing and it is a perfect fit for this
@onetom: I agree, generally i try to avoid polling for performance reasons, and less over-the-wire
All domain transitions are captured in discrete events. To get the latest model you simply (reduce ) the events for that aggregate root
the event-store happens to be in MS SQL server but it could be anywhere.
So all I need to do is stream any events that have happened since the client last talked to the server
this isn’t a particularly high traffic site (internal LAN)
ah, unfortunately for me this is all closed-source and commercial (think medical data)
it does mean I get a pretty nicely controlled environment to play with though
I am also going slightly off-track by doing all the event denormalisation on the client side
which means travelling back in history is a trivial thing - simply discard any events since a particular timestamp.
I haven’t needed one yet - MS SQL is pretty quick and the working set tends to be small (e.g. everybody is interested in the last 10K events for example)
for searching etc. I denormalise some stuff but those are themselves projections of the event stream
DDD + CQRS + event streaming makes your brain hurt but yeah, if you can manage to get it working it is brilliant.
my current architecture is web-socket (and re-frame) based so clients don’t poll, they just get sent the events in real-time
the really nice thing is that your entire domain model can be persistence agnostic - ‘loading’ an entity is really nothing more, and can be nothing more than reducing the events for that aggregate root
the main one is the fact that subscriptions aren’t free to dereference.
it rocks, it really does, but I made an incorrect assumption about it.
So you can imagine an event stream of 20K events going into a data-transformation which trims by time going into one which hydrates each entity etc.
I had many subscriptions which did that the calculations
to be blunt, I tried to implement the spreadsheet model using subscriptions and it worked really nicely, except I noticed many things were being recalculated
re-frame is still feasible, I could simple use the app-db as a denormalised cache of sorts
I also ran into some issues, almost certainly mine where things wouldn’t always update when they should - things like that. I am absolutely not claiming they are bugs, more my lack of knowledge
the problem, as i see it, is that the client has very little memory and no access to the usual ways we alleviate memory pressure
to be honest, I could achieve what I want with a trivial end-point which I poll using cljs-ajax passing in the parameters I am interested in which in general are simply the checksum of the last seen id
yes, this is definitely not a general solution for unbounded lists etc.
yeah the cljs-ajax approach is how we do it currently, using castra just to handle all the boilerplate and whatnot
ah OK. I see.
this "handle errors at the caller, but handle state globally" model has worked very well for us
so I think I can still get this to work with castra by calling the rpc with the ‘last-seen’ sequence-id. The server then loads the (select event_blob from t_events where sequence_id>last_seen) into the cell
thanks again all.
(and yes, I really must blog/produce a template project when I get all the dots working)
if I can give you one bit of advice it is to know your domain. By far the hardest thing was getting the aggregate roots defined when doing DDD.
The notion of state changes as discrete events is orthogonal and to be honest gives me 80% of the benefit without the DDD modelling. Were I to do it again I might skip the more formal DDD aspects
(aggregate roots are part of DDD - they encapsulate a graph of data which should be atomically changed and represents a constraint boundary)
so, a Customer and their address might be an aggregate root - you wouldn’t change the address separately from the customer and the customer might enforce some constraints on the address
I tell you, if you haven’t jumped into DDD it is a whole new world whose pain and skill has little to do with technical or tooling skills
Eric Evans’ ‘Blue Book’ is the authoritive reference but this is also really good: (finds link…)
http://www.amazon.co.uk/Domain-driven-Design-Tackling-Complexity-Software/dp/0321125215 is the blue book
https://vaughnvernon.co/?page_id=168 is a book for mere humans
gotta go put the kids to bed
the ‘promise’ is that you can design your aggregate roots to be whatever they need to be to support your business process/constraints. Those aggregate roots service commands, the effect of that command is then captured as discrete events (CustomerAddedAdvert, CustomerChangedAdvert etc.). Your RO model(s) can then subscribe to those events and denormalise the heck out of things for super speedy.
No more single model which (badly) serves both the read and the write layers.
the beauty comes when you realise that the event-stream is the audit log and all the various SQL/no document databases are simply projections of those events.
Change the view - no problem, blow it away and then replay all the events
ha - I thought ‘is that your phone number?’ and then got it. Man, I need to stop working
i have a castra backend that builds views (compiled on the server as queries) for reads
are you using materialised views - I was really excited about them when I first heard of them but then looked at the list of restrictions (in MS SQL) and found out they really weren’t that flexible
but now, nothing beats a simple event-stream which can be consumed to denormalise anything - no need for views or anything
that means we don't have any state in the database for the views, wich is key for my sanity
I see. Are you using https://github.com/jkk/honeysql? That rocked my world as well.
ah OK, I think you might be misunderstanding honeysql? It turns a Clojure map into SQL, that’s all. It is brilliant for composing SQL queries (as it is just data).
I used it to good effect for our internal reporting library, The idea of building up the strings by hand ran into a brick wall pretty quickly.
colin.yates: btw, i heard about this approach u describe from this article for the 1st time: http://martinfowler.com/articles/lmax.html how about you?
@onetime I read about that when I was back in the Spring world, a long time ago. Very exciting at the time and it made me wish I had a use-case where that was the bottleneck
@micha oh I see - got you.
I toyed with yesql for that but didn’t really settle on anything
and breaks Cursive’s engine behind all those macros (or at least it did)
colin.yates: are u following the advances of kafka + samza? any opinion on them? mentioning the materialized views reminded me this talk: https://www.youtube.com/watch?v=yO3SBU6vVKA
I’m not following them closely but I do remember watching that video, about 35 minutes in (if I recall) and thinking, yeah, that is CQRS + event sourcing
to be honest, I haven’t yet needed to do anything that needed more than one machine to scale
and I find a lot of ‘scaling’ problems can be designed away
I see - yeah, honeysql is not going to help there.
man the conversations that come out of this thread awesome
thats pretty much my experience too...
if it was running on multiple machines then i can usually re-architect so 1 machine was sufficient for it...
did you not find the isnull(x, 1) came out way more expensive than (and x is nil or x = 1) in the query optimiser (been years since I looked at this though)
sounds like a trick from the "celko" sql book... i was doing the proof reading of the hungarian translation and translated some of it too
http://www.amazon.com/Joe-Celkos-Smarties-Fourth-Edition/dp/0123820227
I also managed to get rid of those is nils by a neat trick by equality and the fact that nil is always (for all intents and purposes) nil. So (and c.isdeleted <> 1) is true when c.isdeleted is nil or 0
(I think - been a while, but it made a huge difference to the query plan)
It’s really old now, but http://shop.oreilly.com/product/9780596008949.do is a great SQL book
(campaigns {:filters {:networkid {:type :range :from 1234 :to 1234}} :sorting {:by :id :order :desc} :paging {:page 45 :page-size 50}})
since I have your attention
do you guys emit from the dom or render with :display none for tabs and things like that?
each tab might be a non-trivial page with a 1000 row table and a summary chart
because we haev a powerful api so people can make hundreds of thousands of anything pretty much
now jquery is more accessible I might re-think pagination...
if you don't deallocate the dom elements, does it matter if they're actually in the dom or not?
no, I guess not. I remember reading something about reflowing being affected but I can’t remember the details
it mostly matters during debugging because they are in the way so u cant see the tree from the forest
either way I am hoping to have cells backed by those thousands of entities and pagination will be local
colin.yates: im preparing a repo to showcase these kind of things: https://github.com/exicon/webapp-skeleton
we also hit some performance issues because we had ~400 row list of mobile apps with their icon and ratings in the DOM 3 times...
@onetom: nice - thanks.
this is strictly desktop (actually Chrome)
since you can cache and rollup in the client whenever you want and save it to disk where oyu have unlimited space
micha: thanks confirming the :refers
feature. it worked indeed:
https://github.com/exicon/webapp-skeleton/commit/99917510e84709efce7a96b72b0217c6317534e5
like oh i'll just add a debug log here real quick and remember to remove it in 5 minutes
we always go thru the diffs before we commit, so console logs practically never slip into the code
gotta go - back tomorrow - it’s been great all - learnt loads
mynomoto: i would like to define a loop-tpl
replacement. u wouldnt want to require that explicitely, would u? 😉
my pleasure @colin.yates
one thing it does is generate the boilerplate for the html file and the .cljs.edn file based on a single .cljs.hl page file
boot-hoplon is quite clear now (or i just read it enough times 😉
at least im satisfied with the speed i could discover this :refers
thing.
@micha: decoupling those will make it possible to use hoplon as an api to create dom things. I think this is already possible, only no examples around.
btw, just the other day we used an svg dom element generated by a vanilla js lib as a function: https://github.com/exicon/hoplon-chart-examples/blob/master/src/index.cljs.hl#L149-L155
which would ensure that if you "mount" hoplon in another app it won't be causing any ill effects outside the mountpoint
awww, shit, the conversation about javelin cells and whatnot has scrolled out of my slack buffer ;(
we took the example from https://gionkunz.github.io/chartist-js/examples.html#behavior-with-jquery and hoplonified it a bit
@mynomoto: phew thx!
i saw this site once but havent bookmarked it so never found it again.
how do u find it usually?
and who is operating it? doesnt look official (as-in slack related)
is it opensource? can i run it for our company slack account?
micha: i was bitten again by loop-tpl binding behaviour. i haven't wrapped my seq into a formula cell... i guess it's a negligible overhead from performance perspective, so i would make it the default behaviour. can't really think of a case where i wouldn't want to do that.
(loop-tpl :bindings [[sub-label sub-path] (cell= (partition 2 items))]
(menu-item ... ))
vs
(loop-tpl :bindings [[sub-label sub-path] (partition 2 items)]
(menu-item ... ))
so sometimes (which is the rarer case) u would just
(let [things (create-things-cell items)]
(loop-tpl :bindings [thing things]
...))
right?because that's all i get when i forgot the (cell= ) from around that (partition ...) call
i think a tiny bit of better error reporting here could have a huge impact on adoption