Fork me on GitHub
#clojure
<
2018-11-16
>
didibus01:11:04

Anyway to have one namespace equal another? Like say I want to move all the code elsewhere, but I dont want to break existing code?

didibus01:11:29

Hum, it looks like import-vars from potemkin js what I want

Hwasung13:11:09

Hello, I am trying to set up Clojure in a Gradle project using the gradle-clojure plugin. But when I try to run the test, it cannot find the Java code class. (Class not found error.) Would anyone have some pointers how to set up the classpath correctly?

noisesmith17:11:40

what class is it not finding?

Hwasung22:11:44

Thanks for the reply. I figure it out. The class it wasn't finding was in fact a static inner class so I was in fact importing it incorrectly... :-S

ghadi18:11:15

that's gonna get declined @borkdude

ghadi18:11:46

it proposes an action without enumerating the problem sufficiently or alternatives

ghadi18:11:16

(my 2c -- it's not up to me, but I'd decline it)

ghadi18:11:55

coercions are a runtime thing -- you could spec those function and catch improper calls with instrumentation

slipset18:11:22

@alexmiller suggests not specing to expect sets as it would break existing code

slipset18:11:41

coercion would not break existing code, AFAICT

borkdude18:11:29

@ghadi good suggestion. we’re exactly going to with speculative: https://github.com/slipset/speculative/issues/70 one open question is if set functions are expected to receive sets or things that are coercible to sets. if we can settle that the result should always be a set, one or more args to these functions must be set, right now.

borkdude19:11:34

so the major question is: what’s an improper call

andy.fingerhut19:11:58

