Fork me on GitHub

Oh and just to be clear, in case that was directed at me, no worries; I realize that -> is not, say, a pipe function that takes functions and composes them to return a new function like a reversed comp would, but a macro that builds up an actual expression from its unevaluated arguments (er .. not to be redundant, considering its a macro). When I wrote, say, "the comp / ->>" way", I was mentioning the use case they have in common, namely composing functions, but I don't mean to imply that they are themselves the exact same or that they achieved this result in the same manner, nor that the arrow macros could not be used in other ways.


when I call println from multiple threads the result is all garbled, what's the best way to address this?


@roklenarcic a simple way to address this, with agents :

(def safe-println (partial send (agent nil) (fn [_ & args] (apply println args))))


obviously an actual logger takes care of this


thanks for suggestions


is this the correct forum to pose a design question in clojure?


Probably. You may try #architecture too


Hello. I have this small problem designing the architecture for one of the modules. I have 2 modules - game and round. A game is composed of multiple rounds in addition to some other meta-data. There is only active round in a game at any point of time. Any action in a game is delegated directly to the currently active round in the game at that point in time. The code looks something like this


(defn do-action [game-id action params]
  (let [game (from-atom game-id)
          round (curr-round game)
          handler (partial debit-points game-id)
          new-round (do-action round action params handler)]
     (-> (from-atom game-id)
           (set-curr-round round)))
The problem is that I store the game in an atom. A request from the client comes with the current game-id, I extract the game from the atom using the game-id and then run the logic on the round. But the handler is a partial which can change the data in the game. Now I'm handling this by loading the game twice from the atom. But is there a better functional way to handle this?

Andreas Liljeqvist10:09:36

I would define do-action to also take the game(state) as a non-atom. Probably shouldn't use any atoms in do-action

Andreas Liljeqvist10:09:09

Like (do-action state game-id action params) => new-state

Andreas Liljeqvist10:09:58

so the game state is a function of a sequence of actions


So the handlercan actually either change the game object or make a http call. So was not sure how it can send the game object back


If it makes a http call, it is fine because that is a side channel.


@luke.deveraux the handler modifies the atom directly? If so, it shouldn't, but it should return the modified game data, so you have an updated copy of it.


So the handlercan actually either change the game object or make a http call. So was not sure how it can send the game object back


The handler needs to change the game object

Andreas Liljeqvist10:09:39

Change the game atom or just the map?


Game's map


  (:require [com.round :as r]))

