In Clojure/Script the contains? function can be used to check if a subsequent get will succeed. I believe that the Clojure version will do the test without retrieving the value. The ClojureScript version uses get and does retrieve the value.
Is there an equivalent function that will test whether a path through a map, as used by get-in, will succeed? That does the test without retrieving the value?
For example, here is a naive implementation of contains-in? similar to the ClojureScript version of contains? that retrieves the value when doing the test.:
(def attrs {:attrs {:volume {:default "loud"}
:bass nil
:treble {:default nil}}})
(defn contains-in? [m ks]
(let [sentinel (js-obj)]
(if (identical? (get-in m ks sentinel) sentinel)
false
true)))
(defn needs-input? [attr-key]
(not (contains-in? attrs [:attrs attr-key :default])))
(println "volume: needs-input?: " (needs-input? :volume))
(println " bass: needs-input?: " (needs-input? :bass))
(println "treble: needs-input?: " (needs-input? :treble))
;=>volume: needs-input?: false
;=> bass: needs-input?: true
;=>treble: needs-input?: false
Anything in the map of "attributes" that does not contain a default value requires user input. (nil is an acceptable default, but a missing value is not.)
(defn contains-in? [m ks]
(let [ks (seq ks)]
(or (nil? ks)
(loop [m m, [k & ks] ks]
(and (try
(contains? m k)
(catch IllegalArgumentException _ false))
(or (nil? ks)
(recur (get m k) ks)))))))
BTW, looking at the relevant functions in the source of clojure.lang.RT, I noticed that the behavior of contains? and get can be a bit buggy when their first argument is a string or Java array, as is illustrated below:
user=> (contains? "a" :a)
IllegalArgumentException contains? not supported on type: java.lang.String
user=> (contains? "a" 0.5)
true
user=> (get "a" 0.5)
\a
user=> (contains? (to-array [0]) :a)
IllegalArgumentException contains? not supported on type: [Ljava.lang.Object;
user=> (contains? (to-array [0]) 0.5)
true
user=> (get (to-array [0]) 0.5)
0
user=> (contains? [0] :a)
false
user=> (contains? [0] 0.5)
false
user=> (get [0] 0.5)
nil
Related
I am new to Clojure and I'm learning how to write a program that can simplify logical expressions (just 'and' for now to figure out how things work first). For example:
(and-simplify '(and true)) => true
(and-simplify '(and x true)) => x
(and-simplify '(and true false x)) => false
(and-simplify '(and x y z true)) => (and x y z)
I already knew how to simplify two arguments, that everything I can do right now is:
(defn and-simplify []
(def x (and true false))
println x)
(and-simplify)
I've read this post and tried to modify my code a little bit but it doesn't seem to get me anywhere:
(defn and-simplify [&expr]
(def (and &expr))
)
What is the correct way that I should have done?
Here's my take on it.
(defn simplify-and
[[op & forms]]
(let [known-falsy? #(or (false? %) (nil? %))
known-truthy? #(and (not (symbol? %))
(not (seq? %))
(not (known-falsy? %)))
falsy-forms (filter known-falsy? forms)
unknown-forms (remove known-truthy? forms)]
(if (seq falsy-forms)
(first falsy-forms)
(case (count unknown-forms)
0 true
1 (first unknown-forms)
(cons op unknown-forms)))))
(comment (simplify-and `(and true 1 2 a)))
However, we can write a more generic simplify that uses multimethods to simplify lists, so that we can add more optimisations without modifying existing code. Here's that, with optimisations for and, or and + from clojure.core. This simplify only optimises lists based on namespace qualified names.
Check out the examples in the comment form. Hope it makes sense.
(defn- known-falsy? [form]
(or (false? form) (nil? form)))
(defn- known-truthy? [form]
(and (not (symbol? form))
(not (seq? form))
(not (known-falsy? form))))
(declare simplify)
(defmulti simplify-list first)
(defmethod simplify-list :default [form] form)
(defmethod simplify-list 'clojure.core/and
[[op & forms]]
(let [forms (mapv simplify forms)
falsy-forms (filter known-falsy? forms)
unknown-forms (remove known-truthy? forms)]
(if (seq falsy-forms)
(first falsy-forms)
(case (count unknown-forms)
0 true
1 (first unknown-forms)
(cons op unknown-forms)))))
(defmethod simplify-list 'clojure.core/or
[[op & forms]]
(let [forms (mapv simplify forms)
truthy-forms (filter known-truthy? forms)
unknown-forms (remove known-falsy? forms)]
(if (seq truthy-forms)
(first truthy-forms)
(case (count unknown-forms)
0 nil
1 (first unknown-forms)
(cons op unknown-forms)))))
(defmethod simplify-list 'clojure.core/+
[[op & forms]]
(let [{nums true non-nums false} (group-by number? (mapv simplify forms))
sum (apply + nums)]
(if (seq non-nums)
(cons op (cons sum non-nums))
sum)))
(defn simplify
"takes a Clojure form with resolved symbols and performs
peephole optimisations on it"
[form]
(cond (set? form) (into #{} (map simplify) form)
(vector? form) (mapv simplify form)
(map? form) (reduce-kv (fn [m k v] (assoc m (simplify k) (simplify v)))
{} form)
(seq? form) (simplify-list form)
:else form))
(comment
(simplify `(+ 1 2))
(simplify `(foo 1 2))
(simplify `(and true (+ 1 2 3 4 5 foo)))
(simplify `(or false x))
(simplify `(or false x nil y))
(simplify `(or false x (and y nil z) (+ 1 2)))
)
Is there an easy way in Clojure (maybe using specter) to filter collections depending on whether the an arbitrarily nested key with a known name contains an element ?
Ex. :
(def coll [{:res [{:a [{:thekey [
"the value I am looking for"
...
]
}
]}
{:res ...}
{:res ...}
]}])
Knowing that :a could have a different name, and that :thekey could be nested somewhere else.
Let's say I would like to do :
#(find-nested :thekey #{"the value I am looking for"} coll) ;; returns a vector containing the first element in coll (and maybe others)
use zippers.
in repl:
user> coll
[{:res [{:a [{:thekey ["the value I am looking for"]}]} {:res 1} {:res 1}]}]
user> (require '[clojure.zip :as z])
nil
user> (def cc (z/zipper coll? seq nil coll))
#'user/cc
user> (loop [x cc]
(if (= (z/node x) :thekey)
(z/node (z/next x))
(recur (z/next x))))
["the value I am looking for"]
update:
this version is flawed, since it doesn't care about :thekey being the key in a map, or just keyword in a vector, so it would give unneeded result for coll [[:thekey [1 2 3]]]. Here is an updated version:
(defn lookup-key [k coll]
(let [coll-zip (z/zipper coll? #(if (map? %) (vals %) %) nil coll)]
(loop [x coll-zip]
(when-not (z/end? x)
(if-let [v (-> x z/node k)] v (recur (z/next x)))))))
in repl:
user> (lookup-key :thekey coll)
["the value I am looking for"]
user> (lookup-key :absent coll)
nil
lets say we have the same keyword somewhere in a vector in a coll:
(def coll [{:res [:thekey
{:a [{:thekey ["the value I am looking for"]}]}
{:res 1} {:res 1}]}])
#'user/coll
user> (lookup-key :thekey coll)
["the value I am looking for"]
which is what we need.
clojure's pre-expr seems cool, but does it possible if I want to raise an Exception when the :pre is false?
thanks.
You can use Dire instead
(ns mytask
(:require [dire.core :refer [with-precondition! with-handler!]]))
(defn add-one [n]
(inc n))
(with-precondition! #'add-one
"An optional docstring."
;;; Name of the precondition
:not-two
(fn [n & args]
(not= n 2)))
(with-handler! #'add-one
{:precondition :not-two}
(fn [e & args] (apply str "Precondition failure for argument list: " (vector args))))
(add-one 2) ; => "Precondition failure for argument list: (2)"
Preconditions are conditions that must be true or else an exception is thrown. If you have a condition where you want an exception to be thrown when false, just complement the conditional or not the result.
user=> (defn magic? [n] (= 0 (rem n 42)))
#'user/magic?
user=> (defn foo [n] {:pre [(magic? n)]} n)
#'user/foo
user=> (foo 42)
42
user=> (defn bar [n] {:pre [(not (magic? n))]} n)
#'user/bar
user=> (bar 42)
AssertionError Assert failed: (not (magic? n)) user/bar
user=> (defn baz [n] {:pre [((complement magic?) n)]} n)
#'user/baz
user=> (baz 42)
AssertionError Assert failed: ((complement magic?) n) user/baz
I'm looking for a way to apply some defaults to map. I know the following works:
(defn apply-defaults
[needing-defaults]
(merge {:key1 (fn1 10)
:key2 (fn2 76)}
needing-defaults))
The issue with the above is that the value of fn1 and fn2 are evaluated even though needing-defaults might already have these keys - thus never needing them.
I've tried with merge-with but that doesn't seem to work. I'm quite new at this - any suggestions?
I'm ussually applying defaults with merge-with function:
(merge-with #(or %1 %2) my-map default-map)
But in your case it should be something like:
(reduce (fn [m [k v]]
(if (contains? m k) m (assoc m k (v))))
needing-defaults
defaults)
where defaults is a map of functions:
{ :key1 #(fn1 10)
:key2 #(fn2 76)}
if is a special form, so it newer evaluates its false branch.
See my example for more info.
If I understand your question correctly, how about this?
(defn apply-defaults [nd]
(into {:key1 (sf1 10) :key2 (sf2 76)} nd))
You could use a macro to generate the contains? checks and short circuit the function calls.
(defmacro merge-with-defaults [default-coll coll]
(let [ks (reduce (fn [a k] (conj a
`(not (contains? ~coll ~k))
`(assoc ~k ~(k default-coll))))
[] (keys default-coll))]
`(cond-> ~coll ~#ks)))
(defn apply-defaults [needing-defaults]
(merge-with-defaults {:key1 (fn1 10)
:key2 (fn2 76)}
needing-defaults))
Just remember to keep the function calls inside the call to merge-with-defaults to prevent evaluation.
Since you can merge nil into a map, you can use the if-not macro:
(merge {} nil {:a 1} nil) ;; {:a 1}
Try this:
(defn apply-defaults [col]
(merge col
(if-not (contains? col :key1) {:key1 (some-function1 10)})
(if-not (contains? col :key2) {:key2 (some-function2 76)})))
some-function1 and some-function2 will only be executed when col does not already have the key.
(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