#clojure log - Jul 05 2015

The Joy of Clojure
Main Clojure site
Google Group
List of all logged dates

0:06 justin_smith: rhg135: does the server start asynchronously? if so you may be sending the message before the server is ready to read it...

0:07 rhg135: it might be but I doubt it. Even so this hangs indefinetely after the client connects

0:10 Bronsa: justin_smith: do you ever sleep? I feel like you're ever-present here

0:11 justin_smith: haha

0:11 I sleep very reasonable hours actually, I just wake up earlier and turn in earlier than most

0:12 also I don't have much of a life

0:13 Bronsa: I must sleep on such an odd schedule that probably mine matches your timezone then

0:39 irctc: If I have a long-running function that calls lots of other functions and has side effects (io, db, http requests) what is the best way to handle that? In this case I'll never need to track the results in the main program thread. I tried looking at future but the code never seems to execute.

0:41 amalloy: the code in a future runs as soon as the future is created, so you must have had some other problem that you are ascribing to future

0:46 justin_smith: irctc: remember that you won't see errors inside a failed future until you attempt to deref

0:46 irctc: see stuartsierra's blog post of threads and error handling

0:47 irctc: http://stuartsierra.com/2015/05/27/clojure-uncaught-exceptions

0:50 irctc: I did check it for errors by running it all without future. The situation is that I'm using lein test to make sure it's running ok. I thought maybe the tests stopped before the thread stopped (I wasn't seeing the DB update I was expecting). So I put a Thread/sleep in for several minutes (twice as long as it normally seems to take) but no luck.

0:51 (let [arg "argument"] (future (long-running-func arg)) (println "Check database later!"))

0:51 I assume it's ok to just leave it without being assigned a var for later dereferencing? I won't need that information.

0:53 justin_smith: if you never deref the future, its body should be wrapped in try/catch

0:53 otherwise all failures will be totally silent

0:54 and of course the try/catch should do something verbose in the catch part

0:56 irctc: good idea

0:57 justin_smith: irctc: eg. try (do (future (/ 1 0)) nil)

0:57 in your repl

0:57 doesn't work with the bots here, since they don't let you create futures

0:58 irctc: the do/nil is because even if you don't deref the future, you can see the error state in the printed form, at least as of clj 1.7

1:00 irctc: ok

1:00 thanks

1:03 zacts: clojure is so etymologically complicatedly symbolicmolationally awesome

1:03 justin_smith: complicatedly?

1:03 zacts: there I've said something on this channel that has never been said, probably anywhere on earth, in history

1:03 justin_smith: haha

1:03 ,(java.util.UUID/randomUUID)

1:04 zacts: I'm just trying to do that proof of george carlin

1:04 clojurebot: #uuid "7b94c77b-1b3f-4143-b5fa-7f1f7b45fc31"

1:04 zacts: that the saying "everything has been said before already"

1:04 is logically false

1:04 (I'm just being silly)

1:04 a proof by contradiction

1:04 justin_smith: zacts: clojurebot just said something that probably won't ever be said again (unless it's being quoted), and probably wasn't said before

1:04 zacts: oops

1:04 proof by counterexample

1:04 justin_smith: sure

1:06 zacts: oh and what was that TV show... ugh...

1:06 he did borat

1:06 but also he did this other one

1:06 he would say technologically like technomologically

1:06 I think I just saw a youtube clip of it recently, and it was in my subconcious

1:06 oh well.

1:06 :-)

1:06 laters

2:05 jefelante: is there an alternative to the page object model for using selenium webdriver w/ clojure?

5:47 rritoch: Hi, whats the best way to make a "hybrid" uberjar which only includes select dependencies in the jar?

5:48 Is there some profile that is only applied during uberjar, or something similar I can use.

5:49 Some dependencies are needed during compilation, but not needed in the generated uberjar.

5:57 Nevermind, I figured it out, if I put my compile time dependencies which aren't needed in the uberjar into the dev profile they're excluded from the uberjar.

6:22 retrogradeorbit: hi everyone. I hope someone can point me in the right direction

6:23 Im looking for the right mutable type to use. atom vs ref vs var

6:23 I have two threads

6:23 one is polling some data every 5 seconds and (swap! myatom assoc mytime data)

6:24 the other thread I want to poll occasionally and look at the size of myatom

6:24 amd when it goes over a certain amount

