I'm messing around in Clojure specifically the Noir web framework and trying to generate a random grid of tiles.
This is probably pretty bad code but I'm learning! :D
(def tiles [:stairs :stone :monster])
(defpage "/" []
(common/layout
[:div {:class "level"}
(repeatedly 10 [:div {:class "row"}
(repeatedly 10
[:div {:class (str "tile " (name (rand-nth tiles)))}])])]))
But this code is throwing a exception:
Wrong number of args (0) passed to: PersistentVector - (class clojure.lang.ArityException)
repeatedly takes a function not a body so you need to wrap the bodies in functions:
(repeatedly 10 (fn []
[:div
{:class "row"}
(repeatedly 10 (fn []
[:div {:class (str "tile " (name (rand-nth tiles)))}]))]))
Answer:
user=> (repeatedly 10 (fn [] [:div]))
([:div] [:div] [:div] [:div] [:div] [:div] [:div] [:div] [:div] [:div])
user=> (repeatedly 10 [:div])
ArityException Wrong number of args (0) passed to: PersistentVector clojure.lang.AFn.throwArity (AFn.java:437)
user=> (doc repeatedly)
-------------------------
clojure.core/repeatedly
([f] [n f])
Takes a function of no args, presumably with side effects, and
returns an infinite (or length n if supplied) lazy sequence of calls
to it
nil
user=>
(def tiles [:stairs :stone :monster])
(defpage "/" []
(common/layout
[:div {:class "level"}
(repeatedly 10 (fn []
[:div
{:class "row"}
(repeatedly 10 (fn []
[:div {:class (str "tile " (name (rand-nth tiles)))}]))]))
Related
I saw a talk about railroad oriented programming (https://www.youtube.com/watch?v=fYo3LN9Vf_M), but i somehow do not get how to work this out, if i use reduce, because reduce has two or even three arguments.
How am i able to to put the following code like a railroad? I seems to me hard, because of reduce taking a function as an argument in addition to the game object.
(defn play-game-reduce []
(let [game-init
(->>
(io/initialize-cards-and-players)
(shuffle-and-share-cards myio/myshuffle)
(announce))
play-round
(reduce play-card (assoc-in game-init [:current-trick] '()) [:p1 :p2 :p3 :p4])]
(reduce play-round game-init (range (get game-init :round-count)))))
The whole code is here:
https://github.com/davidh38/doppelkopf/blob/master/src/mymain.clj
The code should more look like this:
(->> (io/initialize-cards-and-players)
(shuffle-and-share-cards myio/myshuffle)
(announce)
reduce (play-round .. )
reduce (play-card ...))
That would look to me much more explicit.
That video was made for a different language and you can't directly transfer these ideas to Clojure.
I looked at your source code and there are some things to improve:
(defn play-card-inp []
(eval (read-string (read-line))))
You shouldn't use eval in production code.
Read-string is unsafe and you should use clojure.edn/read-string instead. I'm not sure what is expected input here and what is the result of the evaluation, maybe you should use just clojure.edn/read here.
(defn myshuffle [cards]
(shuffle cards)
)
(defn initialize-cards-and-players []
; init cards
(def cards '([0 :c], [1 :c],[2 :c], [3 :c], [0 :s], [1 :s], [2 :s], [3 :s]))
(def players '(:p1 :p2 :p3 :p4))
;(def round-players (take 4 (drop (who-won_trick tricks) (cycle (keys players)))))
; mix and share cards
{:players (zipmap players (repeat {:cards () :tricks ()}))
:current-trick ()
:round-start-player :p1
:cards cards
:round-count (/ (count cards) (count players))
:mode ""
})
You should delete myshuffle and use directly shuffle where needed. Ending parenthesis shouldn't be on a separate line.
Don't use def (creates global variable) inside defn, use let (creates local variables). I would rewrite this as:
(defn new-deck []
(for [letter [:c :s]
number (range 4)]
[number letter]))
(defn new-game []
(let [cards (new-deck)
players [:p1 :p2 :p3 :p4]]
{:players (zipmap players (repeat {:cards () :tricks ()}))
:current-trick ()
:round-start-player :p1
:cards cards
:round-count (/ (count cards) (count players))
:mode ""}))
Notes for mymain.clj:
(defn who-won-trick [trick]
(eval (read-string (read-line))))
Some unused function, same problems as above.
(defn share-card-to-player [game players-cards]
(assoc game
:players
(assoc
(get game :players)
(first players-cards)
(assoc (get (game :players) (first players-cards))
:cards
(second players-cards)))))
Use assoc-in and some destructuring, something like this:
(defn share-card-to-player [game [player cards]]
(assoc-in game [:players player :cards] cards))
Your next function:
(defn shuffle-and-share-cards [myshuffle game]
(reduce share-card-to-player game
(map vector
(keys (get game :players))
(->> (get game :cards)
(myshuffle)
(partition (/ (count (get game :cards))
(count (get game :players))))))))
You can also destructure hash-maps, so I would rewrite this as:
(defn shuffle-and-share-cards [{:keys [players cards] :as game}]
(let [card-piles (->> cards
shuffle
(partition (/ (count cards)
(count players))))]
(reduce share-card-to-player game
(map vector
(keys players)
card-piles))))
Next functions:
(defn announce [game]
game)
(defn play-card [game curr-player]
(println curr-player)
(println game)
(let [played-card (io/play-card-inp)]
(->
(assoc-in game [:players curr-player :cards]
(remove #(= played-card %) (get-in game [:players curr-player :cards])))
(assoc-in [:current-trick]
(conj (game [:current-trick]) played-card)))))
announce is useless and update and update-in are better here:
(defn play-card [game curr-player]
(println curr-player)
(println game)
(let [played-card (io/play-card-inp)]
(-> game
(update-in [:players curr-player :cards] #(remove #{played-card} %))
(update :current-trick conj played-card))))
And finally, the last two functions:
(defn play-game-reduce []
(let [game-init
(->>
(io/initialize-cards-and-players)
(shuffle-and-share-cards myio/myshuffle)
(announce))
play-round
(reduce play-card (assoc-in game-init [:current-trick] '()) [:p1 :p2 :p3 :p4])]
(reduce play-round game-init (range (get game-init :round-count)))))
(defn play-game []
(let [game-init
(->>
(io/initialize-cards-and-players)
(shuffle-and-share-cards io/myshuffle)
(announce))]
(loop [round 1 game game-init]
(let [game-next (loop [curr-player 1 game-next game]
(if (> curr-player 4)
game-next
(recur (inc curr-player)
(play-card game-next (keyword (str "p" curr-player))))))]
(if (> round 2)
game-next
(recur (inc round) game-next))))))
loop/recur will be probably more readable, but two reduce should also work:
(defn play-game-reduce []
(let [game-init (-> (io/new-game)
shuffle-and-share-cards)]
(reduce (fn [game round]
(reduce play-card (assoc-in game [:current-trick] '()) [:p1 :p2 :p3 :p4]))
game-init
(range (get game-init :round-count)))))
(play-game-reduce)
Version with one reduce:
(defn play-game-reduce []
(let [game-init (-> (io/new-game)
shuffle-and-share-cards)
turns (for [round (range (:round-count game-init))
player [:p1 :p2 :p3 :p4]]
[round player])]
(reduce (fn [game [round player]]
(let [state (cond-> game
(= player (:round-start-player game)) (assoc-in [:current-trick] '()))]
(play-card state player)))
game-init
turns)))
And I also noticed that there's no validation of whether the current player can really play inserted card.
OK, I watched the talk (for the record, it gives a 5 minute overview of FP, then discusses error handling in pipelines in F#.
I didn't really care for the content of the video.
Clojure uses Exceptions for error handling, so a Clojure function always has only one output. Therefore the whole bind and map thing in the video doesn't apply.
I haven't looked at F# much before, but after watching that video I think it over-complicates things without much benefit.
I would like to be able to mock MyFunction however I need the mock to return different values when MyFunction is called.
Is it possible to use with-redefs to return different values based on the call order of a function?
(testing "POST /foo/bar and return ok"
(with-redefs [->Baz (fn [_]
(reify MyProtocol (MyFunction [_] [{:something 1}]))
(reify MyProtocol (MyFunction [_] [{:something 2}])))]
(let [response (routes/foo {:request-method :post
:uri "/foo/bar"
:query-params {}
})]
(is (= (:status response) 200)))))
You could use a mutable collection of the return values, then return/remove values from it on each call.
(defn foo [x] (inc x)) ;; example fn to be mocked
If you wanted to mock three calls to foo returning 1, 2, and 3 respectively:
(with-redefs [foo (let [results (atom [1 2 3])]
(fn [_] (ffirst (swap-vals! results rest))))]
(prn (foo 0))
(prn (foo 0))
(prn (foo 0))
;; additional calls would return nil
(prn (foo 0)))
;; 1
;; 2
;; 3
;; nil
That uses swap-vals! to get the old/new values of the atom, but requires Clojure 1.9 or greater.
If you don't have swap-vals! you could do it (less atomically) like this:
(with-redefs [foo (let [results (atom [1 2 3])]
(fn [_]
(let [result (first #results)]
(swap! results rest)
result)))]
...)
We use Picomock for this, and to assert on the parameters for each call, and to assert on the number of calls. Recommended!
Why calling this function doesn't print anything?
(defn test-go-loop []
(go (for [a (cycle [:a :b :c])]
(do (println a) (<! (timeout 1000))))))
for is lazily evaluated, and nothing in your code is asking for the result of that for. Try doseq:
(defn test-go-loop []
(go (doseq [a (cycle [:a :b :c])]
(println a)
(<! (timeout 1000)))))
I have a namespace like this:
(ns foo.core)
(def ^:dynamic *debug-fn*
"A function taking arguments [bar baz]"
nil)
(defn bar-info
[bar _]
(println bar))
(defn baz-info
[_ baz]
(println baz))
(defn do-stuff
[bar baz]
(when *debug-fn* (*debug-fn* bar baz)))
(defn -main
[& {:keys [debug-fn]}]
(binding [*debug-fn* (symbol debug-fn)] ;; THIS WON'T WORK!
(do-stuff 27 42)))
What I would like to do is allow a debug function to be specified from the command line like this: lein run bar-info or lein run baz-info.
I'm not sure how to take the string specified as a command-line argument and turn it into the namespace-qualified function to bind. Do I need a macro to do this?
Use ns-resolve, you will need to specify namespace where your function is defined though.
user=> (defn f [n] (* n n n))
#'user/f
user=> ((ns-resolve *ns* (symbol "f")) 10)
1000
Use alter-var-root:
user=> (doc alter-var-root)
-------------------------
clojure.core/alter-var-root
([v f & args])
Atomically alters the root binding of var v by applying f to its
current value plus any args
nil
user=> (alter-var-root #'*debug-fn* (fn [v] (fn [x] (println x) x)))
#<user$eval171$fn__172$fn__173 user$eval171$fn__172$fn__173#7c93d88e>
user=> (*debug-fn* 1)
1
1
Though I've accepted Guillermo's answer above, I figured that it might also be useful to add the solution I ended up going with:
(def debug-fns
{:bar-info (fn [bar _] (println bar))
:baz-info (fn [_ baz] (println baz))
(def active-debug-fns (atom []))
(defn activate-debug-fn!
[fn-key]
(let [f (debug-fns fn-key)]
(if f
(swap! active-debug-fns conj f)
(warn (str "Debug function " fn-key " not found! Available functions are: "
(join " " (map name (keys debug-fns))))))))
(defn debug-fn-keys
[args]
(if (args "--debug")
(split (or (args "--debug") "") #",")
[]))
(defn do-stuff
[bar baz]
(doseq [f #active-debug-fns]
(f bar baz)))
(defn -main
[& args]
(let [args (apply hash-map args)]
(doseq [f (debug-fn-keys args)]
(activate-debug-fn! (keyword k)))
(do-stuff 27 42)))
So now you can say something like lein run --debug bar-info to get info on bars, or lein run --debug bar,baz to get info on both bars and bazes.
Any suggestions to make this more idiomatic will be happily accepted and edited in. :)
I want to traverse a vector tree that represents hiccup data structures:
[:div {:class "special"} [:btn-grp '("Hello" "Hi")]]
Then I want to dispatch on the keyword of the vector, if a multimethod has been defined for the keyword, then it would return another set of vectors, which will replace the original tag.
For example, the above structure will be transformed to:
[:div {:class "special"} [:div [:button "Hello"] [:button "Hi"]]]
The custom multimethod will receive the list ("hello" "hi") as parameters. It will then return the div containing the buttons.
How do I write a function that traverses the vector and dispatches on the keyword with everything else in the form as parameter, and then replaces the current form with the returned form ?
(ns customtags
(:require [clojure.walk :as walk]))
(def customtags (atom {}))
(defn add-custom-tag [tag f]
(swap! customtags assoc tag f))
(defn try-transform [[tag & params :as coll]]
(if-let [f (get #customtags tag)]
(apply f params)
coll))
(defmacro defcustomtag [tag params & body]
`(add-custom-tag ~tag (fn ~params ~#body)))
(defn apply-custom-tags [coll]
(walk/prewalk
(fn [x]
(if (vector? x)
(try-transform x)
x)) coll))
Using it:
(require '[customtags :as ct])
(ct/defcustomtag :btn-grp [& coll] (into [:div] (map (fn [x] [:button x]) coll)))
(ct/defcustomtag :button [name] [:input {:type "button" :id name}])
(def data [:div {:class "special"} [:btn-grp "Hello" "Hi"]])
(ct/apply-custom-tags data)
[:div {:class "special"} [:div [:input {:type "button", :id "Hello"}] [:input {:type "button", :id "Hi"}]]]