Very weird - println is affecting test results

Hey guys, how are you?
I’m working on a project and I’m getting the weirdest error I’ve ever seen in my life. I’m quite new to clojure, so I really don’t know what I’m doing wrong. Following is the code I’m using to test my code:

		(testing "should attack dinosaurs next to border"
				(do (add-robot 0 0 "L"))
				(do (add-dino 0 1))
				(do (issue-instruction 0 0 "A"))
				(is (nil? (get-in @board [0 1])))))

First, @board is a 50x50 atom (a vector of vectors). Then, It adds a robot (which is represented by a string “RL” since its facing left), then it adds a dinosaur, which doesn’t face any direction, and then it issues an attack command which makes the robot attack dinosaurs directly above, below, to the left or to the right of it. Attacking a dinosaur means the program will change the position in which that dinosaur is in from “D” to nil in the board (which itself is an atom).
The code for add-dino is as follows:

(defn add-dino [x y]
			(if (or (out-of-bounds? x y)(not (is-spot-available? x y))) @board 
			(swap! board update-in [(Integer. x) (Integer. y)] str "D")))

The code for add-robot is as follows:

(defn add-robot [x y direction]
			(if (or (out-of-bounds? x y)(not (is-spot-available? x y))(nil? direction)) @board 
			(swap! board update-in [(Integer. x) (Integer. y)] str "R" direction)))

I’ve tested both of them in another testcases, so I’m fairly confident the problem is not with them. The code to attack is the following:

(defn perform-attack [[x y]] 
	(swap! board update-in [x y] (fn [arg] nil)))

(defn get-attack-coordinates [x y]
	(into [] (filter #(and (not(out-of-bounds?(:x %)(:y %)))
											(is-dino?(:x %)(:y %)))(coordinates x y))))

(defn attack [x y]	(map (fn [{:keys [x y]}] (perform-attack [x y])) (get-attack-coordinates x y)))

I’ve also tested get-attack-coordinates, which als seems to work. So, when I naively test my file (board_controller.clj) by placing the following commands to the end of it:

(add-robot 0 0 "L")
(add-dino 0 1)
(println (get-board))
(println (issue-instruction 0 0 "A"))

I’m able to see that board[0 1] is correctly updated to nil. However, in my test file (which I mentioned previously), the test only passes if I add a println command to the issue-instruction method. So, this test doesn’t pass:

(testing "should attack dinosaurs next to border"
 (do (add-robot 0 0 "L"))
 (do (add-dino 0 1))
 (do (issue-instruction 0 0 "A"))
 (is (nil? (get-in @board [0 1])))))

but this one does:

(testing "should attack dinosaurs next to border"
 (do (add-robot 0 0 "L"))
 (do (add-dino 0 1))
 (do (println (issue-instruction 0 0 "A")))
 (is (nil? (get-in @board [0 1])))))

Does anyone have any idea on why that could be happening?

Very high chance this is due to lazy sequences.

I think in this case, your attack function. You are trying to perform side effects using lazy evaluation, and that’s never a good thing.

So what happens is that map is a lazy loop. It won’t actually do the mapping over the collection unless the elements are explicitly accessed.

Try changing map to its eager (non-lazy) cousin mapv. That should fix your issue.

The reason println makes it work, is that printing forces the lazy collection to be realized by accessing all its elements in order to display their value.

This is something you have to just know. Pay attention to what functions are lazy and what functions are eager. The distinction matters a bit. If you are using lazy functions, do not do side effects with them. They are for pure logic only. If you want side effect you need to rely on eager functions, or if those don’t work, and you still need to use lazy functions, then you need to explicitly force realize their returned value.

Here’s some eager functions you should get familiar with, that you can use:

mapv
filterv
run!
reduce
loop
doseq
dotimes

All these are eager, and thus should be prefered if you are going to be doing side effects. The following two are what you can use to force realize a lazy sequence:

dorun
doall

1 Like

Hello @teogenesmoura! Welcome to ClojureVerse. This is just the category for this kind of question.

First, I think @didibus is right about this being an issue with lazyness. I’ve come to use doseq when I rely on side effects. It’s eager, and is meant to produce side effects. Here’s an example of doseq doing what we want, and map fooling us:

(let [counter (atom 0)]
  (doseq [_ (range 10)]
    (swap! counter inc))
  @counter)
;; => 10

(let [counter (atom 0)]
  (map (fn [_] (swap! counter inc))
       (range 10))
  @counter)
