This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-03-10
Channels
- # beginners (356)
- # boot (4)
- # cider (44)
- # cljs-dev (73)
- # cljsrn (19)
- # clojars (5)
- # clojure (57)
- # clojure-brasil (2)
- # clojure-italy (1)
- # clojure-russia (9)
- # clojure-spec (9)
- # clojured (3)
- # clojurescript (15)
- # cursive (19)
- # datomic (10)
- # duct (3)
- # emacs (4)
- # events (1)
- # fulcro (5)
- # immutant (12)
- # keechma (3)
- # lein-figwheel (4)
- # lumo (2)
- # parinfer (11)
- # planck (4)
- # re-frame (3)
- # reagent (5)
- # reitit (1)
- # shadow-cljs (5)
- # spacemacs (6)
I mean... I think something like (if (not-empty a) a b)
would probably do it? but I guess I was just wondering if there was something "fancier"... maybe that is the best solution tho?
This ties into my enviornment problem from #cljsrn ...essentially if I have local path set I want to use that... otherwise I want to use the library default
@kara (not-empty a)
returns a
if it is not empty, so (or (not-empty a) b)
is the same as your if
construct
Morning :) I'm trying to find a way to get a collection of grouped subsets from a sequence. For example, if I have (range 16)
to represent a 4x4 grid:
(0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15)
I'd like to group this into 4 subsets (mini 2x2 grids) of:
(0 1 4 5)
(2 3 6 7)
(8 9 12 13)
(10 11 14 15)
Suppose i.have a basket:
(def basket '() )
I want to add cornflakes to it :
(conj bakset "cornflakes")
So how I add items to my basket ? Should I make a new basket everytime ,I want to add a item ?
I seem to spend 50% of the time spent on recatoring my clojure libraries, on figuring out the shapes of function arguments
@yogidevbear maybe partition
is what you are looking for.
1. What would you suggest as a workflow or tooling, to reduce this inefficient programming experience? 2. Would you say clojure and myself are not a fit, if I don't recall by heart the shapes?
@matan If I create a function that takes a list of strings I write it (def myfun [los] (do-something...))
. I know los is short for list of strings.
I guess that's why spec
creeped in into the language, because it's impossible to track your shapes/types when revisit a library for refactor
and then it all gets more cumbersome than writing typed code in a typed language, in a way
@suryapjr jep. Most of the time I store all state in a single atom. The basket is then in the atom. In the atom you swap! the basket for a new one.
following your example, suppose you have a basket:
(def basket (atom []))
you want to add cornflakes to it:
(swap! basket conj "cornflakes")
now the atom contains the basket with the cornflakes:
@basket
and @magra beat me to it π
@joelsanchez I like your vector better though π
@magra @joelsanchez thanks !!!
In the example above you can add the cornflakes to the basket without knowing what is already in the basket, or whether another thread just added something to the basket or removed something etc.
Is there a way to know the available functions for a data structure ?.. Like for example ..I have a list (def a '() ) Now I want to know the available functions for the list like conj .. can clojure show it in the REPL? Like we have methods? In ruby
Thanks Markus. Not sure if that will suit though. The dimensions might be variable (e.g. 6x6 grid or 8x8 grid etc)
I was looking into partition, but gets a bit complicated, esp. with a larger grid
@yogidevbear partition is actually simple and it describes exactly the task you are attempting
if you don't know the total collection is an exact multiple of your input, use partition-all instead
@suryapjr You can treat a list as a collection or as a seq (or a stack). So what you find under seq aplies as well.
you don't need to worry about the skip arg since you don't want to repeat or skip items
and @suryapjr @magra an atom in a def works but typically isn't the best option, for functional code the ideal is to take a basket as an argument to your function, return a new basket from the function, then use that return value from the caller
Ummmm.. @noisesmith..a simple example would be very helpful !
(defn purchase [basket item] (conj basket item))
(defn serve-customer [init-basket events] (reduce (fn [basket e] (cond (buy? ev) (purchase basket ev) ...)) init-basket events))
eventually you'd likely track more than a basket (which is why I defined purchase rather than just use conj) -you'd likely use an account which includes a basket in a hash-map along with other customer data
@suryapjr the problem you have otherwise is you would need a new def for each customer
so now what do you do, make a new namespace on each customer signup? it doesn't really work out
so in a web app for example, you have a function that gets all your customer data from the db (or cache), performs the operations with that data, and returns the data to put in the db (and also indicate api calls to make, etc.)
it's all done with functions - some input, some other output
no need to mutate data, just make sure you use return values
@suryapjr An atom is just a thread safe container for some other value. The idea is that you (mostly) march from one value in an atom to the next by applying a series of functions to it. Behind the scenes if different threads try to update the atom at the same time, one wins and the other gets to rerun its function with the new value.
Guys..when we do Java or scala ,we call the respective compilers and they generate a class bytecode which is interpreted by the JVM. ..what happens in the case of Clojure ?
clojure's byte code is generated as each form is evaluated, and it's in memory
optionally, you can create class files on disk, via aot and gen-class
for example, every fn or defn in your code becomes a new class
it has methods like .invoke and .applyTo that you can use to run the function
well, it's not fast if you create and eval new fn or defn forms inside your code after startup
and startup is slow
it's not interpreted, it's compiled
that startup time is compilation
it's just compiled into memory instead of disk
there's some ways to run lein faster btw https://github.com/technomancy/leiningen/wiki/Faster
but fundamentally there's some design decisions in the language (and to a lesser degree the vm) that mean start up time isn't prioritized - if startup is a primary concern there's languages that are much better about that
To be frank..I have never experienced a language as simple as clojure...I'm a novice in the field of programming..but clojure is the epitome of simplicity..I can say ..very beautiful..simpler than even python
I agree - clojure is definitely optimized for simplicity in its design, and has fewer special cases and gotchas than most languages
Takes a while to get your head around the ()..but then there's no other syntax for u :)
it could be even simpler if we were OK with worse performance or less libraries available (see scheme)
but clojure strikes a nice balance of useful simplicity
also it could start up a lot faster if we were OK with slower performance too
the rest of the lisps are unusable for anything serious in a reasonable timeframe (personal opinion)
@joelsanchez guile ties into a gnu/linux ecosystem nicely, but that's not the ecosystem I need to target for a web app
well - clojure has it's own byte code emitter and doesn't do some java stuff, but yes - the jvm just runs the bytecode
Whenever I create a new lein app ..I see a (gen : ) ;; something like that ..what's that
gen-class tells clojure to write a class file to disk, so that the code can be called directly by java
without extraordinary effort, you'll have about 1/10th the speed of java - which is close enough in a desktop app most of the time to not notice the difference
@suryapjr to date all of my big clojure jobs have been attempts to rewrite ruby to be faster
Somehow suspect the answer will either be "don't do it" or "macros", but I'm pretty new to clojure so I'll see where this lands : )
@mbjarland same answer I gave to pooboy - make functions that take ant as an argument, and return a new ant
if you have to wrap a stateful object, you can hide the side effects inside the ant objects operations (we don't do typical OO most of the time, but it's useful for managing boundaries when they get tricky)
there's a seq function, and there's a ISeq abstraction
You can think of a seq as a very abstract list
you call seq to get an ISeq instance from a collection of some sort
and yes, a seq acts like a list
+user=> (seq "hello")
(\h \e \l \l \o)
functions like map, filter, reduce all turn arguments into ISeq via seq if needed
+user=> (map int "hello")
(104 101 108 108 111)
Cool..gracias @noisesmith @russ767
@mbjarland @noisesmith since ant is fairly declarative, I was wondering if maybe what you do is have jar
and zipgroupfileset
and other similar functions evaluate to data that describes the actions to take and then have ant
interpret that data
A seq is something you can get the first item off of and also get all but the first and you can add something to the front. (This is more or less what implementing ISeq means).
@lee.justin.m sure - but somewhere you still want some context data representation passed from one to the next
@lee.justin.m that is exactly my criteria in the above
@mbjarland I think the key concept here is any global can be replaced by an object taken as an argument by each function, and returned by it also
@noisesmith that is more or less what I was thinking but I would have liked not to litter the dsl with a bunch of ant
as first argument
you need data somewhere
@mbjarland taking your state object as an arg is actually the simplest thing - every other option
every other option is more complex
it might be terser, never simpler
@noisesmith yes, agreed, I'm just wondering if you could opt to not show the noise to the user by using macros or something
you can use functions that take a state as a first arg to implement every other option (global mutables, dynamic vars, db storage, whatever) - nothing else is flexible in that way
@mbjarland worry about noise later - first implement the reliable simple thing (args) - make a syntax once it works
then you started wrong
basically what i was thinking is maybe everything could evaluate to i/o primitives like fileset
and what have you, and then ant
would be a big interpreter that would go make it happen. iβm just not sure how much data gets passed from one primitive to the next.
sorry, that's a bit strongly worded - I've made a lot of design mistakes that were expensive, and the solution was "take immutable data as an arg, return immutable data" -the longer you wait to implement that the more painful the translation is
@noisesmith I totally hear you, should be noted this is an explorative task for me, I am not pushing for any solution, rather trying to learn the pros and cons of different ways of doing things in clojure. Your answer is exactly why I'm asking the question here
for example I used core.cache in the app I work on
people complain about how it takes and returns immutable data, and doesn't cache store anything
that was a huge benefit, because I was able to replace putting data in an atom with putting data in a document store
while leaving all the core.cache logic, since the storage wasn't baked in
and it just worked on the first go - it's the only time I've implemented a cache of any sort that didn't have long term bugs show up
@noisesmith so assuming that we pass in immutable data and return immutable data, would you also opt for being explicit in this case? I.e. force the user to see all those ant arguments or would it be ok to make ant a macro (if this is even possible) and make the first argument ant
optional...or would that push us over onto the dark side again?
you can easily make a system that uses a namespace level atom on top of pure functions - it can be a separate namespace (I actually recommend that)
then, that new namespace allows using all the pure code, while always operating on a given atom
that way, the user doesn't need to bother with that arg, beyond telling the system which atom to use
@noisesmith that was kind of where I was going...but that is still global state and could get tricky in multi threaded situations...which is how I ended up here : )
well that's what atoms are - they protect data when modified by multiple threads
@noisesmith ah...ok I think I'm starting to catch on
just make sure you never @ and followed by swap! in the same block of code
no, atoms are never per thread
swap! is a mechanism that ensures that modifications are not racing
the value of an atom is shared by all threads, and updates are retried on conflict
yeah, knew that...was thinking more "flows of work", the user of the api would still have the option to give different invocations different atoms if the intent is to separate them
sure, OK
right, the first thing to avoid is forcing the user into a singleton
so perhaps there would be an inplicit atom and when needed the user could provide their own
but I'd also avoid forcing the user to use an atom - make sure all the logic works without the state mutation layer
maybe a specific API with an implicit atom or user provided etc. - as long as one can opt out
(that's my preference at least)
you mentioned macros before and I'd make a similar argument there - don't provide features via macro only, have a way to work with functions and an optional macro layer
because nothing is flexible like taking and returning immutable collections is
I would like to avoid macros in this exercise...I've come to understand that they have somewhat of a viral effect on code
You mentioned namespaced atom in a different namespace, care to elaborate that a bit?
what I mean there is have a namespace that is sufficient to use your entire API, and then another that wraps it
exactly
@mbjarland a big reason for this, if nothing else, is it simplifies testing (and writing tests that can be run in parallel threads)
but to me "easy to test" is a good canary for good code
hard to test code is the kind of code that usually goes wrong - not because of lack of tests, but for the same underlying reason that a test was hard to write
@noisesmith ok, I get the feeling this conversation just saved me a whole lot of head scratching and dead ends...thanks a ton. Might publish this as a clojars lib if and when I get it done...some io operations (pick out a bunch of patternmatched files from a zip file, copy them somewhere else etc) are still a tad verbose in clojure. This kind of dsl would make some system scripting tasks nice and concise
@mbjarland even in C++ you see the most knowledgable people saying "your classes should act like an int" and by that they mean const (AKA immutable), and without surprising behaviors of any sort - exactly what we want in clojure :)
I'd be interested to see what you come up with, and glad I could help - I should do a blog post on all this stuff
I come from a world where I've written a crapload of systems automation scripts in all kinds of languages...mostly groovy and bash (and bash really makes you want to jump off a cliff when complexity goes anywhere past copy file from a to b). I get the feeling that the clojure community is mostly sitting in containers and not really dealing with "list processes, filter list, copy files from a to b, deploy files to prod server" kind of scenarios
@mbjarland something I played with recently that might inform what you are trying is scsh -it's a scheme DSL for shell scripting
anyway, scsh is quite complex and does a lot of cool stuff, there's like a couple of nice ideas you can steal from it
this is the repo I was using to run those repl examples https://github.com/ChaosEternal/guile-scsh
usage has much better examples than my little gists https://github.com/ChaosEternal/guile-scsh/blob/master/USAGE ChaosEternal/guile-scsh Resurrection of guile-scsh on guile-2.0
Something wrong with my syntax :
(defn makelist [ a]
( let [ mylist '()])
(conj mylist "ok"))
the closing paren on let needs to wrap around the code using the binding created by let
@suryapjr a core concept in clojure is that things are visible in predictable ways - if you create a value (other than def at the top level), everything that uses it has to be inside the same form that created the value, or has to receive the value as an argument
@noisesmith last quesion...I was planning on all the nested functions returning pure datastructures, essentially maps. Do you see a reason for them to return the passed in "collector" (or ant or whatever) instance instead?
to be inside the let form, you need to be between the parens surrounding let
@noisesmith (let [ a "poo"])
@mbjarland on an abstract level, whose to say an ant isn't a hash-map? realistically, start with hash-maps, they are simple and you don't need the extra features of a custom class, later if you need it you can use a record or deftype
@noisesmith ok, I will noodle on this. Probably need to settle the concepts a bit and things will land
the nice thing about a defrecord is that you can do anything to it that you can do with a map, so switching later is simple
@noisesmith and thanks for the scsh links, will take a look and see what I can learn
@noisesmith ha, I guess I keep adding to the list of questions. Just figured I would compare to clojure.data.xml which has a very similar pattern for building up data:
(element :foo {:foo-attr "foo value"}
(element :bar {:bar-attr "bar value"}
(element :baz {} "The baz value")))
the problem I have with this is that as far as I can understand this becomes a "pure data" dsl, i.e. without passing in a "collector" of some sort, it becomes difficult to inject logic and conditionals in the middle of a tree of element
expressions like the above. You can, but then you need to re-structure your entire tree to support the conditional...at least this is how it seems to me. I guess for xml this is a decent compromise for tersenessit's very doable to work with and edit such a tree with zippers
there's also things like clojure.walk/post-walk and even update-in depending on the transformations you need
but yes, deeply nested data can get tricky in clojure's immutable data structures
as a worst case you can transform to adjacency-list, carry out operations on that representation, and translate back (I wonder if anyone has a lib for this...)
ah...I think the coin just dropped : ) @noisesmith ok thanks for all the help, I'll go back to the shed and see what comes out
the swap! function detects concurrent modifications, and commits the first modification and retries the rest
this totally breaks if people use reset! or if they use @ before swap! to drive their logic, but if you use swap! as intended you can ensure you don't have races
the key here is that swap! takes a function, which takes the value in the atom, and returns the new value for the atom
so even if two modifications try to happen, the second one can retry with the updated value from the first
swap! requires a function, 4 isn't a function and reset! is not safe
reset! is safe in that you won't get broken state, but it isn't safe from data races in your logic
@suryapjr what about (swap! myatom inc)
or (swap! myatom - 2)
that's the kind of thing where each one can retry, and the math will work out
(if you mix in (swap! myatom * 3)
you still get issues because reordering changes the meaning of course...)
that's not the syntax of swap
sure - (swap! some-atom function ...args)
so to subtract it from 2, you would need (swap! my-atom #(- 2 %))
or to subtract 2 from it, (swap! my-atom - 2)
it mutates the atom, by replacing the single object inside with a new object
we very rarely use atoms
and even in the few cases they get used we often find ourselves rewriting to remove them
right - if you just want mutable values to work with there's other languages that can do that in a straightforward way - a lot of the benefit of clojure relies on the fact that we are using immutable values everywhere
even atoms break if you put anything mutable inside
right, it will return 1 if you call it
reduce is for repeatedly calling a function on a series of inputs, and using each result on the next call
there's another function, reductions
that returns all the values along the way
+user=> (reductions + 0 [1 2 3 4 5])
(0 1 3 6 10 15)
+user=> (reduce + 0 [1 2 3 4 5])
15
so you can probably look at that and see how it calculated each of those values
+user=> (reductions conj () [1 2 3 4 5])
(() (1) (2 1) (3 2 1) (4 3 2 1) (5 4 3 2 1))
+user=> (reductions conj #{} [1 2 3 4 5])
(#{} #{1} #{1 2} #{1 3 2} #{1 4 3 2} #{1 4 3 2 5})
+user=> (reductions conj [] [1 2 3 4 5])
([] [1] [1 2] [1 2 3] [1 2 3 4] [1 2 3 4 5])
right
on each element, using the previous result as the first arg
often a for
in another language translates directly to a reduce in clojure
(defmacro simple [x] `x)
it doesn't do anything interesting, but it's quite simple :P
fundamentally it takes a form, returns a new form, and the form it returns gets compiled
+user=> (defmacro simple [] (list '+ 1 1))
#'user/simple
+user=> (simple)
2
it made a list with the symbol plus, the compiler compiled and ran that list
so you can use clojure's built in functions to modify code before it compiles
(where in most languages all you can do is manipulate strings before compiling)
+user=> (defmacro make-next-n [s n] `(def ~s ~(inc n)))
#'user/make-next-n
+user=> (make-next-n two 1)
#'user/two
+user=> two
2
the ~ inside means "expand this before compiling"
it's good to learn how macros work, but also one should be very careful implementing them - it's easy to make bad libraries that are hard to use with poorly designed macros
but a lot of our favorite clojure features aren't in the compiler, they are macros
A nice example of macros that i found is the infix one. A good demo of clojureβs ability to change its own behaviour.
(defmacro infix [exp] (list (second exp) (first exp) (last exp)))
The a call like (infix (1 + 2))
produces 3 like a usual infix evaluation in other languages.Run macroexpand
on the expression to see what actually is evaluated. (macroexpand '(infix (1 + 2)))
gives (+ 1 2)
The other thing to keep in mind is that while syntax quoting is very useful inside of macros, you don't need a macro to use -- or more likely -- play with it. So if you evaluate this: (def x 44) `(println "the number is" ~x)
You get:
(clojure.core/println "the number is" 44)
there's some nice trick with that
Or (def a [1 2 3]) `(println ~a) `(println [100 100 ~a]) `(clojure.core/println [100 100 1 2 3])
+user=> `#(+ % %2)
(fn* [p1__184__186__auto__ p2__185__187__auto__] (clojure.core/+ p1__184__186__auto__ p2__185__187__auto__))
Ops that last expression should have been `(println [100 100 ~@a])
Macros + syntax quoting means never having to wish that you could change the compiler source code. Well maybe never is a little too strong...
I think it means the main reason to edit the compiler is to make it emit better byte code
I'm probably forgetting other cases though
I have some confusion about the difference in what's happening with output when evaluating code in my editor (using an nrepl addon, evaluate selected code), vs running the code in the terminal repl (lein repl, the same one my IDE is connected to and uses to evaluate selected code). If I run the below code by typing it in my code file, selecting it, and evaluating it, in my "Output" window it waits three seconds, then prints 0, 1, 2, nil all at once, like it printed to an output buffer and waited for the loop to finish before showing me the contents of said buffer. If I run the below code in the terminal repl, it prints 0, waits a second, 1, waits a second, etc. Can anyone explain what is happening here?
(dotimes [i 3]
(Thread/sleep 1000)
(println "==>" i))
the terminal output is correct and I'd consider the editor integrated behavior a bug
nrepl must be waiting to collect all output of the form before displaying
iβd bet a nickel the editor is blocking until the form evaluates and then goes and retrieves the output
this has to do with nrepl's protocol, where it reifies inputs and output behavior
it's nrepl, there's no explicit "retrieval" - but that's basically what nrepl does
perhaps someday nrepl will implement streaming or partial output of forms?
i think nrepl does? here's the nrepl traffic in CIDER:
(<--
id "679"
session "87958be7-a29e-48de-9b5c-702418748369"
time-stamp "2018-03-10 14:20:17.914045319"
out "2
"
)
(<--
id "679"
session "87958be7-a29e-48de-9b5c-702418748369"
time-stamp "2018-03-10 14:20:17.929469596"
ns "com.breezeehr.docker.zookeeper"
value "nil"
)
@dpsutton how did you spy on the nrepl like that? is that a feature of cider? the reason i ask is because i would love to screw around with protorepl to see if i can maybe implement more features for cljs
as far as i know it should be pretty faithful to what's going back and forth. obviously annotated with some extra stuff like time stamp
well i was hoping to see what protorepl is doing and i donβt think it has any convenient logging features like that
@brandon.ringe my only guess is that you're building stuff up in a buffer to output when it's "done". you could try to poke it a bit and println a really big string and see if it will flush before it is totally done
@brandon.ringe does the behavior change if you cut-n-paste the code into your editors repl instead of selecting it and evaluating it?
(dotimes [i 3]
(Thread/sleep 1000)
(println (clojure.string/join (take 500 (repeat "a")))))
something like that? see if you can fill up its buffer and make it dump it@lee.justin.m Yes, the behavior changes. Select->evaluate waits for all output from the form to finish and prints all output at once. Copying and pasting into the terminal repl prints each value after each Thread/sleep.
so iβm distinguishing between a terminal repl and your editorβs interactive repl
ahh right, I stand corrected, it's definitely a client side bug and not nrepl
can you add an explicit call to (flush)
at the end of your do times block? is it possible that could work?
sorry iβm not being super clear. there three potential ways to evaluate the code (1) select and use some key combination to evaluate the code, (2) cut-n-paste into a repl running in your editor, (3) cut-n-paste into a repl running in a terminal. anyway it doesnβt matter. i was just thinking that method (1) is probably doing some thing different from (2) and (3)
@dpsutton I ran your code and it has the same behavior as before. I tried the flush too, it still waits in the integrated repl.
see if this produces any output for you
(.start (Thread. (fn []
(dotimes [i 3]
(Thread/sleep 1000)
(println i)))))
it immediately returns nil and only after prints. i wonder if after something is "done" if it can still watch for output
the problem with code like that in nrepl is that Thread doesn't inherit a re-bound *out*
i think CIDER is a little greedy in taking over *out*
and can cause problems which is why this "works" in CIDER
I'm using VS Code with an extension called Clojure which is what gives me the integrated nrepl. Idk if the editor or extension have anything to do with it.
@noisesmith that works at lein repl
. did you expect it to not?
@dpsutton try it with the server run :headless and the client in a new terminal
lien repl :connect <port>
heres's a zoom in on my tmux:
+justin@HOST:~$ lein repl :connect 59109
Connecting to nREPL at 127.0.0.1:59109
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_05-b13
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=> (.start (Thread. (fn [] (println "hi"))))
nil
user=>
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
gpg-agent[28197]: a gpg-agent is already running - not starting a new one
+justin@HOST:~$ lein repl :headless
nREPL server started on port 59109 on host 127.0.0.1 -
hi
My question originally stemmed from me trying to connect to a websocket feed. My on-receive function that handles received messages from the feed uses println to display the message. The same thing happens with that. When I connect to the websocket server using the integrated eval, I don't see the received messages in output. When I connect to it in the terminal repl, the received messages are immediately printed in my terminal and continue to print when received.
sounds like someone is getting mucked up in their binding of *out*
if you use a logging library, you can ensure that all its usage goes to the right place
or you can make sure to always start clojure from a terminal, so it's more likely you at least see the misdirected messages, you can always :connect to it from elsewhere
I used a filesystem watcher to trigger reloadsβ¦really not ideal so I hope you find something else useful
Is there a way I can make my lein repl
automatically reload my namespace when changes are made to the file? In my repl, I enter (load "my_folder/core")
and then (use 'my-folder.core :reload-all)
. I call a function, see output, change the function to output something else, save the file, call the function in the repl and see the old output.
@brandon.ringe The :reload
and :reload-all
options only take effect when you do use
and require
(they don't involve filesystem watchers)
@brandon.ringe Having said that, it is certainly possible to build such functionality. For example, ClojureScript has built-in watch capability
https://github.com/clojure/clojurescript/blob/56c774be6227d51f4aa3a52571cb2640c7325db7/src/main/clojure/cljs/closure.clj#L2933
and here is an example of using it to call load-file
when files change http://blog.fikesfarm.com/posts/2015-05-30-poor-mans-figwheel-for-ambly.html