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>>}
Related
I'd like to create a list depending on the results of some functions. In Java (my background), I'd do something like:
List<String> messages = ...
if(condition 1)
messages.add(message 1);
if(condition 2)
messages.add(message 2);
...
if(condition N)
messages.add(message N);
In clojure, I think I'll need to create a list using let like the following (just dummy example):
(let [result
(vec
(if (= 1 1) "message1" "message2")
(if (= 1 0) "message3" "message4"))]
result)
I've also checked cond but I need to be appending the elements to the list considering all the validations (and cond breaks after one condition is satisfied).
Which way should I follow to achieve this?
If you want them to be conditionally added like in the Java example, you could use cond->, which does not short circuit:
(let [messages []]
(cond-> messages ; Conditionally thread through conj
(= 1 1) (conj "Message1")
(= 0 1) (conj "Message2")
(= 0 0) (conj "Message3")))
=> ["Message1" "Message3"]
If you want to conditionally add one or the other like your second example suggests however, you could just use plain conj with some if expressions:
(let [messages []]
(conj messages
(if (= 1 1) "Message1" "Message2")
(if (= 0 1) "Message3" "Message4")))
=> ["Message1" "Message4"]
And I'll note that your original attempt almost worked. Instead of vec, you could have used vector, or just a vector literal:
(let [messages [(if (= 1 1) "Message1" "Message2")
(if (= 1 0) "Message3" "Message4")]]
messages)
=> ["Message1" "Message4"]
Although, this is would only be beneficial if you didn't already have a messages populated that you wanted to add to. If that was the case, you'd have to use concat or into:
(let [old-messages ["old stuff"]
messages [(if (= 1 1) "Message1" "Message2")
(if (= 1 0) "Message3" "Message4")]]
(into old-messages messages))
=> ["old stuff" "Message1" "Message4"]
Take a look at cond->.
For example, your Java example could be written like:
(cond-> (some-fn-returning-messages)
(= 1 1) (conj "message1")
(= 1 2) (conj "message2")
...
(= 1 n) (conj "messagen"))
I see several answers pointing to the cond-> macro which appears to match your request most closely in that it is nearest to the style outlined in your question.
Depending on the number of conditions you have, your question seems like a good candiate for simply using filter.
(def nums (range 10))
(filter #(or (even? %) (= 7 %)) nums)
If you have a bunch of conditions (functions), and "or-ing" them together would be unwieldy, you can use some-fn.
Numbers from 0-19 that are either even, divisible by 7, greater than 17, or exactly equal to 1. Stupid example I know, just wanted to show a simple use-case.
(filter (some-fn
even?
#(zero? (mod % 7))
#(> % 17)
#(= 1 %))
(range 20))
Looks like everyone had the same idea! I did mine with keywords:
(ns tst.demo.core
(:use tupelo.core demo.core tupelo.test))
(defn accum
[conds]
(cond-> [] ; append to the vector in order 1,2,3
(contains? conds :cond-1) (conj :msg-1)
(contains? conds :cond-2) (conj :msg-2)
(contains? conds :cond-3) (conj :msg-3)))
(dotest
(is= [:msg-1] (accum #{:cond-1}))
(is= [:msg-1 :msg-3] (accum #{:cond-1 :cond-3}))
(is= [:msg-1 :msg-2] (accum #{:cond-2 :cond-1}))
(is= [:msg-2 :msg-3] (accum #{:cond-2 :cond-3}))
(is= [:msg-1 :msg-2 :msg-3] (accum #{:cond-3 :cond-2 :cond-1 })) ; note sets are unsorted
)
If you want more power, you can use cond-it-> from the Tupelo library. It threads the target value through both the condition and the action forms, and uses the special symbol it to show where the threaded value is to be placed. This modified example shows a 4th condition where, "msg-3 is jealous of msg-1" and always boots it out of the result:
(ns tst.demo.core
(:use tupelo.core demo.core tupelo.test))
(defn accum
[conds]
(cond-it-> #{} ; accumulate result in a set
(contains? conds :cond-1) (conj it :msg-1)
(contains? conds :cond-2) (conj it :msg-2)
(contains? conds :cond-3) (conj it :msg-3)
(contains? it :msg-3) (disj it :msg-1) ; :msg-3 doesn't like :msg-1
))
; remember that sets are unsorted
(dotest
(is= #{:msg-1} (accum #{:cond-1}))
(is= #{:msg-3} (accum #{:cond-1 :cond-3}))
(is= #{:msg-1 :msg-2} (accum #{:cond-2 :cond-1}))
(is= #{:msg-2 :msg-3} (accum #{:cond-2 :cond-3}))
(is= #{:msg-2 :msg-3} (accum #{:cond-3 :cond-2 :cond-1 }))
)
Not necessarily relevant to your use case, and certainly not a mainstream solution, but once in a while I like cl-format's conditional expressions:
(require '[clojure.pprint :refer [cl-format]])
(require '[clojure.data.generators :as g])
(cl-format nil
"~:[He~;She~] ~:[did~;did not~] ~:[thought about it~;care~]"
(g/boolean) (g/boolean) (g/boolean))
A typical case would be validating a piece of data to produce an error list.
I would construct a table that maps condition to message:
(def error->message-table
{condition1 message1
condition2 message2
...})
Note that the conditions are functions. Since we can never properly recognise functions by value, you could make this table a sequence of pairs.
However you implement the table, all we have to do is collect the messages for the predicates that apply:
(defn messages [stuff]
(->> error->message-table
(filter (fn [pair] ((first pair) stuff)))
(map second)))
Without a coherent example, it's difficult to be more explicit.
First-class functions and the packaged control structures within filter and map give us the means to express the algorithm briefly and clearly, isolating the content into a data structure.
In Clojure, is there a way to make a var constant such that it can be used in case statements?
e.g.
(def a 1)
(def b 2)
(let [x 1]
(case x
a :1
b :2
:none))
=> :none
I understand I can use something like cond or condp to get around this, but it would be nice if I could define something that does not require further evaluation so I could use case.
Related and answer stolen from it:
As the docstring tells you: No you cannot do this. You can use Chas Emericks macro and do this however:
(defmacro case+
"Same as case, but evaluates dispatch values, needed for referring to
class and def'ed constants as well as java.util.Enum instances."
[value & clauses]
(let [clauses (partition 2 2 nil clauses)
default (when (-> clauses last count (== 1))
(last clauses))
clauses (if default (drop-last clauses) clauses)
eval-dispatch (fn [d]
(if (list? d)
(map eval d)
(eval d)))]
`(case ~value
~#(concat (->> clauses
(map #(-> % first eval-dispatch (list (second %))))
(mapcat identity))
default))))
Thus:
(def ^:const a 1)
(def ^:const b 2)
(let [x 1]
(case+ x
a :1
b :2
:none))
=> :1
An alternative (which is nice since it's more powerful) is to use core.match's functionality. Though you can only match against local bindings:
(let [x 2
a a
b b]
(match x
a :1
b :2
:none))
=> :2
You can also use clojure.core/condp for the job:
(def a 1)
(def b 2)
(let [x 1]
(condp = x
a :1
b :2
:none))
#=> :1
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
I'm trying this:
(hash-set (when (= a 1) x))
I'm expecting #{x} if a equals to 1 and an empty set #{} otherwise. But I'm getting #{nil} instead of any empty set. How to re-write the statement?
ps. A workaround, but it looks ugly:
(filter #(not (nil? %)) (hash-set (when (= a 1) x)))
The solution:
(apply hash-set (when (= a 1) (list x)))
You could use:
(apply hash-set (when (= a 1) x))
I'm assuming that a and x are variables, for example via:
(defn foo [a & x] (apply hash-set (when (= a 1) x)))
I made the parameter x optional since you don't need to provide it if a not equals 1.
Another way:
(into #{} (when (= a 1) [x]))
If you like the word hash-set in your code, you can do:
(apply hash-set (when (= a 1) [x]))
You could do:
(if (= a 1) #{x} #{})
(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