#clojure log - Jan 23 2016

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

0:10 tcrawley: justin_smith: stapler: the public key was only used to verify artifacts so you could promote them to the releases repo, which wasn't really used by anyone, so it was removed this week.

0:11 see https://github.com/clojars/clojars-web/issues/415 for details

0:11 where does it reference it in the lein docs?

0:13 justin_smith: tcrawley: when I said "documentation bug" I didn't know the feature was intentionally removed

0:13 that resolves my questions at least

0:22 stapler: tcrawley: interesting. has lein been updated to acommodate?

0:23 accommodate*

0:28 tcrawley: stapler: lein doesn't require your key to be on clojars - all that needs to change on the lein side is the documentation in https://github.com/technomancy/leiningen/blob/stable/doc/GPG.md#clojars

0:28 that entire section is now moot. I'll get that updated

0:29 stapler: It still wants me to put a key in.

0:29 tcrawley: where does it want you to do that?

0:29 stapler: See `lein help gpg` for how to set up gpg.

0:29 If you don't expect people to need to verify the authorship of your jar, you

0:29 can add `:sign-releases false` to the relevant `:deploy-repositories` entry.

0:29 windows by the way

0:30 tcrawley: that's all on the client side - it wants to be able to sign the artifacts before deployment, and doesn't involve the server at all

0:30 stapler: right

0:30 i guess my point is that perhaps the client should be updated to uh

0:30 not require it

0:31 tcrawley: so you either need to not sign the releases, or figure out how to set up gpg on windows

0:31 stapler: i would do it if i was experienced enough to not break everything

0:31 tcrawley: yeah, I have no idea how to do it on win

0:32 stapler: i mean fix up lein

0:32 now about :sign-releases false

0:32 this doesnt go in my project.clj does it?

0:32 tcrawley: updating lein to not require signatures is unrelated to the clojars change, but the value of signing can be debated

0:32 justin_smith: it's a per-project setting, but you can put it in profiles.clj to apply it generally

0:33 stapler: i was under the impression that it didnt take symbols

0:33 and then i tried it and didnt work

0:33 maybe i should try it again :o

0:33 tcrawley: it's really only useful if there is tooling that verifies sigs against your web of trust

0:35 justin_smith: tcrawley: I can see the value of being able to check whether any two jars were signed by the same key - eg. can we check if the jar I am getting now is signed by the same key as the last version

0:35 which only helps so much, but it is something

0:35 tcrawley: true, but there is no automated way to do that currently, it's a manual step

0:35 justin_smith: right, still not an implemented thing

0:36 tcrawley: I still sign all my releases in the hope that someday we'll get this figured out :)

0:36 justin_smith: that's the spirit

0:37 tcrawley: when I get discouraged by security I remind myself of the times I gave up on an api because I found just one more weird thing I had to figure out.

0:37 tcrawley: I hear ya

0:37 justin_smith: point being, just turning away most of the hackers is better than nothing

0:37 heh

0:51 princeso: I just parsed the --help of an app. Then i thought "what if we start making a --help-robot of our apps", some csv would be fine. :D

1:19 devn: feels good when you find some clever code that turns out to be 3x slower than the equivalent simple thing

1:20 a lesson i've learned over and over again: don't get cute with your threads unless you can show it's faster when you write it

1:20 justin_smith: ,((:clever :clever :clever) :clever ((:clever :clever :clever) (:clever :clever :clever) (:clever :clever :clever)))

1:20 clojurebot: :clever

1:22 justin_smith: devn: measurement is king

1:22 devn: im actually a little bit surprised and not surprised at all

1:22 in this particular case

1:23 13 postgres tables, highly variable in size, a function which takes each table, and inserts datascript datoms for each table

1:24 each table was a future, deref'd in a doseq

1:25 difference of about 3.2min to 1.3min

1:26 justin_smith: devn: what kind of db would you use for a collection that has millions of entries, each carrying an association by id to median 100 others

1:27 where you would expect simultaneously heavy read and write loads

1:27 the data is actually small, there is very little there except the relations between items, each carrying an id that is correlated to an api

1:28 I mean the data in each entry is small

1:29 devn: when you say heavy read/write loads, what are you talking about?

1:29 are you trying to do real-time?

1:29 justin_smith: thousands a second

1:29 so not so heavy

1:29 devn: I want my heavy graph analysis to return in under 10 minutes, ideally

1:30 (for each user, ~100 concurrent users during higher load times)

1:31 devn: current issue ends up being everything is really snappy until writes reach a threshold and then suddenly everything takes forever. This is with mongo.

1:31 devn: hm, kind of sad to admit it, but not really my area of expertise

1:31 in light of the last thing we discussed, i would encourage you to measure it :D

1:31 justin_smith: OK, just a shot in the dark, I wonder what forum would be general enough for a question like that :)

1:32 devn: yeah, it would be quite a thing to even make a test case that does that justice, but it's probably worth it for what I'd learn trying...

1:32 devn: I mean, simulating 100 concurrent users ain't no thang

1:32 * justin_smith is off to replace all mongo usage with a protocol....

1:33 justin_smith: devn: one "task" is 4 to 30 minutes worth of api calls and number crunching

1:33 a lot of things to simulate....

1:33 devn: justin_smith: could you get away with modeling just best/worst/average

1:33 justin_smith: devn: I can't just simulate one part of it in isolate, each part performs great in isolation for our current db, it's when they all run and hit the same db that the exponential failure happens

1:33 hmm

1:34 devn: i mean, another thing you could do is record the traffic and play it back

1:34 justin_smith: so I actually need to understand and/or accurately model usage patterns I guess

1:34 oh yeah, isolate db traffic...

1:34 devn: craig andera has a good talk IMO

1:34 on performance testing

1:35 https://www.youtube.com/watch?v=2XDg-C5y0Bc

1:35 justin_smith: looking it up

1:35 oh, cool

1:35 devn: though i'm probably biased because I've had a couple opportunities to work with him

1:35 justin_smith: topic is exactly what I want, but I will watch it when not stoned, thanks

1:36 devn: haha

1:36 justin_smith: it's so sad that I waited until it was legal, haha

1:37 devn: 《〠_〠》

1:38 * devn denies everything

1:39 devn: justin_smith: This is now officially OT, but I am actually heading to Paris->Amsterdam next month. First timer. Going out there to see Avishai Cohen play 3 shows in Paris at the Paris Philharmoic, then to Le Havre to see his trio play.