6:24 I want it to swap! the atom with {} and write the old atom to a file

6:24 so I did that, and it works great, but now im thinking

6:24 is an atom right here?

6:25 The swap! fn that writes the file may be called more than once, is that right?

6:25 and if so, the second call may blat the first?

6:25 should I be using refs and dosync here to make sure the file only writes once?

6:26 or is there some nice way to do in an uncorordinated way with atoms?

6:27 rritoch: retrogradeorbit: To start with, if you use an atom or a ref you'll typically keep them in a var either way unless your entire functionality is contained within a single form.

6:28 retrogradeorbit: ok. yeah I have it in a var

6:28 an atom at the moment

6:29 is the atom semantics Compare And Swap?

6:29 rritoch: retrogradeoribit: Your operations on atoms should not have any side effects (Such as writing to files) because they can run multiple times. Considering you just want to perform some action based on your stored value, I suggest you use an atom to get into the habbit of thread safety, even though in this case you only need on thread.

6:29 retrogradeorbit: The reason you only need one thread is that you should be using add-watch, https://clojuredocs.org/clojure.core/add-watch

6:30 retrogradeorbit: aaaaaawwww

6:30 yeah right!

6:30 I didnt think of that

6:30 so I can add a watch to the atom, and do my size comparison in that?

6:30 so the add-watch fn can have side effects?

6:31 is it only run when the swap succeeds?

6:31 rritoch: retrogradeorbit: Let me recheck the atom source code, but I believe the watches are only called when the transaction is successful.

6:32 retrogradeorbit: Yes, the watches are only called when the state of the atom has been successfully updated, so your watches can have side-effects.

6:39 retrogradeorbit: One thing of note based on this code, it looks like watches will block your update until after the watch operation completes. If your watch logic is going to take a long time to run, you may want to use a "future"

6:40 retrogradeorbit: One thing of note based on this code, it looks like watches will block your update until after the watch operation completes. If your watch logic is going to take a long time to run, you may want to use a "future"

6:42 retrogradeorbit: hmm. Im worried about the watcher updating the atom

6:42 it shouldnt take long, and blocking would be fine

6:42 rritoch: retrogradeorbit: Why would the watcher update the atom?

6:42 retrogradeorbit: Im trying to archive off the hashmap when its over a size, and reset it to empty

6:42 so the atom builds and builds

6:43 sitting in ram

6:43 and at a certain size, it gets serialised to disk, and reset to empty to continue

6:43 rritoch: retrogradeorbit: I see, a watch + future would be perfect than

6:43 But the re-update is a problem

6:44 retrogradeorbit: yeah. another update to the atom could happen at any time

6:44 maybe I need coordination with refs?

6:44 rritoch: Here is what I would suggest, and it is mildly complicated.

6:44 retrogradeorbit: Im all ears

6:45 FarzadBekran: guys has anyone been able to get https://github.com/raph-amiard/clojurescript-lua running?

6:45 rritoch: Have your watcher launch a future ONLY when the size of the atom == the max size, because you don't want it running more than once.

6:46 * FarzadBekran needs a clojure to lua compiler

6:46 rritoch: Now, when your future runs, have it only strip off the max number, leaving any others behind, using your typical swap! syntax.

6:46 retrogradeorbit: ah ok. will that free up all the memory?

6:46 if I dissoc a bunch of keys?

6:47 rritoch: retrogradeorbit: If your data is indexed, than the process would be simple, you just deref your atom, grab the last X keys, and then swap! dissoc the keys your archiving.

6:48 retrogradeorbit: yeah its indexed by timestamp

6:48 rritoch: retrogradeorbit: You will probably also want to add a "shutdown" hook to save any remaining data in the atom when your application closes normally.

6:49 retrogradeorbit: That sounds good. How would I go about adding a shutdown hook?

6:49 I suppose I should go read up on clojure and signals

6:50 catch a SIGTERM

6:50 rritoch: retrogradeorbit: Good question, I'm not sure what the clojure way is.

6:50 retrogradeorbit: cool. np.

6:50 rritoch: It's basically the same as the Java way

6:50 See: http://stackoverflow.com/questions/11709639/how-to-catch-ctrlc-in-clojure

6:51 retrogradeorbit: let me try the watcher method first, and then I'll add the shutdown hook

6:51 at the moment Im running it all in cider in emacs anyway

