This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-10-30
Channels
- # aleph (2)
- # announcements (4)
- # babashka (3)
- # beginners (89)
- # calva (12)
- # cljdoc (7)
- # cljsrn (5)
- # clojars (1)
- # clojure (19)
- # clojure-dev (6)
- # clojure-europe (2)
- # clojurescript (12)
- # conjure (7)
- # cursive (4)
- # fulcro (28)
- # graphql (6)
- # gratitude (2)
- # introduce-yourself (2)
- # jobs (1)
- # jobs-discuss (26)
- # off-topic (2)
- # pathom (22)
- # pedestal (2)
- # podcasts-discuss (10)
- # re-frame (9)
- # reitit (8)
- # releases (2)
- # remote-jobs (1)
- # shadow-cljs (1)
- # tools-deps (27)
- # xtdb (11)
What I'm getting wrong here?
(defn foo [& {:keys [bar]}]
bar)
(defn faa [fname & args]
(let [fun (ns-resolve *ns* (symbol fname))]
; (println args)
(apply fun args)))
(defn fee [fname & args]
(faa fname args))
(fee "foo") ; => No value supplied for key: null
(fee "foo" :bar 1) ; => No value supplied for key: (:bar 1)
I expected args
to be passed down, but logging inside faa
, if I don't use any arguments, args
becomes (nil)
instead of nil
breaking the following destructuringYes, when you use & args
destructuring, you need to use apply
to call down the chain. This is why it is often better to pass a hash map of options or a vector of arguments down the chain instead of using a variadic function -- the latter doesn't compose very well (although it's nice for human interaction).
This is why Clojure 1.11 supports passing a hash map to functions declared to destructure key/value pairs -- so you get the composability without losing the human interaction.
a partial is not a transducer?
A transducer is any function from a step function (a function suitable for passing to reduce) to a step function
Are there any charting libraries that are popular with clojurians? I'm making progress with D3 but the API seems too low-level and complex for my foreseeable needs. I'm thinking about trying https://recharts.org/ instead, but wanted to see if there's a popular option when writing CLJS.
I’ve been looking to try https://github.com/metasoarous/oz and https://github.com/jsa-aerial/hanami, might be what you’re looking for
Hi, I am new here. I have recently started working on a simple telegram bot for me and my friends. There is a library just for that called https://github.com/Otann/morse, so I decided to use it. The idea is to implement a game: a user asks to play with something like "/play", the bot answers with a random question and waits for the player to submit an answer with "/answer ...". Then it updates player stats accordingly. I am now thinking what would be the best way to implement this behavior. I'll outline some of this task's challenges here, hoping for an advice on how to approach them. But please feel free to say that my design is all wrong and this should be done some other way. This is my first clojure project, so I am hoping to learn from you more experienced clojurians. The first problem I'm facing has to do with concurrency. What if another user sends "/play" command while the first one is still thinking about an answer? The second user should still be able to play. My initial idea is to use core.async's channels for this: create a thread (or a go block, I don't really understand what would change) when "/play" handler is called, which waits for an input form a certain channel (should be unique for each user). When the user finally submits an answer, the "/answer" handler puts on this channel, so the initial thread can continue working, checking if the answer is correct, then updating the player's score (which I want to implement as an atom, but more on that later). The actual problem here is this: where do I store the channels? The command handlers have no direct way of communicating (or at least I don't see one), so should I use a global var to store each player's channel? Or is there a cleaner solution to this? The second problem is about storing game data: player scores and predefined list of questions. I won't be able to keep the bot running 24/7, so there needs to be some way to transfer data between sessions. Should I load it from files on startup and dump to files when exiting? If so, how do I achieve the latter? Is it okay to use an atom for player scores and a simple var for questions? Should I store them as global references or is there another way? Lastly, what file format would you use? I'm thinking clojure data structures, then read them with read-string. As you can see, this project is fairly simple, but I'm really struggling with design, as I have no experience creating concurrent programs. I hope other beginners can also benefit from answers to this post. If something isn't clear, feel free to ask.
I have an example game I made for a school project that uses agents for a similar purpose
(defn create-socket-backed-player!
"Creates a player backed by a socket. Will
potentially create some threads"
[^Socket socket-conn]
(let [server-side-state (agent {:listeners []
:identifier nil})
player (reify
Player
(identifier [_]
(:identifier @server-side-state))
(assign-id! [_ player-id]
(send-off server-side-state
(fn [state]
(util/write-to-socket! socket-conn {:kind :assigned-id
:player-id player-id})
(assoc state :identifier player-id))))
(register-action-listener! [_ cb]
(send server-side-state update :listeners conj cb))
(update-state! [_ new-state]
(util/write-to-socket! socket-conn {:kind :new-state
:new-state new-state})))]
(async/thread
(try
(while true
(let [reader (transit/reader (.getInputStream socket-conn) :json)
msg (transit/read reader)]
(log/debug "Received socket message:" msg)
(doseq [listener (:listeners @server-side-state)]
(listener msg))))
(catch Exception e
(log/error "Error communicating with client" e))))
player))
;; ----------------------------------------------------------------------------
(defn create-referee-agent
"Creates a new referee"
[]
(doto (agent {:game-state gamestate/starting-game
:players {}
:observers []}
:error-mode :continue)
(set-error-handler! (fn [& _] (log/error _)))))
;; ----------------------------------------------------------------------------
(defn ^{:private true}
inform-everyone-of-state!
"Informs all players of the state of the game"
[referee]
(doseq [[_ player] (:players referee)]
(player/update-state! player (:game-state referee)))
(doseq [observer (:observers referee)]
(observer/update-state! observer (:game-state referee)))
referee)
;; ----------------------------------------------------------------------------
(defn ^{:private true} next-player-id [referee]
(case (count (get referee :players))
0 "white"
1 "black"
2 "red"
3 "green"
4 "blue"
(throw (IllegalStateException. "A referee only manages up to 5 players"))))
;; ----------------------------------------------------------------------------
(defn ^{:private true} make-move! [referee move]
(inform-everyone-of-state!
(f/if-let-ok? [next-game-state (gamestate/make-move
(:game-state referee)
move)]
(assoc referee :game-state next-game-state)
(f/if-let-ok? [with-player-kicked (gamestate/make-move
(:game-state referee)
{:kind :kick-player-for-invalid-move
:player-id (:player-id move)})]
(assoc referee :game-state with-player-kicked)
(do (log/error "Cannot kick the player who made a wrong move")
referee)))))
and for the saving of data - yep for a simple thing you can just dump your clojure data to a .edn file
for the second one, how about just save your data to a sqlite db. for the first one the program should be able to distinguish one user from another. each user has corresponding state in your bot.
@UP82LQR9N Yes I've considered using a db, but for an application of such a small scale it's a bit overkill. But maybe you're right: dbs are unavoidable, so why not practice now? Are there any resources for learning how to interact with dbs in clojure? Any good libraries? Thanks in advance.
@U3JH98J4R Thank you for your example. It is indeed more complicated than I need as it seems to handle server-client communications. As I understand, you're using an agent (referee) to store game state and players (represented as records). Could you please explain the advantages of using agents instead of atoms here? I've read that the only difference between the two is that when an agent's state is updated, it happens on a separate thread. Could you also tell me what those instructions starting with ^ are? I've seen them being used to denote that a function parameter should be of certain type, but that ^{:private true} confused me. Is it some kind of metadata?
libs: org.xerial/sqlite-jdbc conman/conman mount/mount mount is optional conman is also optional but it's nice to use.
(ns xyz.db
(:require [mount.core :refer [defstate]]
[conman.core :as conman]))
(def pool-spec
{:jdbc-url "jdbc:sqlite:xyz.db"})
(defstate ^:dynamic *db*
:start (conman/connect! pool-spec)
:stop (conman/disconnect! *db*))
(conman/bind-connection *db* "sql/queries.sql")
queries.sql
-- :name find-users :n
select * from users
to use all the above
(ns xyz.core
(:require [xyz.db :as db])
(db/find-users)
use repl to play with the code it should get you going in no time. then if you need to go deeper just google the name of libs
@UP82LQR9N Thanks a lot! I'll try to use them in this project.
@U02KJRX7HJR can you ping me tommorow and I'll give this a closer look?
Atoms can potentially retry so you can't safely do side effects without another strategy
@U3JH98J4R Oh, so agents use some kind of a queue for updating their state instead of retrying as I understand it?
So, I've come up with a solution. I have all the code for the game in a separate namespace from the bot code. In this namespace I define an agent to store the players' data:
(def players (agent {})) ;; Should actually be read from somewhere on startup
Then I create two functions to use with send
on this agent:
(defn challenge-player!
"If the player doesn't have a challenge, get a random one, send a message
to the chat and return an updated players map with the challenge assigned."
[players challenges player-name token chat-id]
(if-not (get-in players [player-name :current-challenge])
(let [{:keys [question] :as challenge} (rand-nth challenges)
message (get-question-message player-name question)]
(t/send-text token chat-id message)
(assoc-in players [player-name :current-challenge] challenge))
players))
This function checks whether the player already has a challenge. If not, it randomly selects one (for now challenges are stored in a vector as hash maps), then assigns it to the player by setting the :current-challenge property. It then sends a message to the chat with morse.api/send-text. The fact that an agent is used guarantees that the message won't be sent if the player already has a challenge. So yeah, an atom wouldn't work here because of potential retries.
The same logic applies to the second function:
(defn submit-answer!
"If the player has a challenge, check whether their answer is correct,
send a message to the chat and return a players map with updated scores.
Also set this player's :current-challenge to nil. Intended to be used with
`send` on an agent to avoid retries."
[players answer player-name token chat-id]
(if-let [challenge (get-in players [player-name :current-challenge])]
(let [correct-answer (:answer challenge)
answer-correct? (= answer correct-answer)
message (get-answer-message
player-name correct-answer answer-correct?)]
(t/send-text token chat-id message)
(->
players
(assoc-in [player-name :current-challenge] nil)
(update-in [player-name :games-played] (fnil inc 0))
(update-in [player-name :games-won] (if answer-correct?
(fnil inc 0)
identity))))
players))
This one sets the player's challenge to nil, informs them about whether their answer is correct, then updates the stats. If the player doesn't have a challenge, nothing happens.@U3JH98J4R Your advice to use an agent has solved my problem! I'd like to hear what you think about my solution. I've actually written the whole thing, and it works. So, thank you.
I have this code :
`(defn update-output [_]
(cond
((= \C (get-input-target) (= \F (get-output-temp)))
(do (set-output-temp (c->f (get-input-temp)))
(gdom/setTextContent degree "F"))
((= \F (get-input-target)) (= \C (get-output-temp)))
(do (set-output-temp (f->c (get-input-temp)))
(gdom/setTextContent degree "C"))
:else (do (set-output-temp (get-input-temp))
(gdom/setTextContent degree (get-output-temp))))))
but as soon as I try to compile it . I see this compile error
cond requires an even number of forms
What do I have done wrong ?You’re writing a scheme type cons where check and consequence are in the same list
So because of this cond needs an even number of forms. Each test has an associated consequence.
This is even less compiling
`(defn update-output [_]
(cond
((= \C (get-input-target) (= \F (get-output-temp))))
(do (set-output-temp (c->f (get-input-temp)))
(gdom/setTextContent degree "F"))
((= \F (get-input-target)) (= \C (get-output-temp))))
(do (set-output-temp (f->c (get-input-temp)))
(gdom/setTextContent degree "C"))
:else (do (set-output-temp (get-input-temp))
(gdom/setTextContent degree (get-output-temp))))
You have too many parens clojures cond uses fewer parents then common lisp or scheme, read the docs find some examples
Ah, misread, now your problem is you have a few forms like ((= ... ...) (= ... ...))
(defn update-output [_]
(cond
(and(= \C (get-input-target) (= \F (get-output-temp)))
(do (set-output-temp (c->f (get-input-temp)))
(gdom/setTextContent degree "F")))
(and(= \F (get-input-target)) (= \C (get-output-temp))
(do (set-output-temp (f->c (get-input-temp)))
(gdom/setTextContent degree "C")))
:else (do (set-output-temp (get-input-temp))
(gdom/setTextContent degree (get-output-temp)))))
the parenteses makes my crazy nowIt looks like you are writing clojurescript not clojure, which can make the errors tricky
Buy if you are not compiling in advanced mode, it should be possible to figure things out
Then there is some logic error, you best bet is sprinkling prn calls around to see where things are diverging from your expectations
oke because you said a decent one would help me to see where I did wrong and this time it did not
but to come back to the code I need a extra ) in the first condition so I need to be
((= \C (get-input-target)) (= \F (get-output-temp)))
If you want two things true
use (and thing1 thing2)
@roleof (and (= \C (get-input-target)) (= \F (get-output-temp)))
Sometimes it helps to put it on mulitple lines
(and
(= \C (get-input-target))
(= \F (get-output-temp))
)
Now we can make sure that the parens are balanced easily.you can play in the REPL and see
lein repl
> (and (= 4 (* 2 2)) (= 5 (+ 1 1 1 1 1)))
@U0EGWJE3E if you indent the code like this, you can see that both the first and second condition has incorrect parens:
(defn update-output [_]
(cond
(and (= \C (get-input-target)
(= \F (get-output-temp)))
(do (set-output-temp (c->f (get-input-temp)))
(gdom/setTextContent degree "F")))
(and (= \F (get-input-target))
(= \C (get-output-temp))
(do (set-output-temp (f->c (get-input-temp)))
(gdom/setTextContent degree "C")))
:else
(do (set-output-temp (get-input-temp))
(gdom/setTextContent degree (get-output-temp)))))
here's what the indentation looks like if we correct the parens:
(defn update-output [_]
(cond
(and (= \C (get-input-target))
(= \F (get-output-temp)))
(do (set-output-temp (c->f (get-input-temp)))
(gdom/setTextContent degree "F"))
(and (= \F (get-input-target))
(= \C (get-output-temp)))
(do (set-output-temp (f->c (get-input-temp)))
(gdom/setTextContent degree "C"))
:else
(do (set-output-temp (get-input-temp))
(gdom/setTextContent degree (get-output-temp)))))
and I wonder if there is a better way to make this work. Later I have to add Kelvin to the mix and then it will be I think some 7 conditions
think of ways you can generalize the problem; here's a small refactor of your existing code to give you some ideas:
(defn temp-converter
[from to]
(case [from to]
[\C \F] [c->f "F"]
[\F \C] [f->c "C"]))
(defn update-output [_]
(let [[converter suffix] (temp-converter (input-target) (output-target))
new-temp (converter (get-input-temp))]
(set-output-temp new-temp)
(gdom/setTextContent degree suffix)))
if you find yourself duplicating lots of similar lines, consider how you can pull a variable out and de-duplicate
;;4 ways to solve your problem
;; there are certainly more.
;;nested if statements
(def starting-symbol \C)
(def desired-symbol \F)
;;using the pattern
;; (if thing-to-evaluate
;; when-if-is-true
;; when-if-is-false)
(if (= starting-symbol \C)
(if (= desired-symbol \F)
(set-output-temp (c->f @input-temp)) ;;c->f
;; else start is C and output is K
(set-output-temp (c->k @input-temp))) ;;c->k
;;else starting symbol is not C
(if (= starting-symbol \F)
(if (= desired-symbol \C)
(set-output-temp (f->c @input-temp)) ;;f->c
;;else start is F and output is K
(set-output-temp (f->k @input-temp))) ;;f->k
;;else starting symbol is not C and not F
(if (= starting-symbol \K)
(if (= desired-symbol \C)
(set-output-temp (k->c @input-temp)) ;;k->c
;;else start is K and output is not C
(set-output-temp (k->f @input-temp)))))) ;;k->f
;;using (cond
;; statement1 what-to-do-1
;; statement2 what-to-do-2
;; statement3 what-to-do-3 )
;;
;; more at
(def starting-symbol \C)
(def desired-symbol \F)
(cond
(and (= starting-symbol \C)
(= desired-symbol \F)) (set-output-temp (c->f @input-temp))
(and (= starting-symbol \F)
(= desired-symbol \C)) (set-output-temp (f->c @input-temp))
(and (= starting-symbol \C)
(= desired-symbol \K)) (set-output-temp (c->k @input-temp))
(and (= starting-symbol \F)
(= desired-symbol \K)) (set-output-temp (f->k @input-temp))
(and (= starting-symbol \K)
(= desired-symbol \C)) (set-output-temp (k->c @input-temp))
(and (= starting-symbol \K)
(= desired-symbol \F)) (set-output-temp (k->f @input-temp)))
;;using case with a vector [starting-symbol desired-symbol]
(def starting-symbol \C)
(def desired-symbol \F)
(case [starting-symbol desired-symbol]
[\C \F] (do (set-output-temp (c->f @input-temp)
(gdom/setTextContent degree "F")))
[\C \K] (do (set-output-temp (c->k @input-temp)
(gdom/setTextContent degree "K")))
[\F \C] (do (set-output-temp (f->c @input-temp)
(gdom/setTextContent degree "C")))
[\F \K] (do (set-output-temp (f->k @input-temp)
(gdom/setTextContent degree "K")))
[\K \F] (do (set-output-temp (k->f @input-temp)
(gdom/setTextContent degree "F")))
[\K \C] (do (set-output-temp (k->c @input-temp)
(gdom/setTextContent degree "C"))))
;;; crazy magic
(defn c->f [temp]
(float (+ (/ (* temp 9) 5) 32)))
(defn converter-method [input-symbol output-symbol]
"This takes something like 'C' and 'F' and
returns the function called c->f in the namespace"
(let [in (clojure.string/lower-case input-symbol)
out (clojure.string/lower-case output-symbol)
funkshan-name (str in "->" out)
fk-invokation (ns-resolve *ns* (symbol funkshan-name))]
fk-invokation))
;;example: define c->f above and then call
;; (converter-method \C \F)
;; returns
;; #'user/c->f (the method in the namespace that looks like in->out)
;;test
((converter-method "c" "f") -40)
;;result is -40.0
;; first it translates the above to (c->f ...)
;; then it invokes that function in the namespace with
;; the argument -40
;; and returns the result -40.0
;;finished update-output statement
(defn update-output [_]
(do (set-output-temp ((converter-method (get-input-target) (get-output-temp)) (get-input-temp)))
(gdom/setTextContent degree (get-output-temp)))
@U0EGWJE3E yeah 3 might be easiest to read and you can always add [\F \F]
as a case that returns the number unchanged
hope that helps :]
seemed like a good opportunity to showcase some different approaches for you to study at your leisure. maybe copy those to a file and look over them from time to time