Fork me on GitHub
#clojure
<
2018-09-20
>
Cameron00:09:23

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.

roklenarcic08:09:14

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

leonoel08:09:19

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

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

roklenarcic08:09:19

obviously an actual logger takes care of this

roklenarcic08:09:26

thanks for suggestions

luke09:09:49

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

jumar09:09:31

Probably. You may try #architecture too

luke10:09:50

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

(ns com.game)

(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

luke10:09:00

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

luke10:09:59

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

mdallastella10:09:03

@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.

luke10:09:45

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

luke10:09:28

The handler needs to change the game object

Andreas Liljeqvist10:09:39

Change the game atom or just the map?

luke10:09:53

Game's map

luke10:09:24

(ns com.game
  (: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)))

luke10:09:07

@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

luke10:09:02

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?

luke10:09:16

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?

luke11:09:43

{: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

luke06:09:01

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

pesterhazy12:09:43

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?

nyoung16:09:26

(def v (symbol "foo bar"))

manutter5112:09:45

A simple string will (def v "foo")

pesterhazy12:09:07

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

pesterhazy12:09:08

anything other than bare strings?

manutter5112:09:40

Hmm, reader tags?

manutter5112:09:46

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

manutter5112:09:06

or whatever they’re called.

Alex Miller (Clojure team)12:09:07

read-string is the dual of pr, not str

pesterhazy12:09:47

@alexmiller that's what I wrote, right?

pesterhazy13:09:41

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
user=> (->R 1 2)
#user.R{:a 1, :b 2}
user=> (str *1)
"user.R@78de1816"
user=> (pr-str *2)
"#user.R{:a 1, :b 2}"

pesterhazy13:09:14

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

manutter5113:09:18

Well, you didn’t ask for unexpected examples 😉

pesterhazy13:09:25

@alexmiller gotcha, yeah that makes sense

pesterhazy13:09:26

@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

pesterhazy13:09:26

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

pesterhazy14:09:10

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

lsantanna15:09:25

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)))

lsantanna15:09:57

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

lsantanna15:09:45

like “Level/var”

lsantanna15:09:13

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

dpsutton15:09:49

why do you hate this code?

dpsutton15:09:31

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

dpsutton15:09:24

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

lsantanna15:09:08

nice idea 😄 will do that!

lsantanna15:09:22

i hate it because it’s to many duplication.

ghadi15:09:42

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

ghadi15:09:43

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

ghadi15:09:01

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

ghadi15:09:55

what dpsutton said

lsantanna15:09:13

nice! doing that now thx guys!

lsantanna16:09:28

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
mpenet17:09:00

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

lsantanna16:09:55

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.

dpsutton16:09:46

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

dpsutton16:09:18

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

dpsutton16:09:57

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
hiredman16:09:22

immutable constant global data is the best

hiredman16:09:42

hiding the best is silly

lsantanna16:09:36

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
lsantanna16:09:21

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

lsantanna16:09:21

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

mpenet17:09:18

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.

john17:09:06

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)

john17:09:19

I actually don't want in-editor eval

john17:09:49

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

john17:09:00

Yeah, yeah, I know 🙂

hiredman17:09:10

yeah, but think of the guy writing the editor

john17:09:33

Yeah, they have needs too, I get it 🙂

ghadi17:09:07

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

ghadi17:09:49

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

ghadi17:09:10

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

john17:09:59

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.

ghadi17:09:40

you can use jaotc

ghadi17:09:57

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

john17:09:03

hmm, nice

john17:09:51

"Tiered AOT"

leblowl19:09:19

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

leblowl19:09:13

My guess is probably not, but does affect the order of the groupings: https://github.com/clojure/clojure/blob/clojure-1.9.0/src/clj/clojure/core.clj#L7066 ...

Alex Miller (Clojure team)19:09:22

no, probably the reverse :)

andy.fingerhut19:09:41

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

leblowl19:09:37

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

leblowl19:09:43

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..

andy.fingerhut19:09:44

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

leblowl19:09:45

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

andy.fingerhut19:09:37

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.

leblowl19:09:56

@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...

andy.fingerhut19:09:34

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".

leblowl19:09:31

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

leblowl19:09:58

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

andy.fingerhut19:09:52

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

leblowl19:09:54

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

jaawerth20:09:08

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

ghadi20:09:52

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

lilactown22:09:45

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

dpsutton22:09:15

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

lilactown22:09:00

I just wish there was a general library for implementing datalog

lilactown22:09:24

but it doesn't have the nifty syntax 😛

hiredman23:09:15

there is also an article somewhere random