6:51 so when I wrap it into an actual application I'll work on the shutdown

6:52 thanks heaps rritoch. You've been most helpful

6:52 * retrogradeorbit gets coding

7:05 rritoch: retrogradeorbit: One more thing that came to mind, in your future task, be sure to include a loop that repeats for as long as the number of keys is greater than your maximum, otherwise you have a potential bug if it takes longer to write the max entries than it takes to collect the max entries.

7:06 retrogradeorbit: It seems to be a rare case, but it could happen if it's running on a heavily loaded server.

7:14 retrogradeorbit: If you remove the values before you save, it won't be a problem, because a second thread will handle it, but if you remove them after, than you'll need to run again while number is > max.

7:17 * rritoch is procrastinating

8:19 crocket: Is there a decent gitlab/github alternative written in clojure?

8:20 I found an alternative written in scala.

8:20 GitBucket in scala

8:52 deadghost: I kind of feel like learning another language

8:52 what's something that will complement my clojure?

8:55 justin_smith: deadghost: there's a lot to learn in haskell (whether that's a good thing is up to you of course)

8:57 crocket: Ok, I decided to replace GitLab with GitBucket for easier maintenance.

8:58 deadghost: crocket, if it doesn't have to be clojure

8:58 I quite like gogs

8:58 much much lower memory footprint than gitlab

8:59 my needs are simple though

8:59 crocket: deadghost, Gogs vs GitBucket

8:59 GitBucket is also simple.

8:59 deadghost: I just need a git webui that lets me have push to deploy scripts

9:00 crocket: But, GitBucket is not exactly light. It consumes 610MB.

9:00 * wasamasa slaps crocket with a bucket full of bloat

9:00 crocket: While Go is not a terrible language, I think Go is not a good language.

9:01 It feels mediocre.

9:01 Scala manages to be barely better than mediocre, but clojure is elegant.

9:03 deadghost, Its low memory footprint is definitely desirable.

9:04 GitLab is complex, slow, and greedy of memory.

9:04 deadghost: yes

9:04 very much yes

9:04 I used gitlab previously

9:05 crocket: Maintaining GitLab natively on ArchLinux is a nightmare.

9:05 Every upgrade of ruby in ArchLinux breaks GitLab.

9:05 GitLab is fragile.

9:07 rritoch: Does anyone know if *use-context-classloader* is true or false by default, and if it's not true, whats the best way to set it to true from Java?

9:07 crocket: deadghost, Do you run gogs in a docker container?

9:07 deadghost: nope

9:07 justin_smith: rritoch: it's true in my repl.

9:08 1.7

9:09 kitallis: I'm trying to split my defroutes (compojure-api) into multiple files because it's getting large. I now reconcile them in a single file under (defapi) and (swaggered), this seems to want to re-require all the files that each defroutes macro required internally outside as well – is there a way to get around this?

9:09 deadghost: crocket, my gogs shows 23348kb

9:09 crocket: deadghost, If I replace GitLab with either GitBucket or Gogs, I'll be freed of maintenance nightmares.

9:09 deadghost: 0.6% server mem usage

9:09 crocket: Do you use SQLite3?

9:10 deadghost: was that an option for gogs?

9:10 I think I went with a sql

9:10 rritoch: justin_smith: Thanks, I hope that's the default. My loggs say that when the OSGI activator ran the contextClassloader was the system classloader, so hopefuly that's the source of this bug.

9:10 deadghost: yeah I went with postgresql

9:11 justin_smith: kitallis: what do you mean by "this seems to want to re-require" ?

9:11 Deraen: kitallis: You need to use new version (where swaggered is deprecated) and defroutes*

9:13 crocket: deadghost, SQLite3 is the default in gogs.

9:13 You don't need a database server to run gogs.

9:13 Even simpler if you don't need performance.

9:14 deadghost: crocket, I heard they tend to have issues crop up for sqlite in the past

9:14 kitallis: justin_smith, a.clj has dep x and (defroutes* a); b.clj has dep y and (defroutes* b); i refer to both of these in handler.clj as dep a and dep b –– this fails handler.clj as it wants dep x and y as well

9:14 Deraen: kitallis: justin_smith: In old versions swaggered didn't do namespace resolution so if some code referred some var using alias, the route file had to have the same alias even if route was included from other namespace

9:14 kitallis: I'm sorry if that's confusing