(defn do-action [game-id action params]
 (let [game (from-atom game-id)
       round (:curr-round game)
       handler (partial debit-points game-id)
       new-round (r/do-action round action params handler)]
    (-> (from-atom game-id)
        (assoc :curr-round new-round)))


@andreas862 @mdallastella I do not want the round to be able to reach into game and and make changed; so i'm using a partial here - But then how do I ensure that the game map is correctly handled inside round?

Andreas Liljeqvist10:09:29

I think that you should try to make a completely mutation free version of your do-action function. (f state action) => new-state Eventual side-effects like updating an atom or a score somewhere via http can be done by inspecting the new-state.

👍 4
Andreas Liljeqvist10:09:45

Score is probably a part of the game-state from my point of view - Your mileage might vary


Right ... that is what I started it as (f state action) => new-state will give me a neat finite state machine which can be easily composed. But the problem is how do i make sure that the round module needs to mutate it's parent (in this case the game via handler) - how do i structure that?


oh so you mean you want me to put in instructions that require modifications of the parent as data, and then let the parent run through those modifications?

Andreas Liljeqvist11:09:52

Could you share an example of your game-map?


{:id 123
 :curr-round-key [:rounds 1]
 :rounds {1 {:id 673
             :clicks {:total 5 :remaining 3}
             :points 5}}
 :lives {:total 3 :remaining 2}
 :points 45}
So generally I have this :curr-round-key which is a cursor / pointer to the actual round which is is accessed via (get-in game (:curr-round-key game))

Andreas Liljeqvist13:09:40

is :points just the sum of all :rounds/points?

Andreas Liljeqvist13:09:26

Then you could update the points in the parent using the round-map


Yes :points in game get updated after a round ends. However, a round starts only if the player has :lives remaining


I had the rule in mind "str is for humans, pr-str for machines". But now I can't come up with an example that doesn't roundtrip (= (-> v str read-string) v). Can anyone think of a v that breaks that cycle?


(def v (symbol "foo bar"))


A simple string will (def v "foo")


@manutter51 true, I tried ["foo"] but not "foo"


anything other than bare strings?


Hmm, reader tags?


(def v #inst "2018")
=> #'user/v
(= (-> v str read-string) v)
=> false
(= (-> v pr-str read-string) v)
=> true


or whatever they’re called.

Alex Miller (Clojure team)12:09:07

read-string is the dual of pr, not str


@alexmiller that's what I wrote, right?


I was only wondering if there's any example, other than bare strings, for which str is not an identical substitute

Alex Miller (Clojure team)13:09:40

str does toString type stuff so anything where that’s wrong like a record instance

Alex Miller (Clojure team)13:09:11

user=> (defrecord R [a b])
user=> (->R 1 2)
#user.R{:a 1, :b 2}
user=> (str *1)
"[email protected]"
user=> (pr-str *2)
"#user.R{:a 1, :b 2}"


@manutter51 that's not a great example - it's expected that it normalizes insts


Well, you didn’t ask for unexpected examples 😉


@alexmiller gotcha, yeah that makes sense


@manutter51 I take that back, that actually does surprise me, good point (I thought I'd checked that)

👍 4
Alex Miller (Clojure team)13:09:42

pretty much any Java object that’s not a Clojure type is likely to be different in this way

Alex Miller (Clojure team)13:09:34

as str will not use the printer (so you just get .toString) but pr-str will use the printer


yeah, although regular Java objects like (java.util.HashMap.) won't roundtrip anyway, with pr-str or str

Alex Miller (Clojure team)13:09:50

well that example will, but using Clojure maps instead, so depends on what level of abstraction you’re talking about


oh yes I forgot that (= (java.util.HashMap.) {})


hey guys, i have this code that i hate!

(defn set-log-level [level]
  (case level
    "WARN" (.setLevel
            (org.slf4j.LoggerFactory/getLogger (Logger/ROOT_LOGGER_NAME)) Level/WARN)
    "TRACE" (.setLevel
             (org.slf4j.LoggerFactory/getLogger (Logger/ROOT_LOGGER_NAME)) Level/TRACE)
    "INFO" (.setLevel
            (org.slf4j.LoggerFactory/getLogger (Logger/ROOT_LOGGER_NAME)) Level/INFO)
    "DEBUG" (.setLevel
             (org.slf4j.LoggerFactory/getLogger (Logger/ROOT_LOGGER_NAME)) Level/DEBUG)))


do you guys have any idea how i can use reflection to create the level/“level var”?


like “Level/var”


I tried to play with eval and macros but didn’t work really well.


why do you hate this code?


(eval (symbol "Double/POSITIVE_INFINITY")) will return ##Inf but i would hate that code more than what you have here


you can keep the matches in map {"WARN" Level/WARN ...} and use that to collapse all these cases into one path using the results of the map lookup


nice idea 😄 will do that!


i hate it because it’s to many duplication.


lift (org.slf4j.LoggerFactory/getLogger (Logger/ROOT_LOGGER_NAME)) into a let binding outside the case


use a map to map your levels {"WARN" Level/WARN}


then (.setLevel logger (get that-map level))


what dpsutton said


nice! doing that now thx guys!


Final code:

(defn set-log-level [level]
  (let [logger (org.slf4j.LoggerFactory/getLogger (Logger/ROOT_LOGGER_NAME))
        level {"TRACE" Level/TRACE
               "DEBUG" Level/DEBUG
               "INFO"  Level/INFO
               "ERROR" Level/ERROR
               "WARN"  Level/WARN
               "OFF"   Level/OFF}]
    (.setLevel logger (get level (clojure.string/upper-case level) Level/INFO))))

Alex Miller (Clojure team)16:09:52

I would probably pull the map out:

(def log-levels {"TRACE" Level/TRACE
               "DEBUG" Level/DEBUG
               "INFO"  Level/INFO
               "ERROR" Level/ERROR
               "WARN"  Level/WARN
               "OFF"   Level/OFF})

(defn set-log-level [level]
  (doto (org.slf4j.LoggerFactory/getLogger (Logger/ROOT_LOGGER_NAME))
    (.setLevel (log-levels (clojure.string/upper-case level) Level/INFO)))))

👍 4

It's possible to generate that map with java.util.enumset/allof

Alex Miller (Clojure team)16:09:11

then use the literal map as a function

Alex Miller (Clojure team)16:09:52

nothing wrong with the former code though, just preferences


the reason why I thought would be ok to keep it inside the function is because it’s only relevant in this context. I mean I will not use it anywhere else.


(let [log-levels {}] (defn set-log-level [level] ...))


a defn can be in the body of a let form if you like. but this is for sure bikeshed at the moment 🙂

Alex Miller (Clojure team)16:09:33

please no… :) better to inline it


ah. i figured it would prevent it from being constructed on each invocation

Alex Miller (Clojure team)16:09:33

it’s compiled into the function so no difference there

👍 8

immutable constant global data is the best


hiding the best is silly


I will keep it in mind 🙂 thx folks!

Alex Miller (Clojure team)16:09:46

I just like letting data be data :)