;; => 0

So the last one “doesn’t work” because the map never actually does anything, since we’re not asking for the values it produces. This is often the thing we want when we want to optimize for memory usage – it allows us to not allocate a bunch of lists, which can make your application faster and consume less memory.

However, we could “force the map to be realized” by printing its results:

(let [counter (atom 0)]
  (println (map (fn [_] (swap! counter inc))
                (range 10)))
  @counter)
;; => 10
;; Also prints (1 2 3 4 5 6 7 8 9 10)

Second, it’s idiomatic to avoid side effects (as much as possible) in Clojure. If I were to design the function signatures for your game, I’d include the board as a parameter and return a modified board in the functions, like these:

;; comma is whitespace, imagine real code instead of commas!
(defn add-dino [board x y]
  ,,,)
(defn add-robot [board x y direction]
  ,,,)
(defn perform-attack [board [x y]]
  ,,,)
(defn attack [board x y]
  ,,,)

I’m just to assume that functional programming is a good thing here – please reply if you want some fleshed out argument.

This is what we mean by functional programming, or programming without side effects in Clojure. If you want to learn more about functional programming with Clojure, I’d point you towards Clojure for the Brave and True, chapter 5.

Happy programming!

Teodor

Hi @didibus and @teodorlu, how are you?
First of all, thanks for answering! Both answers helped me solve the problem. Indeed it was a lazy sequence which wasn’t completely processed by the map function. I’ve changed the call from map to mapv and it worked properly. As for passing the board as parameter I agree but I’m not sure how to fit that into the project. I’m working on an API which is completely held in memory (thus the need to use an atom to represent the board) and so I always have the reference to the board in hands when I need it. Do you think it’s still more idiomatic to have the board being passed as a parameter and returned from within each method?
Lastly - and I apologize for the sudden change of subject - what is the best way to deal with Arity Exceptions? Suppose a method I wrote receives 2 parameters and for some reason only 1 is passed. How should I go in order for the program not to break? Should I add throw/catch blocks?
Thank you,
Teo (Just noticed we’re 2 Teos here haha :slight_smile:)

First question

Having a global atom for the state is fine, but, yes, ideally, you should pass the board into the board modifying functions. And the outermost function should be the one that get/swap it from the atom.

There’s a few reasons for this:

  1. The functions are easier to tests. Since you don’t need to to setup an implicit global context prior to calling them. In fact, if you do it like that, you can even now run your tests in parallel, as there’s no chance that a setup for testing one function leaks into the next one.

  2. You can easily extend your code to manage multiple boards.

  3. If you ever move where the board is stored. Say you want to move it to a database later? Or maybe you just want to rename it, or move it to a different namespace. Then you will have a lot less breakage in your code that you’ll need to refactor, because the board modifying functions aren’t coupled to the location of the atom.

  4. If you ever decide that you need to store more things in the atom, or you need to switch to a different reference type, like maybe switch to a ref, because you decided to parallelise certain aspect of the game, and now need better transactional invariants. You can do this without having to refactor all your board modifying code, because it isn’t coupled to the atom.

Now, if this is just a short lived toy project. And you’re sure you’ll never need to do any of these things. Won’t have tests. Won’t ever add boards. Will never move the board elsewhere. Won’t ever need to switch to something that’s not an atom. Then go ahead, couple them to the location and reference type, and take the board as an implicit context. In that case, it doesn’t really matter, and that might just be the quickest way to get going.

Also, as a learning exercise, you could start like that, and then refactor it to really see for yourself what difference it makes.

Second question

Definitly do not try/catch arity exceptions. You shouldn’t ever have those. They’re a bug, and you have to address their root cause.

Can you give an example of when you’re getting this? I could help suggest how.to refactor it so you don’t get these exceptions.

@teogenesmoura glad it helped! I’ve had this exact question myself. Teos learning Clojure For The Win!

I second @didibus’ reply for your questions.

Hi @teodorlu and @didibus!
Sorry for taking a while to respond, had to stay away for a few days. Anyway, I managed to get it all working before the due date, thank you for the help. I’ve refactored the code so that the board is now passed to all methods, as suggested by @didibus. It really does make the code less coupled to whatever is the board implementation, so it makes a lot of sense. As for the arity exception, I was just afraid that in the case the user didn’t pass any one of the arguments in the body of the API call it’d fall into an arity error. However, I figured I could just return a friendly error message in that case.
Thank you both!

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.