9:15 Deraen, I had no idea this was the case

9:15 Deraen: kitallis: What version of compojure-api are you using?

9:15 justin_smith: Deraen: oh, that's a terrible design

9:16 kitallis: I just checked the docs, it is indeed gone, I'm using 0.15.0

9:16 deadghost: crocket, I'm generally satisfied with gogs

9:16 Deraen: kitallis: this was changed in 0.20, that's why docs don't warn about this anymore

9:16 deadghost: until recently development consistently ticked along

9:16 but it's still missing some niceties like deploy keys

9:17 kitallis: I wish compojure-api was just regular functions

9:17 Deraen: justin_smith: Yeah compojure is not exactly designed to be extended, that's by compojure-api does loads of complicated macro magic

9:18 kitallis: Unfortunately it isn't possible without rewriting compojure completely

9:18 kitallis: But we are thinking about writing a bidi adapter for ring-swagger

9:19 crocket: deadghost, It seems GitBucket is easier to set up and upgrade than gogs.

9:19 I can't figure out how to configure gogs.

9:20 deadghost: gogs config was one file

9:20 I don't have xp with gitbucket but w/e works

9:20 Deraen: kitallis: Yada (http://yada.juxt.pro/index.html) already combines bidis data based routing with swagger docs

9:21 deadghost: I used to be on a 2gb mem server so I prioritized mem consumption

9:21 kitallis: yeah, I remember specifically picking compojure-api over bidi last year because of swagger integration

9:22 this should be good.

9:25 crocket: deadghost, Oh, I was just confused at first.

9:25 Gogs generates config files for me.

9:26 And, it uses SQLite3 by default.

9:30 deadghost, One advantage of GitBucket is it has an intergrated SSH server so that you don't have to set up a system user account for ssh access as you have to do with gogs.

9:32 deadghost, Why don't you use SQLite3 for even less memory footprint?

9:32 deadghost: because I heard there were previous problems that cropped up with sqlite3 w/ gogs

9:32 might be resolved now idk

9:32 and 0.3% server mem usage is satisfactory for me

9:33 I don't even remember how much gitlab used

9:33 but it was a lot

9:33 0.5gb-1gb

9:34 crocket, at this point I'd have more memory savings optimizing for my clojure programs

9:34 crocket: deadghost, When did the issue occur? Can you refer me to the issues so that I can check if they are closed by now?

9:35 deadghost, clojure runs on JVM, so you can't save a lot of memory as you do on other natively compiled languages.

9:35 deadghost: crocket, https://github.com/gogits/gogs/issues/805

9:35 >Hmm, SQLite is always the troublemaker.

9:35 that's enough for me to sidestep the issue entirely

9:36 rritoch: Is there any way to get lein uberjar to use the dev profile during the compilation phase? I'm getting errors if I try to "lein uberjar" without running "lein javac" first.

9:36 deadghost: crocket, see I have no idea how jvm memory stuff works

9:37 crocket: deadghost, JVM simply doesn't return memory to OS.

9:37 deadghost: until recently I just let my programs start the jvm with the default

9:37 which iirc is 0.5gb

9:37 crocket: Garbage collection happens in JVM, and JVM doesn't return unused memory space to OS.

9:37 This is a problem if you're optimizing for memory usage.

9:37 deadghost: crocket, right but you can start the jvm to grab less memory initially yeah?

9:38 crocket: deadghost, The best options that I know of are -Xms and -Xmx.

9:38 deadghost: right now I'm also running multiple sites with the same deps in separate jvms

9:39 I suspect I can just dump them in one jvm or something

9:39 crocket: deadghost, Do you mean a servlet container?

9:39 Forget about application servers.

9:39 They are too complex

9:39 deadghost: I was actually going to use wildfly

9:39 since I'm on immutant

9:40 and yes the xml config is complex

9:40 crocket: The true complexity lies in managing classpaths of multiple applications in a single JVM instance.

9:40 This is a mess.

9:41 Instead, you want to make each web application as light as possible.

9:44 deadghost, Is SQLite a trouble in other situations?

9:45 deadghost: crocket, don't know

9:45 but that comment suggests that at some point it was

9:53 rritoch: Anyhow, I'm nearly at "proof of concept" with the clj-osgi-namespaces project. It successfully bundles clojure with an OSGI activator and a bundleListener which facilitates swapping of clojure namespaces. I still need to test it against a real clojure bundle, but it looks like this may work based on my logs.

10:03 jefelante: does anyone have any experience with clj-webdriver and could help with this question? https://groups.google.com/forum/#!topic/clj-webdriver/xJL0fsJG6o0

10:06 crocket: What a pain...

10:12 dstockton: just to get an idea, how many people are using component library to organise their applications?

10:42 crocket: What is a component library?

11:08 justin_smith: crocket: stuartsierra/component

11:08 dstockton: I use it and try to spread the good news

11:14 sveri: +1 on component

11:14 or a similar library

11:23 dstockton: thanks justin_smith and sveri

11:34 digiorgi: I am learning STM and in some point appeared the "write skew" problem, but i can't find in google a text explaining what is that? Did you have some link?

11:35 do*

11:36 weebz: digiorgi: I don't have an answer to your question, but do you have any resources on STM in Clojure or a language other than Haskell? I would like to learn about it, but every resource I've seen explains in Haskell which I'm still not comfortable in

11:39 justin_smith: digiorgi: this ppt explains it pretty well I think https://blog.itu.dk/ET20-F2013/files/2013/04/a5b-relaxed-db-kap-5-countermeasures-against-isolation-anomalies-130330.ppt (opens OK in libreoffice)

11:40 digiorgi: tl;dr most relevant slide "The write skew anomaly is a situation where two transactions both read two different related records. Next, the two transactions each update one of the two related records. Finally, both transactions commit. If a constraint has existed between the two related records, it might have been violated."

11:41 digiorgi: clojure STM transactions handle this by marking not just the data you write in a transaction, but also the data you read, and there is a retry if any of it changes before commit

11:42 classic write skew: "the gift of the magi" by o henry

11:52 digiorgi: justin_smith: thanks!!

12:22 dnolen: Bronsa: ping

12:22 Bronsa: dnolen: pong

12:22 dnolen: Bronsa: in read* why do you use trampoline instead of recur?

12:23 Bronsa: dnolen: we need to recur to target https://github.com/clojure/tools.reader/blob/master/src/main/cljs/cljs/tools/reader.cljs#L832, recur would jump to https://github.com/clojure/tools.reader/blob/master/src/main/cljs/cljs/tools/reader.cljs#L834

12:38 dnolen: Bronsa: k, makes sense, thanks. Thanks doing some more serious profiling of tools.reader via Chrome DevTools and going through the hot spots.

12:41 Bronsa: dnolen: in the jvm version we don't need the trampoline since we use a macro for log-source rather than a function. I guess now that cljs supports macros we could do that aswell?

12:42 dnolen: Bronsa: not sure I understand, why can't use a macro in this case in ClojureScript

12:44 Bronsa: dnolen: IUC using defmacro wasn't an option when andrewmcveigh ported t.r since it couldn't be loaded in cljs

12:44 but I might be wrong

12:45 dnolen: hrm ok I'll take a look at that

12:45 Bronsa: is it because this is a macro that needs to be done at runtime or something?

12:50 Bronsa: dnolen: no, it's just a regular macro https://github.com/clojure/tools.reader/blob/master/src/main/clojure/clojure/tools/reader/reader_types.clj#L344-L351

12:50 dnolen: if bootstrapped cljs can do that, then there's no point in using a function

12:51 dnolen: Bronsa: but I don't understand bootstrapped even matters in this context

12:51 curious as to why the macro was moved into a macro ns as usual

12:51 s/was/was not

12:52 Bronsa: dnolen: probably just a misunderstanding by both me and and andrewmcveigh

12:52 dnolen: Bronsa: bootstrapped ClojureScript isn't going to support putting macros inline anyway

12:52 they always need to be in a macro ns

12:52 Bronsa: dnolen: does it need to be a cljc or something 1.7-only?

12:53 dnolen: Bronsa: it's simpler than that, macro resolution happens by changing the ns

12:54 cljs.core -> cljs.core$macros

12:54 so any ns that wasn't loaded via require-macros will never be used to resolve macros

12:55 Bronsa: dnolen: if changing log-source to be a macro like in the jvm version doesn't *require* 1.7 then I don't see why it couldn't be done

12:56 dnolen: Bronsa: yeah doesn't require 1.7 at all, just a ClojureScript .clj macro file as always

12:57 Bronsa: dnolen: if you're currently hacking on t.r I won't bother changing that myself -- otherwise let me know and I can do it

12:58 dnolen: Bronsa: currently hacking on it

12:59 Bronsa: dnolen: cool then, there should be just two instances where log-source is used, should just need to unwrap the fn & replace the trampoline with a recur

14:05 dnolen: Bronsa: approaching an order of magnitude faster than master https://github.com/swannodette/tools.reader/commits/cljs-bootstrap

15:44 murdd: Trying to port an old java application that was using deprecated internal clojure apis to invoke clojure code.

15:44 One of the things it had to do was change the class loader clojure, as it runs within a class loader itself.

15:45 I can't see how to do this with the new api though.

16:55 xzilend: Hi there, I'm trying to use ring-mock to test a file-upload POST to an API. Does anyone have any examples of how to write this kind of test?

19:11 Matachines: join #clojurescript

19:11 lol tahts embarassing

19:52 fredfe: For anyone using Jenkins on deployment, how do you handle uberjar deployment? How do you send a jar to a server, start it; later, send the next version over, shut down old, start up new?

20:03 justin_smith: fredfe: when I used jenkins we had a shell script to do the restart, and used scp or s3 upload

20:03 (for s3 you then need to ask the server to do the download)

20:04 fredfe: also, jsvc can be used with clojure, and can simplify some of the startup / shutdown / restart stuff

21:13 monsta: How can I flip arguments in clojure?

21:16 justin_smith: monsta: I don't know of a lib that does it, but this gist looks good https://gist.github.com/micmarsh/bcbe19c9de8bb7a471bf

21:20 amalloy: justin_smith: if you are looking for a laugh, check out one of the first clojure libraries i wrote: https://github.com/amalloy/hot-potato

21:21 justin_smith: ;; warning warning unhygenic macro.

21:27 monsta: One more question, how can I use a thread macro inside another thread macro?

21:27 amalloy: monsta: the answer is like...you just use a thread macro inside another thread macro. unless you have a more specific question it's hard to provide a userful answer

21:29 justin_smith: ,(-> 1 list (-> (map inc)))

21:29 clojurebot: #<IllegalArgumentException java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.core$inc>

21:29 monsta: Ok, how a make work something like (->> 3 (-> (/ 4)))?

21:29 justin_smith: oops

21:29 ,(-> 1 list (->> (map inc)))

21:29 clojurebot: (2)

21:29 justin_smith: monsta: you can only do the opposite

21:29 monsta: ok, thanks for the help

21:30 justin_smith: monsta: for obvious reasons, ->> can be inside -> but not visa-versa

21:31 (well, without some other trickery at least)

21:31 Surgo: yeah it's important to remember that macros aren't necessarily composable

22:55 clojer: `lein new reagent app` with advanced optimisation produces a 446Kb file. Are there other settings, gzip aside, which will reduce the size? I notice the file includes a minified React 13.3 but the Reagent code doesn't seem to be minified.

23:05 TimMc: justin_smith: I've been using as-> quite a bit recently.

23:05 justin_smith: yeah, that's a handy one

23:05 TimMc: The only thing I don't like is (let [d ...] (as-> d d ...))

23:06 justin_smith: TimMc: that syntax is like that so nesting inside -> works smoothly

23:07 TimMc: Sure, the part I don't like is the repeated symbol.

23:07 I could use a different symbol as in (let [d ...] (as-> d r ...)) but that feels weird too.

23:08 justin_smith: given how as-> macroexpands, you could just use (as-> ... d ...)

23:08 TimMc: (This comes up when the thing is bound, then I do some conditionals, and then I process it with as-> (so it's not appropriate to inline the initial computation as the first arg to as->)

23:08 )

23:08 justin_smith: ahh

23:10 I don't know your criteria of propriety, but as-> just expands to a big let where every clause is bound to the same symbol

23:10 so depending on your conditionals it might still work

23:11 TimMc: Nah, I usually don't want to execute the as-> at all when the guards fail.

23:12 This might just be my coding style.

23:12 justin_smith: (some-> .... (as-> d ...)) ?

23:12 haha

23:12 yeah, this is clearly a style thing

23:12 TimMc: ~guards

23:12 clojurebot: SEIZE HIM!

23:12 * TimMc tells clojurebot that guards is actually a type of conditional

23:13 justin_smith: but who watches the add-watch?

Logging service provided by n01se.net