clojure equality when comparing pseudo class instances - clojure

I have this clojure code from the book Functional Programming for the objet-oriented programmer:
(def point {:x 1, :y 2, :__class_symbol__ 'Point})
(def Point
(fn [x y]
{:x x,
:y y
:__class_symbol__ 'Point}))
(def x :x)
(def y :y)
(def class-of :__class_symbol__)
(def shift
(fn [this xinc yinc]
(Point (+ (x this) xinc)
(+ (y this) yinc))))
(defn add [left right]
(shift left (x right) (y right)))
(def Triangle
(fn [point1 point2 point3]
{:point1 point1, :point2 point2, :point3 point3
:__class_symbol__ 'Triangle}))
(def right-triangle (Triangle (Point 0 0)
(Point 0 1)
(Point 1 0)))
(def equal-right-triangle (Triangle (Point 0 0)
(Point 0 1)
(Point 1 0)))
(def different-triangle (Triangle (Point 0 0)
(Point 0 10)
(Point 10 0)))
(defn equal-triangles? [& args]
(apply = args))
(defn make [klass & args]
(apply klass args))
I have created this function to check equality of the Triangle pseudo class:
(defn equal-triangles? [& args]
(apply = args))
It is obvious why this expression is true
(= right-triangle right-triangle)
It is also obvious why this expression is not true
(equal-triangles? right-triangle different-triangle)
What is not obvious is why this expression is true:
(= right-triangle equal-right-triangle)
They both have the Point values but I would have thought they would be different because I am probably still thinking in terms of instances.
Can anyone shed any light on why the last expression is true?

user=> (= right-triangle equal-right-triangle)
true
user=> (identical? right-triangle equal-right-triangle)
false
user=> (doc =)
-------------------------
... Clojure's immutable data structures define equals() (and thus =)
as a value, not an identity, comparison.
user=> (doc identical?)
-------------------------
...Tests if 2 arguments are the same object

Related

Idiomatic Creation of Hash-Map

I'd like to create a hash-map that has n number of key-value pairs created in sets of 3 where the sets do not intersect, e.g. [(34 false) (35 false) (36 false)] && [(24 false) (25 false) (26 false)] -> {34 false 35 false 36 false 24 false 25 false 26 false}
EDIT:
To play/practice with Clojure, I'm attempting to implement an idiomatic version of the battleship board game. I decided to store the battleship coordinates in a hash-map where the keys are coordinates and the values are booleans indicating whether that section of the ship has been hit. The specific piece of code below is supposed to
Select an axis (horizontal or vertical)
Select a coordinate for the bow of the ship
"Build" the rest of the ship (3 coordinates in total) by increasing the x or y value accordingly, e.g. {"10" false "11" false "12" false}. Note the "10" translates into the second row of a matrix, first column.
Note: Before adding the ship to the hash-map of coordinates the new ship coordinates must be checked to ensure that an intersection does not exist. If it does, the ship must be "re-built."
To that end, I've created the code below. It has 2 issues:
Executing the function results in the following exception from the use of the 'acc' accumulator:
clojure.lang.LazySeq cannot be cast to clojure.lang.Associative
The result of the function is not a single hash-map, but rather a list of n hash-maps
Using idiomatic clojure, how can I achieve my goal?
(defn launch
[n]
(loop [cnt n acc {}]
(if (= cnt 0)
acc
(recur
(- cnt 1)
((fn []
(let [axis (rand-int 2)]
(if (= axis 0)
(let [x (rand-int 8) y (rand-int 10)]
(for [k (range 3)]
(assoc acc (str y (+ x k)) false)))
(let [x (rand-int 10) y (rand-int 8)]
(for [k (range 3)]
(assoc acc (str (+ y k) x) false)))))))))))
that's how i would rewrite it:
(defn create-key [axis-val i]
(if axis-val
(str (rand-int 10) (+ (rand-int 8) i))
(str (+ (rand-int 8) i) (rand-int 10))))
(defn launch [n]
(reduce (fn [acc axis]
(reduce #(assoc % (create-key axis %2) false)
acc
(range 3)))
{}
(repeatedly n #(zero? (rand-int 2)))))
in repl:
user> (launch 5)
{"40" false, "07" false, "19" false,
"46" false, "87" false, "47" false,
"41" false, "62" false, "86" false}
or (in case you don't like reduce):
(defn launch [n]
(zipmap (mapcat #(map (partial create-key %) (range 3))
(repeatedly n #(zero? (rand-int 2))))
(repeat false)))
the third variant is to use list comprehension to generate keys:
(defn launch [n]
(zipmap (for [_ (range n)
:let [axis (zero? (rand-int 2))]
i (range 3)]
(create-key axis i))
(repeat false)))
all three of them are idiomatic ones, i guess, so it's up to you to choose one, according to your own preferred programming style.
notice that the resulting keys are shuffled inside the map, because unsorted maps don't preserve order. If it is important, you should use sorted-map
What about your variant, the one generating error is this:
(for [k (range 3)] (assoc acc (str y (+ x k)) false))
it doesn't put all the keys to one map, rather it generates a seq of three items equalling (assoc acc k false):
(let [acc {}]
(for [k (range 3)] (assoc acc k false)))
;;=> ({0 false} {1 false} {2 false})
to do what you want, you use reduce:
(let [acc {}]
(reduce #(assoc %1 %2 false) acc (range 3)))
;;=> {0 false, 1 false, 2 false}
leetwinski has given a more concise answer, but I thought I would post this anyway, since I basically left your structure intact, and this may help you see the error a bit more clearly.
First, I am not sure why you were rebinding acc to the value of an anonymous function call. Your let will happily return a result; so, you should probably do some thinking about why you thought it was necessary to create an anonymous function.
Second, the problem is that for returns a lazy seq, and you are binding this to what you think is a map data structure. This explains why it works fine for cases 0 and 1, but when you use a value of 2 it fails.
Since I don't really fully understand what you're trying to accomplish, here is your original code, modified to work. Disclaimer--this is not really idiomatic and not how I would write it, but I'm posting because it may be helpful to see versus the original, since it actually works.
(defn launch
[n]
(loop [cnt n
acc {}]
(if (= cnt 0)
acc
(recur
(dec cnt)
(into acc
(let [axis (rand-int 2)]
(if (= axis 0)
(let [x (rand-int 8) y (rand-int 10)]
(map #(hash-map (str y (+ x %)) false) (range 3)))
(let [x (rand-int 10) y (rand-int 8)]
(map #(hash-map (str (+ y %) x) false) (range 3))))))))))

Clojure value equality and sets

If I have a custom type and I use it to create two separate instances having precisely the same values, what method could I use to determine that the two things are equivalent? identical? and = and == don't seem to work. I would have expected there to be some protocol for setting up type comparisons. Ultimately, I would like it so that it would be impossible to add equivalent things to a set.
(deftype Ref [id])
(def r1 (->Ref 1))
(def r2 (->Ref 1))
(= r1 r2) ;false rather than true
(def refs #{})
(conj refs r1 r2) ;adds both, but want one
= works with defrecord, but how would I define = for deftype?
In your deftype, extend Object and implement equals to give them equality semantics:
(deftype Ref [id]
Object
(equals [_ other] (= id (.id other))))
Set containment will also require hashcode support:
(deftype Ref [id]
Object
(equals [_ other] (= id (.id other)))
(hashCode [_] id)
clojure.lang.IHashEq
(hasheq [_] id))
I implemented both Java hash support and Clojure hasheq support there. Implementing IHashEq will be faster.
defrecord already has this behavior you describe:
user=> (defrecord Point [x y])
user.Point
user=> (= (Point. 0 0) (Point. 0 0))
true
user=> (into #{} [(Point. 0 0) (Point. 1 1) (Point. 0 0)])
#{#user.Point{:x 1, :y 1} #user.Point{:x 0, :y 0}}
deftype on the other hand does not implement Clojure's usual structural equality by default (nor the readable print method that defstruct gives us):
user=> (deftype Pair [a b])
user.Pair
user=> (= (Pair. 0 0) (Pair. 0 0))
false
user=> (into #{} [(Pair. 0 0) (Pair. 1 1) (Pair. 0 0)])
#{#<Pair user.Pair#5de3182> #<Pair user.Pair#6497d63> #<Pair user.Pair#38eed810>}
That said, deftype is more powerful, and you can make it behave as we like:
user=> (deftype Tuple [a b]
Object
(equals [this other]
(and (= (.a this) (.a other))
(= (.b this) (.b other))))
(toString [this]
(str "<" (.a this) "," (.b this) ">"))
(hashCode [this]
(hash {:a (.a this) :b (.b this)}))
Comparable
(compareTo [this that]
(compare [(.a this) (.b this)]
[(.a that) (.b that)])))
user.Tuple
user=> (= (Tuple. 0 0) (Tuple. 0 0))
true
user=> (into #{} [(Tuple. 0 0) (Tuple. 1 1) (Tuple. 0 0)])
#{#<Tuple <0,0>> #<Tuple <1,1>>}

make-keyword-map in Clojure - Idiomatic?

I have been writing some Clojure recently, and I found myself using the following pattern frequently enough:
(let [x (bam)
y (boom)]
{:x x
:y y})
So I went ahead and wrote the following macro:
(defmacro make-keyword-map [& syms]
`(hash-map ~#(mapcat (fn [s] [(keyword (name s)) s]) syms)))
With that, code now looks like:
(let [x (bam)
y (boom)]
(make-keyword-map x y)
Would this sort of macro be considered idiomatic? Or am I doing something wrong, and missing some already established pattern to deal with something of this sort?
Note, that you can also replace all of:
(let [x (bam)
y (boom)]
{:x x
:y y})
with just:
{:x (bam)
:y (boom)}
which will evaluate to the same thing.
If your let expressions depend on one another, then how about a macro like so:
(defmacro make-keyword-map [& let-body]
(let [keywords-vals (flatten (map (juxt keyword identity)
(map first (partition 2 let-body))))]
`(let ~(vec let-body)
(hash-map ~#keywords-vals))))
that way (make-keyword-map x (foo 1) y (bar 2) z (zoom x)) expands to:
(clojure.core/let [x (foo 1) y (bar 2) z (zoom x)]
(clojure.core/hash-map :x x :y y :z z))
So something like this would work:
user=> (defn foo [x] (+ x 1))
#'user/foo
user=> (defn bar [x] (* x 2))
#'user/bar
user=> (defn zoom [x] [(* x 100) "zoom!"])
#'user/zoom
user=> (make-keyword-map x (foo 1) y (bar 2) z (zoom x))
{:z [200 "zoom!"], :y 4, :x 2}
Not sure how idiomatic that is, but it also saves you a let, compared to your original example.

Clojure: Why does if-let only allow 2 forms in the binding vector?

When I use if-let like
(if-let [a 2 b nil] (+ a b))
I get an IllegalArgumentException:
clojure.core/if-let requires exactly 2 forms in binding vector...
Similar for when-let...
This is not what I would expect. If-let could try all bindings and break when one fails and evaluate the else expression.
The same complaint can be found in the comments at clojuredocs. I found an answer here which did not really satisfy since the poster seems to have the equivalent of a nested if-let-structure in mind.
What reasons are there to limit the bindings of the *-let macros?
UPDATE:
As it seems to be unclear, what my expectations of if-let are:
It should evaluate all bindings sequentially.
When all succeed, it should evaluate the 'then'-case.
If one binding fails it should immediately break and evaluate the 'else'-case.
In case of failure the bindings, even succeeded ones, should not be available in the 'else'-expression
Try this out:
(defmacro if-let-multi
([bindings then-exp]
(let [values (take-nth 2 (rest bindings))]
`(if (and ~#values) (let ~bindings ~then-exp) false)))
([bindings then-exp else-exp]
(let [values (take-nth 2 (rest bindings))]
`(if (and ~#values) (let ~bindings ~then-exp) ~else-exp))))
Here it is in action:
user> (if-let-multi [a 2 b nil] (+ a b))
false
user> (if-let-multi [a 2 b 3] (+ a b))
5
user> (if-let-multi [a 2 b nil] (+ a b) "NO WAY")
"NO WAY"
if-let and let serve different purposes and if-let is not just a more restricted version of let. of instance if-let differs from let in that the value is bound only for the then clause and not the else.
user> (if-let [ans (+ 1 2 3)] ans :foo)
6
user> (if-let [ans (+ 1 2 3)] ans ans)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: ans in this context, compiling:(NO_SOURCE_PATH:1)
user> (let [ans (+ 1 2 3)] ans ans)
6
if-let is intended to make life easier in the case where you are binding a value simply to test and use it.
Try this out.
(defmacro if-lets
([bindings true-expr] `(if-lets ~bindings ~true-expr nil))
([bindings true-expr false-expr]
(cond
(or (not (seq bindings)) (not (zero? (rem (count bindings) 2))))
`(throw (IllegalArgumentException. "if-lets requires 2 or multiple of 2 forms in binding vector in user:1"))
(seq (drop 2 bindings))
`(if-let ~(vec (take 2 bindings))
(if-lets ~(vec (drop 2 bindings))
~true-expr
~false-expr)
~false-expr)
:else
`(if-let ~(vec bindings)
~true-expr
~false-expr))))
This macro passed these tests below.
(deftest ut-if-lets
(testing "if-lets macro (normal cases)"
(is (= 0 (if-lets [x 0] x)))
(is (= 0 (if-lets [x 0] x 1)))
(is (= 1 (if-lets [x nil] x 1)))
(is (= 0 (if-lets [x 0 y x] y)))
(is (= 0 (if-lets [x 0 y x] y 1)))
(is (= 1 (if-lets [x nil y x] y 1)))
(is (= 0 (if-lets [x 0 y x z y] z)))
(is (= 0 (if-lets [x 0 y x z y] z 1)))
(is (= 1 (if-lets [x nil y x z y] y 1)))
(is (= true (if-lets [x true] true false)))
(is (= false (if-lets [x false] true false)))
(is (= true (if-lets [x true y true] true false)))
(is (= false (if-lets [x false y true] true false)))
(is (= false (if-lets [x true y false] true false)))
(is (= true (if-lets [x true y true z true] true false)))
(is (= false (if-lets [x false y true z true] true false)))
(is (= false (if-lets [x true y false z true] true false)))
(is (= false (if-lets [x true y true z false] true false)))
)
)
(deftest ut-if-lets-ab
(testing "if-lets macro (abnormal cases)"
(is (= (try (if-lets [] true false) (catch Exception e (.getMessage e)))
"if-lets requires 2 or multiple of 2 forms in binding vector in user:1"))
(is (= (try (if-lets [x] true false) (catch Exception e (.getMessage e)))
"if-lets requires 2 or multiple of 2 forms in binding vector in user:1"))
(is (= (try (if-lets [x true y] true false) (catch Exception e (.getMessage e)))
"if-lets requires 2 or multiple of 2 forms in binding vector in user:1"))
)
)

Why applying seq on a LazySeq returns ChunkedCons?

(class (range 10))
;=> clojure.lang.LazySeq
(class (seq (range 10))
;=> clojure.lang.ChunkedCons
From my understanding, LazySeq is already an sequence, since:
(seq? (range 10))
;=> true
I guess I have an answer.
That's because using seq enforces the evaluation of the first element of LazySeq. Because seq returns nil when the collection & sequence is empty, it has to eval the element to decide that.
That's the exactly reason why rest is lazier than next, because (next s) is just (seq (rest s)).
To expand upon your answer (and because comments don't support new lines):
user=> (def r (range 10))
#'user/r
user=> (realized? r)
false
user=> (class r)
clojure.lang.LazySeq
user=> (def r2 (rest r))
#'user/r2
user=> (realized? r2)
ClassCastException clojure.lang.ChunkedCons cannot be cast to clojure.lang.IPending clojure.core/realized? (core.clj:6607)
user=> (class r2)
clojure.lang.ChunkedCons
user=> (realized? r)
true