1:39 justin_smith: nice!

1:39 devn: Philharmonic*

1:40 justin_smith: I went out to see the Jerusalem Quartet play Beethoven and Bartok last night

1:41 devn: Interested to see what Amsterdam holds. I've had good experiences with the dutch so far, expecting good things.

1:41 That sounds pretty rad. I had to admit I haven't listened to much Beethoven or Bartok

1:41 justin_smith: maybe you can meet up with borkdude, some of hte other dutch clojurists

1:41 devn: Whole 'lotta Bach

1:42 I was talking to Philipp Meier, trying to get him to join me. He invited me to stop over to S. Germany, but it's already going to be kind of a packed trip.

1:43 That said, I'm going alone. My fiance was not totally sold on seeing 4 days of Avishai Cohen shows, even if they are all different projects. :D

1:43 A little torn because I'd kind of like someone to hang with here and there, but frankly, I'm looking forward to not being fluent and generally going quiet for a little bit.

1:43 justin_smith: haha, that's how I got my ticket yesterday, friend had tickets to a full 4 concert series and his girlfriend could do 3 :)

1:43 * devn needs a solid break from work to clear his head

1:44 devn: as evidenced by me hacking at ~12:30AM on a Friday night

1:45 (I should note here that there is absolutely 0 expectation that I would do this. I'd probably go a bit further and say it's *dis*couraged.)

1:45 but when you get on a roll...

1:45 justin_smith: hell yeah, ride it as far as it can take you

1:46 devn: i am pretty sure im at my end. feeling pretty satisfied about the performance bump

1:46 justin_smith: something that was huge for us was taking all our uses of futures and wrapping them in usage of claypoole

1:46 devn: justin_smith: do you happen to remember (i could swear someone made one of these at one point) a github project that would analyze your clojure namespaces and build a call graph

1:46 clojurebot: No entiendo

1:46 justin_smith: devn: oh man I want to say that's ztellman...

1:47 stapler: devn: where you at

1:47 devn: i tried out claypoole a couple years ago, along with pulsar/quasar, annddd i think one more?

1:47 justin_smith: devn: I hacked something to do that but it was super super hacky

1:47 stapler: just curious

1:47 devn: stapler: WI

1:47 stapler: since you also seem to be in CST

1:47 devn: ah, IL reporting in c:

1:47 * devn tips his hat

1:47 justin_smith: devn: https://github.com/actsasgeek/lein-gossip

1:47 devn: justin_smith: meh, i think sed + awk might be enough

1:47 stapler: southeastern WI?

1:48 devn: stapler: thereabouts

1:48 stapler: mm

1:48 justin_smith: devn: link above isn't my "super hacky" one btw, it's an actual good one from the looks of it

1:48 stapler: leaving out of ORD?

1:48 devn: stapler: nah, I fly out of MSN

1:48 closer to home

1:49 guarantees me another hop, but there's nothing worse than finding yourself in rush hour traffic after 12 hours of travel

1:49 stapler: ah

1:50 devn: if you do stop over at ORD, frontera grill is really good

1:50 intl terminal

1:50 albondigas torta

1:50 devn: yeah, hit there last time. not cheap! but definitely some of the better airport food i've had

1:50 Minneapolis has some pretty good airport food

1:50 stapler: oof, minneapolis was a bit rough honestly

1:50 devn: I went to a wine bar that had pretty good charcuterie

1:51 stapler: oh no. was it the one with the wine bottles for a chandlier

1:51 chandelier*

1:51 devn: don't know that one

1:51 stapler: so that and it made me shudder. just terrible.

1:51 saw*

1:51 jeez..

1:51 devn: the one thing minneapolis has going for it generally, is that it's actually clean

1:51 stapler: if youre ever back

1:51 travail is really good and pretty affordable for what it is

1:52 devn: in MSP? or ORD?

1:52 stapler: minneapolis

1:52 its in a suburb

1:52 devn: nice. will have to check it out

1:52 justin_smith: last time I flew through ohare I spent my layover taking an el ride to la pasadita on milwaukee to get a burrito for old time's sake

1:52 stapler: it's pretty insane

1:52 devn: this is making me hungry

1:53 stapler: msp has good food

1:53 if youre ever hanging around chicago hit me up c:

1:53 got lots of restaurant recommendations too

1:53 devn: yeah, im in MSN. we have a few really standout places

1:54 a lot of stuff that i'd call "good" but nothing to write home about

1:54 stapler: is there kopps in msn

1:54 devn: not that i know of, but i kind of stick to the downtown area

1:54 stapler: nope, thats milwaukee

1:54 shame

1:54 best custard ive had

1:55 devn: l'etoile in madison is not cheap, but it's certainly _up there_

1:55 justin_smith: portland has amazing variety of unique food options almost to the point it is tiresome and I just have to get something boring eventually

1:56 devn: james beard award winning chef. a friend of mine who just got back from france with his girlfriend said he went out of his way to go to some michelin star restaurants and said they didn't compare

1:56 last time i was in portland i ate at a FANTASTIC place, trying to find it now

1:56 justin_smith: yeah, "FANTASTIC" doesn't narrow it down much lol

1:57 devn: I think this was it: http://www.clydecommon.com/

1:58 stapler: 1am and everything is closed

1:58 justin_smith: nice, that's about 6 blocks from my office, at most

1:58 stapler: except white palace grill i guess

1:58 devn: man, i start reading yelp reviews, and i... dont get it.

1:59 stapler: http://www.honeybutter.com/ this is absolutely fabulous

1:59 gourmet chicken is also really good and a few blocks away

1:59 devn: so much evidence of people who unconsciously convey in their reviews that they are very sure about their place at the center of the universe

3:32 shem: pity that the butter chicken place probably won't deliver to Oulu, Finland...

4:52 montanstercat: consider this: have you ever seen hypah and trump in the same room?/

4:52 mt

4:52 sorry

5:43 oracle123: if I have many vector, and want to put them into a set, but want the equality based on the first element of the vector, is that doable?

