Fork me on GitHub
#hoplon
<
2016-02-02
>
dm313:02:43

I'm slowly converting to the Hoplon way of doing things, but it's not easy simple_smile. For example, I have the following pattern repeated a few times (the app is kind of like a spreadsheet with cells that you can move around):

(defn move-cell [cells idx new-pos]
   (update cells idx #(if (can-move? cells % new-pos) (move % new-pos) %)))

(defelem sheet [{:keys [cells]} _]
  (loop-tpl [[idx c] (cell= (map-indexed vector cells))]
    (sheet-cell :on-moved #(swap! cells idx move-cell %))))
it seems like I could make the above more direct, somehow leverage the cells/formulas and avoid passing the callbacks, but I'm not sure how. E.g. I cannot update the cell bound to c directly as it's a formula, so I go through the index. Am I making some fundamental mistake here?

micha13:02:54

that looks pretty cool!

micha13:02:48

have you seen the javelin lenses?

micha13:02:58

that's how you can make updateable formula cells

micha13:02:12

like formula cells you can call swap! or reset! on

micha13:02:47

it might not be an improvement, but it does let you encapsulate that logic in the lens

dm313:02:56

yep, didn't think of that

micha13:02:07

so the things that use the lens don't need to know about how to update the underlying stuff

micha13:02:25

sure, lmk how it goes simple_smile

laforge4913:02:08

Though you are not required to use dewdrop lenses.

micha13:02:39

yeah the idea is like suppose you have the following cells:

micha13:02:10

(defc the-map {:a 1 :b 2})
(defc= the-a-key (:a the-map))

micha13:02:50

and you want to make it so that you can do (reset! the-a-key 42) and it will update the formula

micha13:02:58

so what you'd do is make a lens:

micha13:02:40

(defc the-map {:a 1 :b 2})
(defc= the-a-key
  (:a the-map)
  (fn [new-value] (swap! the-map assoc :a new-value)))

micha13:02:58

the optional second argument to defc= (or cell=) is a callback

micha13:02:21

when you call swap! or reset! on a lens the new value that you're trying to set is passed to that callback

laforge4913:02:21

@micha did you get a chance to try the lens demo yet?

micha13:02:43

the callback then updates the underlying thing, so the thing that uses the lens doesn't need to know how to do that

micha14:02:00

i have not had a chance yet

laforge4914:02:37

It displays a map, a key and a value. You can change the key and the value and see the immediate change in the map.

laforge4914:02:53

This allows you to change any value in the map and to add new keys.

laforge4914:02:29

What is missing is deleting keys with empty string values.

laforge4914:02:50

Based it on your input demo, so all key entries are immediate.

micha14:02:06

how is it different from the path-cell thing?

micha14:02:12

(defn path-cell [c path]
  (cell= (get-in c path) (partial swap! c assoc-in path)))

(defc a {:a [1 2 3], :b [4 5 6]})
(def  c (path-cell a [:a]))

laforge4914:02:08

Except that it uses dewdrop lenses. So it is just packaging differences.

laforge4914:02:21

One lens handles atoms, doing the dereferencing and the resetting.

laforge4914:02:00

Another handles maps, doing the get and assoc. You combine these two lenses and drop them into a formula cell.

micha14:02:50

atoms? or cells?

laforge4914:02:10

from dewdrop's perspective, they are indistinguishable.

micha14:02:24

atoms don't provide glitch elimination

micha14:02:34

like you can't make formulas with atoms

laforge4914:02:58

both support dereferencing and reset! and swap!. Dewdrop does not use type hints.

laforge4914:02:17

So the code for dewdrop does not care.

micha14:02:33

it doesn't use any formulas, i guess?

laforge4914:02:53

Correct. you drop the dewdrop lenses into a formula cell.

micha14:02:19

what is the purpose of the extra level of indirection, the dewdrop layer?

laforge4914:02:16

no purpose when things are simple. But too often the structures are deep. Dewdrop lenses are composable but also it is easy to create custom lenses. So dealing with deep and complex structures becomes a reasonable thing.

micha14:02:26

what does it do that you can't do easily with just the cell lenses?

micha14:02:26

note that composite-value could also be a javelin lens

laforge4914:02:47

it doesn't do anything special.

micha14:02:49

and lens-key could be a lens itself

laforge4914:02:09

But if you have lenses for your app, you might want to use them, eh?

laforge4914:02:28

Dewdrop lenses are .cljc files.

laforge4914:02:41

So you can reuse your code.

micha14:02:09

i'm skeptical because it redefines swap! and reset! to be pretty much the same as the core ones, but incompatible

micha14:02:16

things like that

laforge4914:02:33

Not following.

micha14:02:44

like lswap!

micha14:02:55

seems unnecessary

laforge4914:02:10

lswap! is a dewdrop function that uses swap!

micha14:02:16

the already existing swap! seems to accomplish the same thing

laforge4914:02:30

yes. but the airity differs

micha14:02:51

but the extra thing you pass in is unnecessary

micha14:02:05

like the object should encapsulate that

micha14:02:11

like the javelin lenses do

micha14:02:33

that's why in javelin we didn't have to wrote jswap! and jderef

laforge4914:02:40

yeah, but I don't use objects. Perhaps I should.

micha14:02:10

whenever i find myself reimplementing core functions i always later see that it was unnecessary

laforge4914:02:44

I kept things simple by allowing you to define new lenses from a map with 2 values. In java I would have used subclassing.

micha14:02:22

there are two issues:

micha14:02:08

1. the lens object is tightly coupled to the data, but the data doesn't provide the lens itself, so you need to know at the call site which lens to use with which data

micha14:02:20

that means that you can't write abstract code

micha14:02:42

2. the lens object seems unnecessary, look at the javelin lenses for example

micha14:02:04

they have a getter and a setter, but it's encapsulated in the object

laforge4914:02:04

each lens object is coupled to a type of data. map, base64 encoded, etc.

micha14:02:14

yeah but you need to know at the call site

micha14:02:20

which lens to use with which data

micha14:02:40

this means you can't make general purpose abstractions on top of it

laforge4914:02:03

I can compose multiple lenses for use on both the server and the client.

micha14:02:08

so the next thing you will do is create a whole new set of lswap! functions that do swap and reset and whatnot for some specific lens

micha14:02:24

and the number of different swap! implementations explodes

laforge4914:02:26

lswap! is generic.

micha14:02:36

soppose i have this

micha14:02:49

(lswap! foo data1)

micha14:02:58

(lswap! bar data2)

micha14:02:20

if i do (lswap! bar data1) i get nonsense, because that lens doesn't work with data2

micha14:02:24

so what do i do?

micha14:02:29

of course i will do this

micha14:02:05

(def swap1! (partial lswap! foo))
(def swap2! (partial lswap! bar))

(swap1! data1)
(swap2! data2)

micha14:02:15

this is not good

laforge4914:02:48

you do get a warning when doing that in clj files I believe.

micha14:02:02

yes so now you have a case analysis in your code everywhere maybe

micha14:02:09

to try to figure out which swap to use

laforge4914:02:24

which is why you should not do that. heh

micha14:02:58

javelin bakes the lens implementattion instance into the data

micha14:02:11

so the caller can just use swap! and reset!

micha14:02:18

and they don't need to know which lens to use

micha14:02:34

because the object carries that information internally, and just does the right thing

laforge4914:02:45

Actually, the lens is baked in when you define a formula cell.

micha14:02:48

so now you can make a function that accepts a thing that can be swapped

micha14:02:00

and that function will work, no matter what kind of lens it is

micha14:02:10

or even if it's not a lens, and is just a cell or an atom

micha14:02:28

compare that to a thing that uses lswap!, you need a different function for each type of lens

micha14:02:38

which prevents you from forming abstractions on top of it

laforge4914:02:19

yes yes and when you use a dewdrop lens in a formula cell, the swap! still works on the formula cell. I don't see your point at all.

micha14:02:22

ok, suppose i have a function that adds one to a thing:

micha14:02:33

(defn add1 [x] (swap! x inc))

micha14:02:40

you can use this function on any javelin lens

micha14:02:49

it doesn't matter what the setter or getter is

micha14:02:59

that's not the case with dewdrop lenses

laforge4914:02:27

but it is the case for formula cells implemented using dewdrop lenses.

micha14:02:47

but then what's the point?

laforge4914:02:58

I have no idea what your point is.

micha14:02:59

you can then just cut out the middleman

alandipert14:02:06

anyone else using on windows 10 successfully? apparently BOOT_EMIT_TARGET=no isn't good enough anymore - https://github.com/hoplon/hoplon/issues/80

micha14:02:30

yeah we need to remove the thing that sets files in the fileset to read-only i think

laforge4914:02:33

Dewdrop lets you define complex transformations for use in both the client and server sides and also works with hoplon/javelin.

micha14:02:21

@alandipert: which version of windows btw?

laforge4914:02:39

I gave up some time back trying to do updates. I always rebuild from scratch. 😞

micha14:02:17

i'm going to push a new 2.6.0 snapshot

micha14:02:23

that should fix it hopefully

micha14:02:29

i removed that code

micha14:02:40

but there are other changes in there that might break things

micha14:02:47

so testing is in order

laforge4914:02:13

I'm always testing. I'll just re-enable reload.

laforge4914:02:53

man, boot show -U is slow. 😄

micha14:02:09

yeah maven sometimes needs to fetch a lot of poms

micha14:02:00

that's caused i think by poms that have parents which have parents etc

laforge4914:02:05

first time I ever ran -U on show.

laforge4914:02:39

Any timeframe for that new snapshot?

micha14:02:52

a few minutes

laforge4914:02:14

time for a coffee

micha14:02:59

2.6.0-SNAPSHOT released

laforge4915:02:56

but how do I grab it? there is no boot -U

laforge4915:02:11

Just update the boot properties file?

laforge4915:02:14

Yeah, that seemed to do it.

micha15:02:47

there is actually a boot -U if you're using the snapshot

micha15:02:25

-U --update-snapshot        Update boot to latest snapshot version.

micha15:02:56

that will be released with 2.6.0, of course

laforge4915:02:42

I'm using unshared worker, i.e. worker gets its own .js file. And worker.js MUST NOT reference window, which it does.

laforge4915:02:23

So I CAN NOT use the 2.6.0 snapshot. 😞

micha15:02:51

what does the version of boot have to do with it?

micha15:02:05

boot doesn't know anything about javascript web workers

laforge4915:02:19

not sure yet. I enabled repl and reload. disablin gthem now.

laforge4915:02:38

OK, is now working with the snapshot.

laforge4915:02:24

Re-enabled reload and that is the problem.

laforge4915:02:55

Need to try something less non-standard now. simple_smile

laforge4915:02:30

castra-notify-random works

micha15:02:44

👍 awesome!

micha15:02:09

did you try it with incremental compilation?

micha15:02:15

i.e., the watch task

laforge4915:02:06

now this is a bit more wierd...

laforge4915:02:27

I tried with servant demo. This is a shared worker.

laforge4915:02:10

The problem with this demo is that once it starts, it kills the workers. --It is not an interactive demo. So you would think everything would run fine.

laforge4915:02:19

Here's the output:

laforge4915:02:50

(after modifying a src file by adding a blank line at the end)

micha15:02:03

please do the "\```" thing if you can

laforge4915:02:30

java.io.IOException: Couldn't delete C:\Users\Bill\.boot\cache\tmp\Users\Bill\Documents\aatree\aademos\servant-demo\dys\mm3x96\main.js http://clojure.java.io/delete-file/invokeStatic io.clj: 434 http://clojure.java.io/delete-file io.clj: 430 ... boot.file/delete-file file.clj: 54 boot.tmpdir.TmpFileSet/commit! tmpdir.clj: 246 boot.core/commit! core.clj: 434 boot.task.built-in/fn/fn/fn/fn/fn/fn built_in.clj: 277 boot.task.built-in/fn/fn/fn/fn/fn built_in.clj: 277 boot.task.built-in/fn/fn/fn/fn built_in.clj: 274 boot.core/run-tasks core.clj: 862 boot.core/boot/fn core.clj: 872 clojure.core/binding-conveyor-fn/fn core.clj: 1938 ... Writing main.cljs.edn... Compiling ClojureScript... ò main.js java.nio.file.FileSystemException: C:\Users\Bill\.boot\cache\tmp\Users\Bill\Documents\aatree\aademos\servant-demo\dys\mm3x96\main.js: The process cannot access the file because it is being used by another process. file: "C:\\Users\\Bill\\.boot\\cache\\tmp\\Users\\Bill\\Documents\\aatree\\aademos\\servant-demo\\dys\\mm3x96\\main.js" reason: "The process cannot access the file because it is being used by another process.\r\n" sun.nio.fs.WindowsException.translateToIOException sun.nio.fs.WindowsException.rethrowAsIOException sun.nio.fs.WindowsException.rethrowAsIOException sun.nio.fs.WindowsFileSystemProvider.implDelete sun.nio.fs.AbstractFileSystemProvider.deleteIfExists ... boot.file/hard-link file.clj: 145 boot.tmpdir.TmpFileSet/fn tmpdir.clj: 261 boot.tmpdir.TmpFileSet/commit! tmpdir.clj: 247 boot.core/commit! core.clj: 434 adzerk.boot-cljs/eval328/fn/fn/fn boot_cljs.clj: 230 adzerk.boot-cljs/eval277/fn/fn/fn boot_cljs.clj: 135 adzerk.boot-cljs-repl/eval429/fn/fn/fn boot_cljs_repl.clj: 171 boot.task.built-in/fn/fn/fn/fn built_in.clj: 325 boot.task.built-in/fn/fn/fn/fn built_in.clj: 323 adzerk.boot-reload/eval516/fn/fn/fn/fn boot_reload.clj: 125 adzerk.boot-reload/eval516/fn/fn/fn boot_reload.clj: 124 boot.task.built-in/fn/fn/fn/fn built_in.clj: 166 boot.task.built-in/fn/fn/fn/fn/fn/fn built_in.clj: 277 boot.task.built-in/fn/fn/fn/fn/fn built_in.clj: 277 boot.task.built-in/fn/fn/fn/fn built_in.clj: 274 boot.core/run-tasks core.clj: 862 boot.core/boot/fn core.clj: 872 clojure.core/binding-conveyor-fn/fn core.clj: 1938 ...

laforge4915:02:02

don't understand your request, @micha

micha15:02:30

it's really hard to read stack traces that are pasted into slack

micha15:02:36

if you don't quote them as code

micha15:02:45

or put them in a snippet

laforge4915:02:54

' java.io.IOException: Couldn't delete C:\Users\Bill\.boot\cache\tmp\Users\Bill\Documents\aatree\aademos\servant-demo\dys\mm3x96\main.js http://clojure.java.io/delete-file/invokeStatic io.clj: 434 http://clojure.java.io/delete-file io.clj: 430 ... boot.file/delete-file file.clj: 54 boot.tmpdir.TmpFileSet/commit! tmpdir.clj: 246 boot.core/commit! core.clj: 434 boot.task.built-in/fn/fn/fn/fn/fn/fn built_in.clj: 277 boot.task.built-in/fn/fn/fn/fn/fn built_in.clj: 277 boot.task.built-in/fn/fn/fn/fn built_in.clj: 274 boot.core/run-tasks core.clj: 862 boot.core/boot/fn core.clj: 872 clojure.core/binding-conveyor-fn/fn core.clj: 1938 ... Writing main.cljs.edn... Compiling ClojureScript... ò main.js java.nio.file.FileSystemException: C:\Users\Bill\.boot\cache\tmp\Users\Bill\Documents\aatree\aademos\servant-demo\dys\mm3x96\main.js: The process cannot access the file because it is being used by another process. file: "C:\\Users\\Bill\\.boot\\cache\\tmp\\Users\\Bill\\Documents\\aatree\\aademos\\servant-demo\\dys\\mm3x96\\main.js" reason: "The process cannot access the file because it is being used by another process.\r\n" sun.nio.fs.WindowsException.translateToIOException sun.nio.fs.WindowsException.rethrowAsIOException sun.nio.fs.WindowsException.rethrowAsIOException sun.nio.fs.WindowsFileSystemProvider.implDelete sun.nio.fs.AbstractFileSystemProvider.deleteIfExists ... boot.file/hard-link file.clj: 145 boot.tmpdir.TmpFileSet/fn tmpdir.clj: 261 boot.tmpdir.TmpFileSet/commit! tmpdir.clj: 247 boot.core/commit! core.clj: 434 adzerk.boot-cljs/eval328/fn/fn/fn boot_cljs.clj: 230 adzerk.boot-cljs/eval277/fn/fn/fn boot_cljs.clj: 135 adzerk.boot-cljs-repl/eval429/fn/fn/fn boot_cljs_repl.clj: 171 boot.task.built-in/fn/fn/fn/fn built_in.clj: 325 boot.task.built-in/fn/fn/fn/fn built_in.clj: 323 adzerk.boot-reload/eval516/fn/fn/fn/fn boot_reload.clj: 125 adzerk.boot-reload/eval516/fn/fn/fn boot_reload.clj: 124 boot.task.built-in/fn/fn/fn/fn built_in.clj: 166 boot.task.built-in/fn/fn/fn/fn/fn/fn built_in.clj: 277 boot.task.built-in/fn/fn/fn/fn/fn built_in.clj: 277 boot.task.built-in/fn/fn/fn/fn built_in.clj: 274 boot.core/run-tasks core.clj: 862 boot.core/boot/fn core.clj: 872 clojure.core/binding-conveyor-fn/fn core.clj: 1938 ... '

micha15:02:55

which is better because it can be collapsed

micha15:02:06

look at this:

micha15:02:15

this
is 
  some
    code

micha15:02:20

see what i mean?

laforge4915:02:36

I can not see how you are doing this

laforge4915:02:10

you are not yet providing any help

micha15:02:10

click on the + next to the input box where you're typing

micha15:02:20

select "create a snippet"

micha15:02:34

type or paste things into the popup modal

laforge4915:02:28

you're welcome 😄

micha15:02:24

remember that windows fix we did for jetty?

micha15:02:30

can you try that?

micha15:02:47

no the one for jetty itself

micha15:02:17

make sure you're using boot-jetty of course

laforge4915:02:08

We had dropped that requirement some time back, hmm?

micha15:02:25

seems so yeah

laforge4915:02:53

some change got lost I guess

laforge4915:02:27

In any case, great progress!

laforge4915:02:08

Now, about that problem with shared web workers. It was fixed: https://github.com/adzerk-oss/boot-reload/issues/57

laforge4915:02:46

So I need to do a followup. Retest with 2.5.5 at least.

micha15:02:15

the version of boot shouldn't have anything to do with it

micha15:02:22

boot doesn't touch your web workers in any way

micha15:02:51

the version of the cljs-reload task is relevant though

laforge4915:02:57

which is why I need to retest with 2.5.5--it should still fail but I can't in all fairness report the error with 2.6.0-SNAPSHOT!

micha15:02:14

that issue has nothing to do with boot, is what i'm saying

laforge4915:02:35

agreed. but I need now to do more testing because the issue was closed.

micha15:02:55

yeah it's the boot-reload version that's relevant there

laforge4916:02:50

The fix was released 3 days ago and my test is failing with it. Just failing at a later point methinks.

dm316:02:07

if I create formula cells in the loop-tpl body, how do I make sure they're destroyed when number of elements decreases?

dm318:02:54

@micha AFAIK the cells aren't GC'ed, so need to destroy them explicitly. Should I attach some sort of onremove to the elements?

micha18:02:53

dm3: loop-tpl is actually fine, no need to do anything there

micha18:02:11

nothing is destroyed when the number of elements decreases

micha18:02:28

the elements are actually in a pool and are reused later when the items grow again

micha18:02:43

so there isn't a memory leak there as there is nothing to GC

dm318:02:25

in my case it's

(loop-tpl [[idx c] (map-indexed vector cells)]
   (let [max-size (cell= (max-size cells c))]
      (sheet-cell ...)))

micha18:02:42

yeah no memory leak there

dm318:02:48

after removing a cell from cells the formula gets invoked with c = nil

micha18:02:55

that's correct

micha18:02:03

because the nth item is now nil

dm318:02:09

ok, understood

micha18:02:20

the body of the loop-tpl is called exactly once

micha18:02:26

per item index

micha18:02:48

after that it always just refers to the cells that are derived from the nth item

micha18:02:05

so when the seq shrinks some of those will then be nil

micha18:02:27

but the elements you constructed with the loop-tpl body are still alive and in the pool

micha18:02:41

so it will consume memory corresponding to the maximum number of items in the seq

micha18:02:26

this also means that as the seq shrinks and grows there is no unnecessary DOM churn

dm318:02:34

ok, thanks for explaining

dm318:02:47

probably makes sense to write a detailed docstring there simple_smile

micha18:02:58

new DOM elements are only created when the seq grows beyond it's previous maximum size

dm318:02:11

I read the code and got the logic related to DOM

dm318:02:18

but cell semantics eluded me

micha18:02:20

otherwise previously created DOM elements are simply reinserted into the DOM

micha18:02:28

from the pool

micha18:02:12

it's like the motel example

micha18:02:26

where the rooms exist, even though there may be a vacancy

micha18:02:16

when a new customer arrives you don't need to build them a room

micha18:02:21

and tear it down when they leave