Alex Miller (Clojure team)16:09:27

one interesting thing to notice is that if you invoke log-levels like a function you can actually replace it with a function later if needed and the caller doesn’t change.

🌮 4
👍 4

@alexmiller what you have said makes lots of sense. I will change 🙂


I have to start to think more data as data 🙂

Alex Miller (Clojure team)17:09:32

now you’ve gone and turned data back into code :)

Alex Miller (Clojure team)17:09:56

seriously though, that’s clever and useful, particularly if you want some immunity to an expanding enum set over time. for this particular case, I’m not expecting that set of log levels to change and I find the literal map far easier to understand and maintain


I use it all the time for interop. For logging sure, not so useful but with say a db client lib that evolves over time this saves me the headache of tracking enum changes and error handling for them

Alex Miller (Clojure team)17:09:54

Clojure design around this stuff all largely predates Java enums. Ideally we would have a little more automated interop here.


I'd love a small editor that just did parinfer exactly like protorepl, but was compiled down to a graal native binary and launched super fast.

Alex Miller (Clojure team)17:09:03

but graal == no eval, so no repl? (maybe I’m not up to date on graal capabilities)


I actually don't want in-editor eval


Maybe connect to one, but I personally don't do that much, despite the pros


Yeah, yeah, I know 🙂


yeah, but think of the guy writing the editor


Yeah, they have needs too, I get it 🙂


terminology around this stuff is super confusing -- graal is a compiler like hotspot. What people usually mean is 'native image' -- which is the output of Graal plus this thing called SubstrateVM


you can plug graal a normal JVM to override hotspot -- and it's the Normal Java experience. Dynamic loading, etc.


native-image (graal + substrate) is the one with the many restrictions


It'd be nice if they could combine the magic and get a fast booting native image that transitioned to a runtime optimization strategy as per regular JVM, after it's launched.


you can use jaotc


it's sort of what you describe (uses Graal under the hood)


hmm, nice


"Tiered AOT"


Hey does anyone know off the top of their head if sort-by before group-by helps with performance? Thanks


My guess is probably not, but does affect the order of the groupings: ...

Alex Miller (Clojure team)19:09:22

no, probably the reverse :)


In terms of big-O notation of run time, I don't see how it can. In terms of being faster because of cache locality in the group-by, maybe?

Alex Miller (Clojure team)19:09:56

group-by returns a map, which is unordered

Alex Miller (Clojure team)19:09:18

so you should not have any expectations about the printed order of the keys in the result


@alexmiller I mean it will effect the grouped items (the values to the map)


So if you run a sort-by before a group-by with the same f then you can expect the grouped-items to be sorted basically..


sort-by uses stable sorting, so with same f why would it change the order?


In sort-by it's called keyfn, in group-by it's called f... just to be clear.


Sure, but because sort-by is stable, it should leave all elements with same f / keyfn in the same order they were relative to each other in the input.


@andy.fingerhut given a sorted list, group-by will conj each element of the list onto the vectors of each grouping. So group-by will conj each element in the original order basically. Does that make sense? I am not sure I understand your point about sort-by being stable... we might be talking about the same thing. I don't think group-by would change the order - I think group-by will preserve the order...


You said earlier "but does affect the order of the groupings", which I thought meant "does affect the order of the vectors that are values in the map returned by group-by".


Sorry I was trying to say running sort-by before group-by effects the order of the final groups... which is easier then running sort-by after, for each group...IMO


I didn't know if group-by preserved the order or not...


got it. group-by doc string does promise that.


It's hard to explain this stuff. Thanks, I see it


on the other hand, if perf is a factor, it's probably going to be cheaper to sort each group after group-by, at least if you have a large number of items and decent group distribution. though probably better to go with whatever's cleaner in context over the perf difference, at least for starters


an experiment could validate for sure, but my guess is that it's probably faster to sort the whole list then group it


:thinking_face: it seems like there’s a lot more community work around supporting datomic pull syntax than full datalog


coworker had a frustrated request for a notification when a term in a query is not unified with anything so he doesn't get back the whole db


I just wish there was a general library for implementing datalog


but it doesn't have the nifty syntax 😛


there is also an article somewhere random