5:44 oracle12`: e.g I have [1 2], [1,3],[2,3], and put them into a set, want to get back #{[1,3],[2,3]} which will treat [1 2] as duplication as [1 3]

5:47 luxbock: oracle12`: what should (#{[1 2]} [1 3]) return?

5:50 oracle12`: I need it to return [1 2]

5:50 oracle123: is it doable?

5:51 luxbock: I think you'd need to make your own set type backed up by regular sets

5:52 or you could just make your own versions of the set API functions which might be easier

5:52 for what do you need this for?

5:55 oracle12`: I am pulling data from a web site. There is an id for each product, I used vector to store the id info, and a set of the product to check whether it's already pulled or not, but some info of the product could change and I don't want to pull again even it changed.

5:55 oracle123: so the id should be uniquely identify the product, but other attribute could be different.

5:56 oracle12`: Maybe I shold model it as a hash.

5:59 luxbock: couldn't you just create a set of the ids like (into #{} (map first) data) and use that to do the same thing?

6:01 oracle12`: yes, it works. thanks very much

6:03 luxbock: :)

8:11 tomphp: hi, can anyone help me with a stupid problem? - I'm trying to use figwheel and I've added :figwheel {:ring-handler myproject.core/handler} to my project.clj and I'm getting java.lang.ClassNotFoundException: myproject.core

8:11 myproject.core is definitely right as it's used in :main - I'm sure sure it's just something silly but I can't work it out

8:14 scrap that - paren in the wrong place (what else could it be). 20mins of head scratching, ask here, solve it straight away - typical ;-)