I had not seen anyone go full on for the approach of "help many Clojurians help collect data on how they use set functions in the wild" before. I suspect that collected data may be used in making a decision here (but don't know that for sure). Cool you are trying to collect such info.

borkdude19:11:39

we’re already running specs on a pile of code using coal-mine.

slipset19:11:21

@ghadi Out of curiosity though, what would be the downside of coercing to sets?

andy.fingerhut19:11:22

Alex Miller has at least made a few statements suggesting that how these are commonly used in the wild might affect how an official spec for these functions would be written.

slipset19:11:02

I could live with an argument that “coercing to sets will make the code much slower”

Alex Miller (Clojure team)19:11:11

I’m not going to decline it but agree this could be better written from a problem point of view

borkdude19:11:17

@andy.fingerhut We discovered a lot of beginners use merge as a replacement for conj, which works in most cases, but I’m not going to spec merge the same as conj because of crap code from 4clojure

andy.fingerhut19:11:17

@borkdude Is there some part of that page that I can click on to see spec failures for calling set functions? Not sure what I am looking for there.

borkdude19:11:15

@andy.fingerhut we haven’t got the specs yet, it’s being worked on. but there are questions, like: what is an incorrect call. we’re able to run this on a corpus

andy.fingerhut19:11:22

Are you considering running the corpus on more than one proposed spec, of varying strictness on the function inputs?

andy.fingerhut19:11:09

i.e. don't wait for "the correct spec" to be given to you. Try experiments on 5 or 6 different reasonable-looking variants, and see which have what kinds of things that are caught.

borkdude19:11:50

@andy.fingerhut we usually start with a spec. we find a problem, and come to the conclusion the spec wasn’t good and change it.

borkdude19:11:12

waiting for the correct spec is the halting problem

andy.fingerhut19:11:33

I suspect that some of the specs that do have failures may be reasonable ones for the core team to consider. They might be willing to "break" some existing uses.

andy.fingerhut19:11:54

They have done so for existing usages of the ns macro, and helped fix them in the wild.

andy.fingerhut19:11:26

i.e. don't discount a spec as bad because it had some failures. Record them and summarize them, on a JIRA issue.

borkdude19:11:36

yeah, we may not permit the usage of union in pprint for example, because that’s an outlier

andy.fingerhut19:11:00

I'm not demanding that you do this, of course, and sorry if my language makes it sound like I am. These are suggestions for possible way to move forward here.

borkdude19:11:10

we have had a use of merge with java.util.HashMap, which works and isn’t that unreasonable

borkdude19:11:21

so we changed the spec

borkdude19:11:37

but for crazy shit that just works by luck, we may not. it’s on a case by case basis

andy.fingerhut19:11:22

Right, just realize that whatever you come up with as your favorite spec, the core team may want to go a different way, so having the data on the failures for other alternatives is probably useful in making that decision.

borkdude19:11:08

I’m recording it in issues

andy.fingerhut19:11:33

cool, and thx. You will not be getting any fame or groupies over this, by the way 🙂

borkdude19:11:39

why not, I ask you?

andy.fingerhut19:11:36

OK, if you do, I want to know how you did it 🙂

borkdude19:11:23

you have tried?

andy.fingerhut20:11:02

I have one groupie, and she married me 🙂

borkdude20:11:47

what specs did you impress her with to win her?

borkdude20:11:01

btw I’m also married, so no reason to do it for girls.

andy.fingerhut20:11:30

I am speaking tongue in cheek when I mention "fame and groupies" and software development in the same sentence. Any specs I may use to impress her are proprietary information 🙂

borkdude20:11:54

LOL. I got it. Was just making fun.

andy.fingerhut19:11:02

Yeah, that sounds like good data to collect there, and would love to see what you find. Please let me know when you have more on that.

Alex Miller (Clojure team)19:11:24

the problem (as I understand it) is that calls to set functions with invalid inputs do not fail or give feedback and instead often “succeed” with unexpected results

Alex Miller (Clojure team)19:11:36

there are a set of alternative options to address that

borkdude19:11:49

@alexmiller first we have to define: invalid input.

Alex Miller (Clojure team)19:11:07

that is another aspect of the problem, yes

andy.fingerhut19:11:20

Hyrum's law and the desire for backwards compatibility can make the best laid plans of mice and men gang aft agley

didibus19:11:47

I'm never in favor of coercing input. Garbage in -> garbage out, or runtime error

andy.fingerhut19:11:19

With appropriate tribute and apologies to Robert Burns: https://en.wikipedia.org/wiki/To_a_Mouse

Alex Miller (Clojure team)19:11:20

actionable tickets have: problem, tables of alternatives and their tradeoffs, performance information if applicable, and of (least important) patches

borkdude19:11:43

there’s one call in clojure.pprint that calls set/union with two key-seqs. is it an invalid call (works by luck) or valid.

andy.fingerhut19:11:58

Sorry for the huge in-line image there. Didn't intend for that, either.

Alex Miller (Clojure team)19:11:59

please make a table of questionable known uses

andy.fingerhut19:11:40

@borkdude Do you mean the one in clojure.data/diff ?

andy.fingerhut19:11:28

If so, "infamous" I think overstates how well known it is, outside of a very small group of people, most of whom are in this discussion 🙂

borkdude19:11:41

haha, I’ll remove the word

Alex Miller (Clojure team)19:11:06

and to reiterate, I find this to be like way way down the list of things I consider to be priorities. are people really running into this so often? my suspicion is that we have spent vastly more time talking about it than people have spent debugging it.

andy.fingerhut19:11:44

If there is not a term or phrase for "annoyance that something is so close to perfect, but not quite", there ought to be 🙂

noisesmith19:11:27

something related to the princess and the pea maybe

borkdude19:11:55

personally I always coerce myself when calling these functions or “type check” manually

andy.fingerhut19:11:15

Semi-related, this joke I find quite hilarious by Emo Phillips: https://www.theguardian.com/stage/2005/sep/29/comedy.religion

slipset19:11:20

Ticket updated.

👍 4
slipset19:11:02

I’m happy to have it closed with a decline, but then I’d suggest to decline the referenced tickets as well.

Alex Miller (Clojure team)19:11:24

I’m happy to try to address it (if only so I can never talk about it again :), but would appreciate having everything laid out before-hand

slipset19:11:08

I’m done talking about it. 🙂

didibus19:11:58

This problem of garbage in -> garbage out is kind of everywhere in Clojure though. I feel if we need to address it, Spec is the way to go. With pre/post and assertions probably coming in second.

didibus19:11:56

For Speculative, why not have different levels of strictness?

borkdude19:11:04

or plain asserts, which can be elided with an option. s/assert might also make sense

borkdude19:11:29

but then fdefs might be the way to do as well

borkdude19:11:41

@didibus define level of strictness

didibus19:11:37

Like I can choose between a spec that is what it historically allowed you to do. And one that is what you probably think it should do.

mattly19:11:26

does the clj command-line tool include an option to just install deps for deps.edn without doing anything?

valtteri21:11:27

@U053V4R5N clj -Sforce < /dev/null

borkdude19:11:40

@didibus if you want the spec that allows you what it historically allowed you to do, you might as well spec it with any?

didibus19:11:32

I mean, say using merge for conj, when it works, that could be the relaxed spec

didibus19:11:43

Or union over vectors, sure, the name union no longer makes sense, but it does a kind of unordered merge of both vector. And if it does so consistently, then that can be allowed in the relaxed spec

borkdude19:11:48

@didibus what’s the point of writing specs that aligns with code that is known to work, but at the same time, code that should optimally be changed

didibus19:11:15

@borkdude The point is that, I expect a lot of people try out a function in their REPL, and they are looking for a behaviour, if it works, they use it, even if there is a mismatch with the common sense you'd expect given the name of the function. But, you still want to catch passing input that would throw, or that doesn't return consistently over the entire domain of the type. Like say it works for small vectors, it can union it, but for big vectors it changes on behaviour. So that is still useful. If I'm trying to add speculative to an old big project, that would be a lot less pain. And on new projects I could move to the strict spec.

borkdude19:11:37

@didibus We have exactly the thing for you if you’re running into a speculative spec that doesn’t work for you:

(stest/unstrument `the-fn)
🙂

borkdude19:11:14

and welcome to post an issue

didibus19:11:08

You mean unstrument ?

borkdude19:11:17

yes, I updated the text. you don’t see this? I’ve had this more often that people’s Slack clients were not reflecting changes..

borkdude19:11:49

@didibus we also have some helper macros in speculative which allow you to turn off instrumentation in the scope of a body for one specific spec

didibus19:11:58

Ya I see it now

borkdude19:11:00

so various ways to handle it

didibus19:11:55

Ya. I guess unstrument probably is good enough. Actually, that's why I feel Spec is better then assertions. Because assertions if they broke an existing use case, you can't really say, exclude this assertion.

jaawerth19:11:24

so I've decided to give rebel-readline a try as a replacement for ultra as my main repl driver... am I right in thinking I'm going to need to spin up cider-nrepl myself from some post-launch code if I want to get the two working together?

borkdude19:11:39

@didibus in speculative you could do:

(speculative.test/with-unstrumentation `merge (merge #{} 1))

dpsutton19:11:50

i don't believe rebel and cider can play nice together @jesse.wertheim

borkdude19:11:03

it restores the instrumentation after the body

dpsutton19:11:10

rebel doesn't work with the emacs terminal

jaawerth19:11:21

Oh I'm using it with vim-fireplace

dpsutton19:11:33

this is from trying to spin up rebel readline in emacs terminal. you can try it there though

jaawerth19:11:34

which can also go without cider if need be, it's just nicer with

dpsutton19:11:29

you can run a terminal from vim. maybe see if vim's terminal is nice enough

jaawerth19:11:37

neovim runs a great one. My general workflow with nrepl, though, has been to use my terminal emulator in "stacked pane" mode, and then have one pane in that tab for neovim, and another for the repl. Then I just toggle between them depending on whether I want to send stuff from the editor or interact directly with the repl

noisesmith19:11:58

the one big gotcha with the nvim terminal, is that if the buffer is hidden it can quietly get killed

jaawerth19:11:34

but there's also the instrumentation that fireplace provides, which is IIRC enhanced by using nrepl over eval

noisesmith19:11:16

the one thing I like about a regular repl over fireplace is that with a tty repl I have an immutable log of my interactions

noisesmith19:11:33

(both the terminal hardcopy itself and the repl history file)

jaawerth19:11:41

I like having both and switching between them as the situation calls for it

noisesmith19:11:26

the quiet implicit death of ttys inside nvim means I can't really use the feature - I guess I could see how tmux works inside it but...

jaawerth19:11:30

Like, I'll use :%Eval or :Eval from nvim to load/reload stuff, and then call it from the repl to tinker

jaawerth20:11:14

for the curious: I was able to hack it into working using clojure CLI tools with this quick and dirty setup in my $HOME/.clojure https://gist.github.com/jaawerth/a8b2ad0e1e9de013b0110c549b1216f9

jaawerth20:11:34

I wonder if there's a way to refer to env vars from deps.edn... wasn't able to find anything 😉

seancorfield20:11:29

I have wanted a requiring-resolve in Clojure for years -- hurrah for 1.10 Beta 6! 🙂

😮 4
seancorfield23:11:00

It turned out we had a variant implementation of this in four different systems at work -- all replaced by requiring-resolve now and everything tested on Beta 6! i.e., four variant implementations 😐

Alex Miller (Clojure team)21:11:55

There were several impls in core already and I’m sure more in the wild...

seancorfield21:11:20

I have an impl in almost every single project... 🙂

dominicm21:11:56

It's in integrant and cider. @seancorfield I'm surprised you have it in application code?

seancorfield21:11:03

We actually have some data-driven stuff that uses qualified keywords for a variety of lookups and transforms, and then at the end they are converted to symbols and resolved to functions to call.

dominicm21:11:05

Same as integrant, somewhat. I guess integrant doesn't really have this.

todo21:11:49

I agree that in general, "valued oriented programming" is better than "place oriented programming" (overwriting memory adcdresses), and that "functional programming" is better than "imperative programming." One area I am having a lot of problems with is GUI programming. I often have one big state atom, and a particular GUI element (a textbox, a slider, a toggle) might have a "path" or something like [:superman 23 :sword :damage]. This is problematic in that if I ever insert/delete before an item, or move a hshmap around, the "path" changes. It seems that in this case, we want something similar to "place oriented" programming, where for each GUI element, I can have it point to something and say "this is the object you are going to MODIFY". Unfortunately, "MODIFY" is not pure and does not seem to go well with functional programming / purity. The best I can think of so far is somtype of datascript db that we modify in place. How do otherss solve this problem? (PS: this seems to be a brick wall I run into with any gui app of sufficient complexity)

noisesmith21:11:15

@todo the way that reagent handles this is requiring a metadata :id so it knows how to track persistent objects in a sequence that will be modified

todo21:11:32

you mean ^:key ?

noisesmith21:11:37

if there is no :id, then every redisplay is a brand new object

noisesmith21:11:45

yes, ^ is the syntax for adding metadata

todo21:11:48

I'm referring to a different problem.

todo21:11:14

The problem I'm referring to is: we interact with some GUI element, we modify the state -- how do we update the global atom?

noisesmith21:11:25

you are still place based because you are updating a state

noisesmith21:11:33

you update atoms using swap!

noisesmith21:11:56

if you treat the contents of the atom as data that you derive UI from, things get simpler

todo21:11:59

Does every GUI element have it's own atom? Isn't 'distributed state' bad?

noisesmith21:11:05

no, it's a single atom

todo21:11:26

Okay, so we have a single global atom. We have lots of gui elements.

slipset21:11:29

@seancorfield seems to me from the patch that requiring-resolve is private, so you’re getting it. But not?

seancorfield22:11:57

Doesn't look private to me

Clojure 1.10.0-beta6
user=> (source requiring-resolve)
(defn requiring-resolve
  "Resolves namespace-qualified sym per 'resolve'. If initial resolve
fails, attempts to require sym's namespace and retries."
  {:added "1.10"}
  [sym]
  (if (qualified-symbol? sym)
    (or (resolve sym)
        (do (-> sym namespace symbol require)
            (resolve sym)))
    (throw (IllegalArgumentException. (str "Not a qualified symbol: " sym)))))
nil

slipset22:11:51

You’re right. I just read the latest patch in https://dev.clojure.org/jira/browse/CLJ-2432

slipset22:11:24

Which has a private requiring-resolve.

todo21:11:40

This means each gui element needs to somehow store "which path in the atom" it modifies.

todo21:11:01

The storing of 'which path' seems very problematic to me, and seems like it's a hack at getting 'pointers'

noisesmith21:11:02

why would the ui element modify the global atom for itself?

todo21:11:27

because if we click on acheckbox, we need to toggle some bool from false to true

noisesmith21:11:28

it might have internal state separate from app state (data about the domain)

lilactown22:11:01

@todo this is why re-frame makes you use named keys for subscribing and dispatching of changes

lilactown22:11:41

you create an event of :button-clicked that is then associated with a function that actually knows about the shape of the state atom

lilactown22:11:53

the GUI only knows it needs to dispatch :button-clicked

todo22:11:04

@lilactown: in re-frame, is your "full path" a single keyword or a vector of keywords tracing a "pth" from the "root of atom" to "this particular gui element" ?

lilactown22:11:29

likewise, it can subscribe to the :toggle subscription, which is also associated with a function that actually knows about the shape of the state atom

todo22:11:27

what does 'exactly not' mean? 🙂

noisesmith22:11:50

the position in the page of the GUI element shouldn't have any correlation to the position in the app state of the data it touches

noisesmith22:11:01

I mean, sometimes incidentally it might, but why tie them?

todo22:11:28

@noisesmith: suppose you have a todo list, the item's location in the vec and it's location in the div are pretty related

lilactown22:11:29

(reg-sub :some-query-id
  (fn [state _] (:toggled? state))

;; elsewhere in your UI code

@(subscribe [:some-query-id]) ;; => false

noisesmith22:11:53

@todo I wouldn't make it a 1:1, that seems extremely error prone

lilactown22:11:44

you subscribe to the named query, which is a reference to a function that creates the "materialized view" into the state

todo22:11:51

@lilactown: so the reframe model is: every button fire's off it's on 'msg' , which in this case is some keyword, ... and some other function has the job of doing "keyword + old-state -> new-state" ?

lilactown22:11:29

yes? every button might not have it's own message. it depends on the semantics.

lilactown22:11:40

but your UI code dispatches events and subscribes to subscriptions

lilactown22:11:19

you then write code that associates handler functions to those events and subscriptions which update the state and parse the state into the form that your UI cares about

todo22:11:09

I still have a hard time seeing how this works with nested structs.

todo22:11:22

Suppose we have something like say, a wysiwyg editor.

seancorfield22:11:57

Doesn't look private to me

Clojure 1.10.0-beta6
user=> (source requiring-resolve)
(defn requiring-resolve
  "Resolves namespace-qualified sym per 'resolve'. If initial resolve
fails, attempts to require sym's namespace and retries."
  {:added "1.10"}
  [sym]
  (if (qualified-symbol? sym)
    (or (resolve sym)
        (do (-> sym namespace symbol require)
            (resolve sym)))
    (throw (IllegalArgumentException. (str "Not a qualified symbol: " sym)))))
nil

lilactown22:11:03

honestly the re-frame docs probably explain this better than I can: https://github.com/Day8/re-frame

todo22:11:00

@lilactown: I tried re-frame before, but preferred reagent. After all this dispatch system -- how does re-frame store data?

todo22:11:13

I'm failing to see how re-frame makes the problem easier.

lilactown22:11:25

re-frame is built on top of reagent

todo22:11:22

The problem I have is: clojure/functional programming seems to encourage deeply nested structs. For manipulating state, I think modifying a sql table is easier than modifying a deeply nested structure.

lilactown22:11:52

it solves the problem you're talking about - you have a single state atom (called the "db"), and you want to separate your UI from the structure of your app's db

todo22:11:04

This whole notion of keeping a pointer/ref to some part of a deeply nested structure seems very troublesome, and it seems like raw pointers or a sql table via 'id' is easier.

lilactown22:11:13

think of it liked stored procedures and materialized views

noisesmith22:11:14

why would you want nested state here, I don't get that - you have a widget tree, if two widgets need to share state they can refer to a separate and flatter shared data location

noisesmith22:11:39

why should the widget care where it is in the parent tree?

seancorfield22:11:50

Does re-frame have cursors like reagent? That was the way to deal with nested state.

lilactown22:11:01

yes. that's what subscriptions are!

todo22:11:14

@lilactown: doe re-frame structure the "single atom db" ?

slipset22:11:05

Jumping in here as a total reframe n00b, but the whole concept of subscriptions is bothering me.

lilactown22:11:43

it is it's primary attribute I would say. all of it's machinery is in support of your state being stored in a single global atom

todo22:11:45

how does 'cursor' deal with the problem of 'my parent just moved' or 'some sibling got inserted before me in the vec' ?

todo22:11:55

how does the cursor stay valid in the above two cases?

slipset22:11:18

Simply because my components have gone from being pure fns to something that receives data through other ways than its arguments.

lilactown22:11:27

@todo now you've lost me. are you talking about parent/sibling in the UI tree?

lilactown22:11:52

@slipset you can have as many or as little components that actually subscribe as you want

lilactown22:11:42

ok? ¯\(ツ)

todo22:11:03

I don't see how these cursors can stay valid if it's parent moves or if some sibling gets inserted before the cursor, thus changing it's 'pathn'

lilactown22:11:03

I'm still confused on what you mean

lilactown22:11:19

"parent" or "sibling" of what?

todo22:11:29

suppose a cursor has path [:foo 222 :bar 34]

lilactown22:11:44

are you talking about UI parent/sibling or a change to the structure of your application state?

todo22:11:46

then ,suppose we insert an item into (:foo atom) before 222, ... now the 222 points at what was originaly item 223

todo22:11:03

I'm saying: when we modify the global atom, the cursors can become invalid / point at wrong objects.

slipset22:11:09

@lilactown sure, but it’s more about the concept. And as such I’m not overly enthused about the whole automagically-deref atom functionality of reagent either.

lilactown22:11:12

so you have something like this:

{:foo [ ... {:bar [1 2 3 ... 34]} ... ] }
where :foo is a collection that is being updated dynamically

lilactown22:11:28

this seems very contrived. you would simply not create a cursor that refers to a specific element in the collection :foo

seancorfield22:11:57

Right, cursor is assumed to be a path through nested maps (only), not vectors etc.

seancorfield22:11:31

(and I'm pretty sure cursors got merged into reagent core itself -- not sure why they didn't remove the separate repo?)

lilactown22:11:50

it says it's deprecated

seancorfield22:11:34

Yeah, as a standalone lib. I'm sure it was merged in (partly because I wrote it and we had lots of discussions about whether it was a good addition to core reagent or not! 🙂 )

lilactown22:11:53

yeah, it's merged in

todo22:11:40

okay, so the reagent solution of cursors is: cursor = path, path = vec of keywords, so all ancestors = maps, no vecs allowed

todo22:11:57

so if you were to implement a todo list and have cursors, how do you get a cursor to a single todo item, or do you not ?

seancorfield22:11:07

If you're changing an element of a map in a vector, you have to rebuild the vector anyway so you only need a cursor to the containing component.

noisesmith22:11:34

@todo I keep thinking you're conflating layout structure and domain data structure, these should really be distinct as I understand it

seancorfield22:11:47

(in other words, you need to change the value for the whole :todo-list, in order to change the boolean flag inside a map in one of its elements)

todo22:11:54

@noisesmith: I don't agree with what I think you think I am thinking of. For any app, we need some central storage of state. Then, GUI elements need to end up manipulating this state. This state, in my current work, is often very compositional and thus deeply nested / hierarchical.

lilactown22:11:33

you could have something like this (re-frame):

(reg-sub :todos
  (fn [db _]
    (:todos db))

(defn todo-item [todo]
  [:div (prn-str todo)]))

(defn app []
  (let [todos (subscribe [:todos])]
    (fn []
      (for [todo @todos]
        [todo-item todo]))))

lilactown22:11:08

inserting a new todo will update the :todo list and re-render the app component

todo22:11:49

And we don't care that all the cursors have been invalidated because if we have to rebuild the items, we can rebuild the cursors as well?

lilactown22:11:06

in this case there's only one "cursor" and it's the subscription to the todos list

lilactown22:11:29

but if the child components did contain subscriptions as well, yes those would be re-created

todo22:11:59

I am confused, so say we are editing an item in the todo list, and that we wanted to update the global atom on every keystroke (hypothetically), how would we do this?

didibus08:11:34

I think you'd want to make your global atom state a map of maps of maps. And when you need to represent order, like a list of todo items. You'd have your individual todo items be a map also, so you have a vector of maps. Now to find the element, you need to perform a quick search over it.

didibus08:11:50

{:id :todo-items
 :value [{:id item1
          :value "Call sarah."}
         {:id item2
          :value "Buy milk."}]}

didibus08:11:41

Another way to do it is that your items are also maps, but they have an order entry.

{:todo-items
  {:item1 {:value "Call sarah."
           :order 0}
  {:item2 {:value "Buy milk."
           :order 1}}
And now your component just needs to make sure to render each item based on their order entry.

lilactown22:11:12

I'd probably do something like this

(reg-sub :todos
  (fn [db _]
    (:todos db))

(reg-event-db :update-title
  (fn [db [_ todo-num new-val]
     (update-in db [:todos todo-num :title] new-val])))

(defn todo-item [todo-num todo]
  [:div [:div "Title: " (:title todo)]
   [:input {:value (:title todo) 
                :on-change #(dispatch [:update-todo todo-num (.. % -target -value)])]])

(defn app []
  (let [todos (subscribe [:todos])]
    (fn []
      (for [[todo-num todo] (map-indexed vector @todos)]
        [todo-item todo-num todo]))))

seancorfield23:11:00

It turned out we had a variant implementation of this in four different systems at work -- all replaced by requiring-resolve now and everything tested on Beta 6! i.e., four variant implementations 😐