8:24 justin_smith: ,(sorted-set-by #(compare (first %) (first %2)) [1 2 3] [1 2] [2 3 4] [2 4]) ; oracle123 is gone, but this is a possible answer

8:24 clojurebot: #{[1 2 3] [2 3 4]}

9:13 gfredericks: ,(let [[a :as [b :as [c :as [d]]]] (range)] [a b c d])

9:13 clojurebot: [0 0 0 0]

9:13 gfredericks: #protip

9:37 spuz: gfredericks, what exactly is going on there?

9:38 gfredericks: spuz: the purpose of :as is to let you give a name to the whole value being destructured

9:38 but you can also use it to destructure the same value again

9:38 so I just destructured the same value four times

9:39 spuz: gfredericks, isn't there a nicer way to do that?

9:39 like (let [[a:b:c:d] (range)] [a b c d]) ?

9:40 gfredericks: you mean

9:40 ,(let [[a b c d] (range)] [a b c d])

9:40 or something different?

9:40 clojurebot: [0 1 2 3]

9:40 spuz: that's what I meant yeah

9:40 gfredericks: well that code clearly does something different

9:40 spuz: (am new to clojure today)

9:40 gfredericks: and my code isn't meant to be nice, it's meant to be ridiculous

9:41 spuz: heh

9:42 gfredericks, right i get it

9:42 I think yours is equivalent to

9:42 ,(let [[a] (range)] [a, a, a, a])

9:42 clojurebot: [0 0 0 0]

9:42 spuz: woot

9:42 parsing skills ++

9:45 gfredericks: yep

9:52 spuz: what is the difference between :while and :when in the (for) function?

9:52 justin_smith: spuz: while takes everything until condition is not met

9:53 when selects only items for which that condition is met

9:53 spuz: thanks

9:53 but why is that not documented here? https://clojuredocs.org/clojure.core/for

9:59 kwladyka: spuz mmm i see it is

9:59 Supported modifiers are: :let [binding-form expr ...],

9:59 :while test, :when test.

10:00 and their are examples bellow

10:00 spuz: kwladyka, i mean, why is the difference between when and while not given in the official doc string of the fuction

10:01 kwladyka: spuz beacuse it is quite obviously if you know better Clojure. There is function when and while - look at them.

10:01 spuz: kwladyka, ok thanks

10:02 kwladyka: there is also "let" function

10:03 and also when you see when-let function you can expect is work similar to let function and when function

10:06 spuz i can also share with you this 2 link "must to read" https://gist.github.com/john2x/e1dca953548bfdfb9844 https://github.com/bbatsov/clojure-style-guide

10:07 spuz: kwladyka, great thanks

12:59 I'm struggling with Cursive's structural editing. How do I wrap a selection in parens?

13:05 benjyz1: hello. I'm trying to seq'ing over a map like this https://www.refheap.com/113981

13:05 ,(def clients (atom {}))

13:05 clojurebot: #'sandbox/clients

13:06 benjyz1: ,(swap! clients conj "abc")

13:06 clojurebot: #error {\n :cause "java.lang.Character cannot be cast to java.util.Map$Entry"\n :via\n [{:type java.lang.ClassCastException\n :message "java.lang.Character cannot be cast to java.util.Map$Entry"\n :at [clojure.lang.APersistentMap cons "APersistentMap.java" 42]}]\n :trace\n [[clojure.lang.APersistentMap cons "APersistentMap.java" 42]\n [clojure.lang.RT conj "RT.java" 652]\n [clojure.core$conj...

13:06 benjyz1: ,(swap! clients conj {:name "abc"})

13:06 clojurebot: {:name "abc"}

13:06 benjyz1: ,(doseq [a @clients] (println a))

13:06 clojurebot: [:name abc]\n

13:07 ridcully_: this error message looks rather unrelated to the code?

13:08 could it be from something else? tooling maybe?

13:08 benjyz1: I think I misused swap

13:08 ,(swap! clients conj client)

13:08 clojurebot: #error {\n :cause "Unable to resolve symbol: client in this context"\n :via\n [{:type clojure.lang.Compiler$CompilerException\n :message "java.lang.RuntimeException: Unable to resolve symbol: client in this context, compiling:(NO_SOURCE_PATH:0:0)"\n :at [clojure.lang.Compiler analyze "Compiler.java" 6688]}\n {:type java.lang.RuntimeException\n :message "Unable to resolve symbol: client in t...

13:08 ridcully_: ,(def clients (atom {}))

13:08 clojurebot: #'sandbox/clients

13:09 ridcully_: this is another error. the error you got the first time, is the "correct" one

13:09 clojurebot forgets all it knows every n minutes

13:52 pilne: so... a big part of optimizing clojure for numeric stuff is using let to force the use of primatives? that's smeggin sweet and easy (:

13:57 Kamuela: pilne: can you explain that to a Clojure noob? What about let causes the use of primitives?

13:58 pilne: part 1 would be that it saves the runtime a lot of time, clojure being dynamic, but the jvm being static, means that without explicitly "let"ting the variable as a double, it has to figure out that it is using doubles (taking more time).

13:59 and primatives vs objects is a stack/heap memory location optimization

14:00 in java, double is the primative, and Double is an object wrapper around that primative so 'everything (can be) is an object'

14:00 just like in java, it will be faster to explicitly use the primative, same goes with clojure (:

14:01 sorry if that was a craptastic explanation, i'm about 6 months back into a "programming renaisannce" after being mostly a python hack for silly shit on my computer (and a wow-addiction) for most of my life after college.

14:06 Kamuela: No that makes sense I think you're just making me realize I missed the part of let that has you using explicit types

14:07 pilne: https://clojurefun.wordpress.com/2012/08/06/clojure-performance-tip-exploiting-primitive-casts-20/

14:09 Kamuela: Interesting. I haven't encountered casts yet

14:10 pilne: i google too much at work since i can't code much >.< so i end up with all sorts of reading that is way above my head, but i like that generally there *is* a good answer to even the most nit-picky "complaints" i've found about clojure (:

14:11 and the community is awesome and very motivated to make clojure awesome (:

14:11 now if only i can get comfy with emacs...

14:12 benjyz1: has anyone done work in Clojure and P2P systems?

14:20 qsys: if I have a vector of maps (each only 1 key-value) [{:a "val1"} {:b "val2"} {:c "val3"} {:a 7}] and I want to merge/reduce the maps 'into' {:a ("val1" 7) :b "val2" :c "val3"}... obviously, into doesn't work (duplicate :a key)...

14:20 ,(def v [{:a "v1"} {:b "v2"} {:a 7}])

14:20 clojurebot: #'sandbox/v

14:20 qsys: (require '[core.match :refer [match]])

14:20 ,(require '[core.match :refer [match]])

14:21 clojurebot: #error {\n :cause "Could not locate core/match__init.class or core/match.clj on classpath."\n :via\n [{:type java.io.FileNotFoundException\n :message "Could not locate core/match__init.class or core/match.clj on classpath."\n :at [clojure.lang.RT load "RT.java" 456]}]\n :trace\n [[clojure.lang.RT load "RT.java" 456]\n [clojure.lang.RT load "RT.java" 419]\n [clojure.core$load$fn__5677 invoke ...

14:21 qsys: :p

14:22 ,(->> v (map first) (reduce (fn [m [k v]] (update-in m [k] #(cons v %))) {})))

14:22 clojurebot: {:a (7 "v1"), :b ("v2")}

14:25 pilne: hmmm

14:26 qsys: ,(apply merge-with #(match %1 [& _] (conj (keyword %1) %2) :else [(keyword %1) %2] v)

14:26 clojurebot: #<RuntimeException java.lang.RuntimeException: EOF while reading>

14:26 qsys: ,(apply merge-with #(match %1 [& _] (conj (keyword %1) %2) :else [(keyword %1) %2]) v)

14:26 clojurebot: #error {\n :cause "Unable to resolve symbol: match in this context"\n :via\n [{:type clojure.lang.Compiler$CompilerException\n :message "java.lang.RuntimeException: Unable to resolve symbol: match in this context, compiling:(NO_SOURCE_PATH:0:0)"\n :at [clojure.lang.Compiler analyze "Compiler.java" 6688]}\n {:type java.lang.RuntimeException\n :message "Unable to resolve symbol: match in this...

14:27 qsys: ah, damn'd, match... whatever, got it

14:28 spuz: ,(= "foo" (map (fn [x] x) "foo"))

14:28 clojurebot: false

14:28 spuz: why does that not return true ^

14:30 qsys: ,(map (fn [x] x) foo)

14:30 clojurebot: #error {\n :cause "Unable to resolve symbol: foo in this context"\n :via\n [{:type clojure.lang.Compiler$CompilerException\n :message "java.lang.RuntimeException: Unable to resolve symbol: foo in this context, compiling:(NO_SOURCE_PATH:0:0)"\n :at [clojure.lang.Compiler analyze "Compiler.java" 6688]}\n {:type java.lang.RuntimeException\n :message "Unable to resolve symbol: foo in this conte...

14:30 qsys: ,(map (fn [x] x) "foo")

14:30 clojurebot: (\f \o \o)

14:31 qsys: (= "foo" (\f \o \o))

14:31 ,(= "foo" (\f \o \o))

14:31 clojurebot: #error {\n :cause "java.lang.Character cannot be cast to clojure.lang.IFn"\n :via\n [{:type java.lang.ClassCastException\n :message "java.lang.Character cannot be cast to clojure.lang.IFn"\n :at [sandbox$eval76 invokeStatic "NO_SOURCE_FILE" 0]}]\n :trace\n [[sandbox$eval76 invokeStatic "NO_SOURCE_FILE" 0]\n [sandbox$eval76 invoke "NO_SOURCE_FILE" -1]\n [clojure.lang.Compiler eval "Compiler.j...

14:31 qsys: ,(= "foo" '(\f \o \o))

14:31 clojurebot: false

14:31 qsys: that's why :)

14:31 a string is a collection of 'characters'

14:33 map on a string takes one character at a time...

14:33 spuz: qsys, if a string is a list of characters then why does "foo" not equal '(\f \o \o)?

14:34 amalloy: it's not really

14:34 kwladyka: pilne this performance is because of reflections

14:34 amalloy: it's just something you can treat as a list of characters

14:34 qsys: it's not really a 'list' of chars... I suppose? :p

14:35 spuz: so it's a collection, not a list?

14:35 kwladyka: pilne if you are interesting in this topic you can read more about that here http://clojure.wladyka.eu/#avoid_reflections

14:36 qsys: a string is certainly not a list of chars

14:38 I'm rather sure it's a seq, though... (which is not a list, but can be threated as one, if I'm not mistaken)

14:39 spuz: qsys, ok thanks

14:39 what is the best way to map over a string character by character?

14:39 qsys: oh, damn'd, still looking for the right answer :p

14:40 well, use 'map', that's character by character?

14:40 spuz: the best I've found so far is (apply str '(\f \o \o))

14:41 pilne: ty kwladyka

14:41 qsys: ,(map #(str "done: " %) "ab")

14:41 clojurebot: ("done: a" "done: b")

14:42 qsys: takes a first, than b

14:43 kwladyka: ,(seq "foo")

14:43 clojurebot: (\f \o \o)

14:43 qsys: from: https://www.safaribooksonline.com/library/view/clojure-cookbook/9781449366384/ch01.html:

14:43 Because Clojure strings are sequences under the covers, you may substitute a string anywhere a collection is expected. When you do so, the string will be interpreted as a collection of characters.

14:44 spuz: "More often than not, after you’ve done some work on the characters within a string, you’ll want to transform that collection back into a string. Use apply with str on a collection of characters to collapse them into a string"

14:44 ok that answers my question

14:44 thanks qsys !

14:45 kwladyka: but the point in Clojure is what you want to do with the data. In most cases you want use map, because you have some intention to do with this data.

14:45 qsys: my pleasure :)

14:48 TimMc: qsys: This is wrong: "Clojure strings are sequences under the covers"

14:49 Rather, a Clojure string is a Java String, nothing more, nothing less.

14:49 qsys: alright... it's just from the book :p

14:49 TimMc: Well, the book is wrong. :-)

14:49 qsys: I'm fine with that, but can we treat a string as a 'sequence of chars'?

14:50 TimMc: You can make a seq that is backed by a string.

14:50 clojure.core/seq special-cases strings so that they can participate in the sequence abstraction.

14:51 kwladyka: I think TimMc has right from technical point of view and book saying how you can think about string in Clojure to use it :)

14:51 TimMc: Hmm... you can *use* a string as a collection of chars, but it isn't backed by a seq.

14:52 qsys: I'm interested in both... so next time, I can give a more accurate answer... yeah

14:52 kwladyka: So it should be "Clojure strings are Java Strings under the cover of sequences" :P

14:52 qsys: ,(seq? "ab")

14:52 clojurebot: false

14:52 qsys: ,(sequential? "ab")

14:52 TimMc: kwladyka: Heh, sure. :-)

14:52 clojurebot: false

14:52 TimMc: This is also wrong: "Clojure strings are backed by Java’s UTF-16 strings."

14:52 just s/backed by // and it's right

14:53 qsys: ,(coll? "ab")

14:53 clojurebot: false

14:53 TimMc: So... grain of salt with that section, and maybe that book.

14:53 qsys: it's all false :) - so well, they're not seq, sequential or collections

14:53 but when used with 'map', they are treated as seqs, or something... funny :)

14:53 TimMc: map calls seq, and seq special-cases strings

14:53 kwladyka: qsys technically ther are Java string, but you can use them as sequences

14:54 qsys: I'd say I'm confused, but I feel more 'enlightend' :)

14:55 *enlightened

14:56 kwladyka: Clojure is intelligent and react for data types

14:56 TimMc: No, you can't use a string as a sequence.

14:57 ,(class "foo")

14:57 clojurebot: java.lang.String

14:57 TimMc: ,(class (seq "foo"))

14:57 clojurebot: clojure.lang.StringSeq

14:57 kwladyka: technically yes, but if you use it in for example "map" it will work the same as (seq "foo") even if you write only "foo"

14:58 TimMc: One of those is a string, the other is a sequence.

14:58 You can create a seq *of* a string.

14:58 I'm not just being pedantic here, there are times when the distinction between a collection and a sequence over the collection becomes relevant.

14:59 However, most core functions that deal with sequences call seq on their argument so that you can pretend there's no distinction.

15:00 qsys: so, where is the string 'foo' turned into a (seq "foo") when we do (map #(println %) "foo")

15:00 ah, in the source of map, of course :)...

15:01 TimMc: yes https://github.com/clojure/clojure/blob/clojure-1.7.0/src/clj/clojure/core.clj#L2616

15:01 qsys: ,(source map)

15:01 clojurebot: Source not found\n

15:02 qsys: there it is the first time: (when-let [s (seq coll)]

15:02 even more enlightened, a bit more and I'm Buddha :)

15:02 TimMc: heh

15:12 qsys: You may find this useful: https://www.brainonfire.net/files/seqs-and-colls/main.html

15:13 qsys: yeah, it's on my list to 'read it again, and again, and again', so I'd never forget. And I always do :p.

15:38 kwladyka: is any shortcut to create test file in Intellij for Clojure? I am doing this creating file manually, but maybe is it faster way?

15:39 pilne: not sure, and at my last glance, the only clojure plugin for intellij isn't free, 30 day trial, yes, but free no >.< lein, atom, and a terminal window suffice me enough as of now.

15:41 kwladyka: pilne intellij + cursive is really good. I recommend it :)

15:41 and learn use emacs only to back to use intellij much better :)

15:41 pilne: i will keep that in mind, because for java, intellij is my favorite option.

15:42 but i've actually been a vim guy most of my life >.< lol

15:43 kwladyka: world is subjective :)

15:44 vim and emacs is so cool when you have to work in terminal, but if you don't have too... windows are very comfortable and have the same power if you use shortcuts - it is my personal opinion

15:44 pilne: true, i try to remain editor neutra/minimal but when I work with java i feel like a basic editor doesn't make it "as easy as it should be" lol.

15:45 kwladyka: i feel much better using intellij as emacs instead of using emacs :)

15:45 pilne: yeah, i use a lot of shortcuts regardless of the environment i'm in, blows my coworker's minds that i barely touch the mouse sometimes lol (i don't work in a tech-y job)

15:46 and my only thoughts on windows, mac, linux, etc, is "i like the jvm because i can target them all (relatively) easily" lmao

15:47 i'm a linux (ok, ubuntu guy who likes arch, just not good enough for it lol) guy, gf is a windows 7 gal (who knows her days are numbered in terms of official support), her family is macs, my family is mostly apple... best part is, everyone is satisfied with where they are LMAO.

15:51 kwladyka: i was fun in order: windows, linux, windows again, now mac - long story and a lot of experience to change my mind :)

15:52 pilne: (: same here

15:52 engblom: This is super fast in repl (about 0.5 ms): (map write-value pins values) write-value got a side-effect. When wrapping it with (seq ...) for the time goes up to 1.5ms. How should I get the same fast result as without any wrapping, but still make sure it will run even if the returned value is never used?

15:53 (doseq ...) is taking 4-5 ms seconds for the same task

15:53 justin_smith: engblom: if you don't need a return value, use run!

15:53 it is like the two arg version of map but is not lazy and returns nil

15:54 ,(doc "run!")

15:54 clojurebot: #error {\n :cause "java.lang.String cannot be cast to clojure.lang.Symbol"\n :via\n [{:type java.lang.ClassCastException\n :message "java.lang.String cannot be cast to clojure.lang.Symbol"\n :at [clojure.core$ns_resolve invokeStatic "core.clj" 4239]}]\n :trace\n [[clojure.core$ns_resolve invokeStatic "core.clj" 4239]\n [clojure.core$ns_resolve invokeStatic "core.clj" 4229]\n [clojure.core$re...

15:54 justin_smith: ,(doc run!)

15:54 clojurebot: "([proc coll]); Runs the supplied procedure (via reduce), for purposes of side effects, on successive items in the collection. Returns nil"

15:55 engblom: In this case I have two collections and one function

15:56 justin_smith: engblom: so you are calling (map f c1 c2) ?

15:56 engblom: justin_smith: Yes

15:57 And f got side-effect

15:57 I would prefer the function to return the result also, but most of the time the result will be discarded

15:57 justin_smith: right, and run! only takes a single collection arg, and doseq forces you to create collections and call apply

15:57 engblom: Is mapv also lazy as map?

15:58 Ah, I tried mapv, it is really slow

15:58 luma: no, vectors aren't lazy

15:58 engblom: 4.5ms

15:59 spuz: kwladyka, can i ask you some questions about cursive?

15:59 kwladyka: spuz just ask :)

15:59 spuz: are you using structural editing mode?

15:59 kwladyka: spuz yes

15:59 justin_smith: engblom: I would go with (doall (map f c1 c2))

15:59 qsys: spuz yes

16:00 spuz: how do you wrap a selection with parens?

16:00 qsys: spuz vim mode

16:00 engblom: justin_smith: doall takes 1.5ms compared with 0.5ms for REPL to do both side effect and returning the new vector.

16:00 kwladyka: on the beginning it was irritating, but if you learn shortcuts it is extremely powerful and you wouldn't use anything else :)

16:00 engblom: What is REPL using for forcing the evaluation of the sequence)

16:00 justin_smith: spuz: create parens to the left of the thing with "(", then slurp the righthand element (control-shift-rightarrow here)

16:01 pilne: is that on a fresh non-cached jvm?

16:01 engblom: justin_smith: Sorry, 3ms doall takes..

16:01 kwladyka: spuz go to intellij shortcuts edition preferences -> keymap -> clojure keybinding

16:02 justin_smith: engblom: the repl only forces the result via the printing side effect, but that won't show in the output of "time" because it happens after the function returns

16:02 kwladyka: spuz you can choose the ready map there and apply to shortcuts and set your own

16:02 justin_smith: engblom: so your "time" is not really measuring how long map takes, because map is lazy

16:02 spuz: justin_smith, ctrl-shift-rightarrow just changes the selection, it doesn't change the structure

16:02 justin_smith: engblom: doall is showing you how long map really takes

16:03 spuz: clearly you have different bindings

16:03 kwladyka: spuz *you can choose the ready map of shortcuts there and apply or set your own shorcuts

16:03 spuz: kwladyka, what do you mean by 'choose the ready map'?

16:03 pilne: you need to find the "slurp" clojure/vim mode binding and see what that is on to add parens the way justin_smith suggested

16:03 kwladyka: spuz you have there "binding set" list, choose one and you will see

16:04 justin_smith: ,(time (map #(Thread/sleep %) [1000 2000 3000]))

16:04 engblom: see the above

16:04 clojurebot: "Elapsed time: 0.109709 msecs"\n(nil nil nil)

16:04 justin_smith: engblom: it says "elapsed 0.10... ms"

16:04 TEttinger: do you have access to Thread?

16:05 justin_smith: engblom: we both know that's bullshit :)

16:05 engblom: justin_smith: Thanks! That explains

16:05 kwladyka: spuz i am using bindings for mac with my little changes, but it is your preference and it depend on your system

16:05 engblom: justin_smith: Then the fastest option in my tests so far seem to be (seq ...)

16:05 justin_smith: TEttinger: it didn't complain about Thread/sleep...

16:05 TEttinger: I had some lag

16:05 justin_smith: engblom: doall and seq should be the same

16:05 TEttinger: ahh, OK :)

16:06 engblom: justin_smith: (seq ...) seem to be faster than (doall ...) for me.

16:06 justin_smith: that's weird!

16:06 kwladyka: spuz you definitely need set "slurp forwards" and "barf forwards"

16:07 pilne: ahhh lisps, the only place it is ok to talk about slurping and barfing in mixed company >.<

16:07 kwladyka: spuz also look on cursive doc how to use cursive. They are great gifs explain everything.

16:07 spuz: the docs are not great

16:07 justin_smith: engblom: seq and doall are really close in my repl, but the real way to know a microbenchmark like this is to use criterium

16:07 stapler: do clojurians lean towards tabs or spaces?

16:08 justin_smith: spaces

16:08 spuz: https://cursive-ide.com/userguide/paredit.html

16:08 kwladyka: stapler https://github.com/bbatsov/clojure-style-guide

16:09 spuz: maybe it is because I'm using the intellij plugin and not the standalone ide but the menus are not the same as described by the docs

16:09 kwladyka: spuz yes docs are great :) just you have to connect description with intellij shortcuts. In shortcuts you have searcher so it is easy.

16:09 spuz: For example, I can't find this setting: Settings→Editor→Smart Keys→Surround selection on typing quote or brace

16:11 also the gifs don't show when a keyboard-shortcut is pressed

16:11 kwladyka: spuz open preferences, you have search textbox there

16:11 just write there smart keys

16:11 spuz: as far as I know the user just whispered a magic command and something magic happened

16:12 kwladyka: spuz don't worry, you will fast understand it. You can try emacs course and after that you will know how to use intellij

16:12 spuz: kwladyka, ah thanks

16:13 kwladyka, i've used intellij for a long time

16:13 ridcully_: spuz: ctrl-shift-a for search in the options/actions

16:13 kwladyka: spuz me too, but learn how to use emacs bring me to new level :)

16:13 of using intellij

16:13 spuz: are they not completely different?

16:14 kwladyka: spuz just if you start use intellij as emacs... it is very powerful.

16:14 You wouldn't touch the mouse and you will edit everything easier and simpler

16:14 spuz: what features do they share?

16:15 kwladyka: spuz just try, you will see it very fast. But of course it is my own experience.

16:16 pilne: http://www.braveclojure.com/basic-emacs/

16:16 has a good "quick and dirty" setup /runthrough for clojure and emacs

16:18 kwladyka: pilne why "pilne" ? :) In my language it means something is very important and you have to do it fast :)

16:18 pilne: just some random combinations of math stuff lol pi and ln_sub_e

16:19 i don't do much fast... other than type, i'm a cranky old man (:

16:40 elvis4526: Let's say I have a lazy list of 10000 hash structured like this {:id 1}, {:id 2}, {:id 3} ... until {:id 10000}

16:41 if I do (filter #(= (:id %) 10000) list-of-hashes)

16:42 it will still have to go through all the hashes right ?

16:42 justin_smith: elvis4526: right

16:43 elvis4526: k

16:44 how is the same thing possible with an infinite sequence returned by (range) then ?

16:45 If filter would have to go through all the hashes, it would never terminate

16:47 ,(first (filter #(= % 10) (range 10 0 0)))

16:47 clojurebot: 10

16:48 ridcully_: ,(first (filter #{10000} (range)))

16:48 clojurebot: 10000

16:49 elvis4526: ridcully_: is this a new syntax for filter ?

16:49 I thought #{} were reserved for sets

16:49 luma: that is a set

16:49 ridcully_: sets are functions too

16:49 e.g. like keywords

16:49 luma: #{10000} is a set that has one element, 10000

16:50 elvis4526: sweet I didn't know about this - thanks

16:52 justin_smith: elvis4526: filter won't return if it never finds the item, but it can stop when it finds the item

16:52 ,(filter #(= % Double/NaN) (range))

16:53 clojurebot: eval service is offline

17:49 domokato: Is anyone here using neko? I discovered a bug you might want to know about https://github.com/clojure-android/neko/issues/59

18:09 engblom: Why Clojure almost double as slow as BASH in this example: http://pastebin.com/GQyrmSZq ?

18:09 Both measure only the time taken to write to files

18:09 justin_smith: how are you starting the clojure process?

18:10 engblom: justin_smith: I made uberjar, and run it by java -jar ...

18:10 justin_smith: I do not measure the time for starting it up, only the actual time it is doing something

18:11 justin_smith: interesting, yeah

18:11 I wonder what extra thing clojure is doing there that bash doesn't

18:11 engblom: this is probably irrelevant, but bash is sending newlines and clojure is not

18:12 it would be equivalent if you used echo -n or if you added a newline to what you spit

18:12 engblom: justin_smith: I could test with newline...

18:12 justin_smith: another difference is that in bash, the thing after > is always a file

18:13 with spit, it has to figure out what to do with the first arg (could be a file, could be an outputstream, etc.)

18:13 could be a file object, or a name of a file to open

18:15 engblom: Even with newline, BASH is winning big. Bash does take 3.26s while Clojure takes 5.9s

18:16 justin_smith: engblom: try doing (let [pin (java.io.File. "/sys/class/gpio18/value")] (time (dotimes [_ 10000] (spit pin "1\n") (spit pin "0\n"))))

18:16 hmm... maybe spit closes the output though

18:18 no, it doesn't, so that should work

18:21 engblom: justin_smith: It reduced the time to 4.6s, but still not as good as Bash

18:22 I wonder what I could do to get it faster. I would need to get it faster to get stepper motors to work well

18:22 justin_smith: hmm

18:23 I bet using .write on an OutputStream that comes from opening the file would be much faster than spit

18:26 engblom: justin_smith: spit seem to already use .write

18:26 ,(source spit)

18:27 clojurebot: Source not found\n

18:27 justin_smith: so it would be like (let [pin (clojure.java.io/output-stream (java.io.File. "..."))] (doseq ... (.write pin (.getBytes "0\n")) (.flush pin))))

18:27 engblom: but it's creating a new writer each time, the above can reuse an output stream

18:29 engblom: justin_smith: But the bash equivalent does not keep an file open...

18:29 justin_smith: I will try the code and see what happens.

18:29 justin_smith: engblom: OK, why does that matter? clojure probably can't do things as simply as bash.

18:36 engblom: justin_smith: Thanks, the last thing really made it fast. Now it takes 0.2s

18:36 justin_smith: kickass!

18:36 engblom: It beats bash by far

18:36 justin_smith: engblom: it makes sense, if you keep using the same file, why not just keep outputting and not re-opening over and over

18:37 engblom: But it will also force me to make quite big changes to my library if I will need to have those output-streams open all the time...

18:37 justin_smith: engblom: you could use core.async and do channel sends

18:37 and (and then have that outputstream open in a go block where you get those etc.)

18:38 engblom: Is it possible to have both an output- and an input-stream at the same time to one single file=

18:38 ?

18:38 justin_smith: or even just a future with a loop, reading o na queue

18:38 engblom: should be possible, yeah

18:39 pilne: i wonder if the BEAM VM would lend itself better or worse to clojure...

18:41 stapler: clojure is pretty dope

18:41 justin_smith: engblom: another option actually, if you are just doing an app and not a reusable lib, is to just have a top level def for the outputstream, then a function that writes to it

18:41 stapler: started working on stuff yesterday

18:42 pilne: welcome to the powerful addiction that is a well-done lisp (:

18:55 engblom: justin_smith: If you look at https://github.com/engblom/gpio/blob/master/src/gpio/core.clj , do you have any advice for how I should keep track of the output streams? Should I have a ref containing a map between pin number and the stream? I would like both the input and the ouput stream to be created upon running (open-pin ...)

18:57 justin_smith: engblom: how about a hash-map from number to input stream, and another from number to output-stream

18:57 each can be either in a def, or an atom inside a def

18:59 engblom: also, seq will force some of a result, but not the whole thing

18:59 engblom: (do (seq (map println (range 100))) nil) - only prints 0-31 then stops

19:00 doall is the correct function

19:02 engblom: justin_smith: I had forgotten to push the latest changes. I actually have doall there. It should be updated at github now

19:02 justin_smith: also (into [] (map ...)) is (mapv ...)

19:46 engblom: justin_smith: Should an input- or an output-stream get closed in some special way, or is it enough that there is no reference anymore to it?

19:46 justin_smith: engblom: you can calle .close on it

19:46 but if there are no references, the finalizer will... eventually close it

19:46 but if you know you are done with it, you can totally use .close

19:47 engblom: justin_smith: Ok, thanks!

19:52 Kamuela: What's a large diffeeeence between Clojure and its script counterpart?

19:53 WickedShell: Kamuela, runtime enviorment really. Clojure is meant for the JVM (there are other ports but JVM is the main target) clojurescript generates JS to be run on a browser or other JS interpreter

19:53 Luyt: Kamuela: ClojureScript gets compiled into JavaScript; Clojure compiles into JVM bytecode.

19:56 Kamuela: In terms of like the limitations of each, I mean. I'm guessing library support obviously changes but is the core identical?

20:04 rhg135: mostly, a few things differ.

20:04 the format function not existing was rather surprising

20:17 engblom: justin_smith: Are you still around?

20:18 justin_smith: yes, what's up?

20:20 engblom: justin_smith: All worked well in my benchmark, but now when I got the streams stored in a map, I end up in trouble. Please have a look at this simple function: http://pastebin.com/Cn0mpusf

20:20 pilne: hrm, i think i've found a personal project for my downtime at work.... converting this object-oriented code for a simulator of the mechanical workings of the "enigma machine" (i.e. it has the wheels as objects, with all sorts of state and such...)

20:21 justin_smith: engblom: seems OK...

20:21 engblom: justin_smith: Still, running that function does not change the value...

20:23 justin_smith: Sorry that I distrurbed you with this... It was my own fault. I had not been setting the direction of the pin. I should probably go to sleep. It is 03:23 am here

20:24 justin_smith: well, at least you figured it out

20:24 engblom: I would just want to do the same for the read function now. What is the "inverse" of (.getBytes) ?

20:25 justin_smith: ,(String. (.getBytes "OK"))

20:25 clojurebot: "OK"

20:25 engblom: Thanks!

20:26 justin_smith: ,(apply str (.getBytes "OK")) ; gotcha!

20:26 clojurebot: "7975"

20:29 engblom: IllegalArgumentException No matching ctor found for class java.lang.String clojure.lang.Reflector.invokeConstructor (Reflector.java:183)

20:30 pilne: hmmmm, i think all i need to do, is represent the "state" of the enigma machine as the only "mutable", then pass that data structure, and the input key to a function, in order to both generate a new machine state, and the output!

20:30 engblom: I am trying (String. (.read (get @input-streams pin)))

20:31 justin_smith: engblom: and is .read returning a byte-array? I thought it returned the number of bytes read

20:31 clojurebot: Ok.

20:31 justin_smith: engblom: .read with only the stream as arg just returns a byte

20:31 https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html

20:32 you probably want to use the version that takes a byte-array as argument

20:34 pilne: is there a way to check a vector for a given element and return its index?

20:34 justin_smith: pilne: .indexOf

20:34 pilne: beautiful

20:34 justin_smith: ,(.indexOf [:a :b :c] :b)

20:34 clojurebot: 1

20:35 pilne: nice... that makes the fact there is a reflector on the enigma machine even easier to deal with (: (: (:

20:36 it's amazing how much doing this the functional way has already refactored the pseudocode.... >.<

20:39 justin_smith: pilne: I think the reasoning of needing to use indexOf and not providing a clojure function is that scanning linearly for a matching item is often a sign that you could reorganize your data for better performance

20:39 pilne: not to say you need to change anything, I don't know what you are doing well enough to say that

20:40 pilne: it's just a toy project, trying to use code to not only emulate the output of the enigma machine, but to also "resemble" how it functions mechanically with the code itself.

20:40 https://en.wikipedia.org/wiki/Enigma_machine

21:01 kenrestivo: domokato: try #clojure-android or file it on github?

21:08 stapler: its good to avoid "do," yes?

21:23 justin_smith: stapler: do is implicit inside let and defn

21:23 err, fn, which is what defn wraps

21:23 stapler: justin_smith: oh, huh

21:23 interesting!

21:24 justin_smith: ,(let [] (print 'a) (print 'b) (print 'c))

21:24 clojurebot: abc

21:24 justin_smith: invisible do

21:24 stapler: lots of uh invisible things in clojure

21:25 justin_smith: ehh, less than most languages

21:25 also there is an implicit do inside dotimes and doseq, but not inside for

21:29 engblom: Java surely makes simple things difficult. I have been now spending a long time trying to used buffered-input for reading a file and failing. It only reads once, and even though I use (.reset) it does not want to read it a second time.

21:31 This is the function I am trying to get to work: http://pastebin.com/B74yid0e

21:31 With (.reset ...) it does not read even once correctly

21:32 Without (.reset ...) it only once read the correct value

21:33 justin_smith: engblom: are you sure reading a device file works that way? maybe it needs you to reopen the device each time (eg. just use slurp?)

21:36 engblom: (.markSupported (get @gpio.core/input-streams 18)) gives True

21:41 adu: engblom: Java is wierd like that

23:40 yuung: hey everyone, clojure noob here: could someone tell me why they think line 2 from https://gist.github.com/johnmarinelli/a8f3be10a7f994bfb532 isn't getting run?

23:40 whenever i take out the recur part of get-wikimedia-pages (line 20), it works fine

23:42 tolstoy: yuung: My guess is that on line 10, you're using "map" to iterate, but map is lazy and nothing's causing it to resolve.

23:43 yuung: @tolstoy that was one of my suspicions

23:43 tolstoy: yuung: Try wrapping that map in (doall (map .... pages)) and see if that fixes it.

23:43 yuung: @tolstoy any recommendatioins how to fix this?

23:43 will try and report back, thanks!

23:43 justin_smith: yuung: you can use run! instead of map in clojure 1.8

23:44 tolstoy: If you don't need the results from the mapping, try (run! #(insert-page-into-db %) pages) as an alternative.

23:44 justin_smith: it is designed for cases like yours where you are not generating a collection, but actually doing a series of calls

23:44 yuung: tolstoy looks like it worked! now my question is, why was it being lazy?

23:44 tolstoy, justin_smith, will do

23:44 justin_smith: yuung: map is always lazy

23:44 yuung: justin_smith so if i had returned a value from it then operated on that value, map would have ran

23:44 justin_smith: yuung: map is for generating values, and it doesn't actually generate them (sometimes) until you access the value

23:45 yuung: justin_smith i see i see

23:45 justin_smith: yuung: if you accessed the first value that map returned, the first function call would have run

23:45 tolstoy: Yeah, that's a language-design choice.

23:45 justin_smith: if you read all of them, they all would have run

23:46 yuung: an easy way to think of it is that a map, filter, concat, for, etc. is a no-op unless something consumes the values they return

23:46 there are other tricky things like chunking so they might realize more items than you access

Logging service provided by n01se.net