Expression for "a member in set" with plumatic Schema - clojure

I want to write a schema where an element e in a data structure can be any member in a set S of items; e ∈ S. With Clojure Spec this is expressed with a set like:
(spec/def ::key-set #{:subprocess :leiningen :nrepl})
(gen/sample (spec/gen ::key-set))
; => (:nrepl :subprocess :subprocess :leiningen :subprocess :nrepl :subprocess :leiningen :nrepl :subprocess)
for a set of keywords.
In Schema however a set is used to denote a set of things rather than one element member of a set. So how do I in Schema express that I want one member out of a set?

schema.core/enum is what you're looking for.
user=> (schema.core/validate (schema.core/enum "a" "b" "c") "a")
"a"
=> (schema.core/validate (schema.core/enum "a" "b" "c") "z")
clojure.lang.ExceptionInfo: Value does not match schema: (not (#{"a" "b" "c"} "z"))

Related

Why is the hash map get returning nil after passing through the hash map as a function argument?

I am very new to clojure so this may have a simple fix. I have two examples of code that I can't seem to find the difference in why one works and the other doesn't. The first is:
(defn example []
(def demokeys (hash-map "z" 1 "b" 2 "a" 3))
(println demokeys)
(println (get demokeys "b")))
(example)
which is from https://www.tutorialspoint.com/clojure/clojure_maps_get.htm. This works the way I expect it to, with it printing the hash map and then a 2 on the next line.
The second example is:
(defn bill-total [bill]
(println bill)
(println (get bill "b"))
(println "sometext")
)
(bill-total[(hash-map "z" 1 "b" 2 "a" 3)])
which prints the hashmap, then nil, then sometext. Why does it not correctly print the 2 as it does in the previous example?
First of all, don't use def within defn unless you need dynamic runtime declarations (and even then, you should probably reconsider). Instead, use let for local bindings.
As to your main question, you have wrapped the map in a vector, with those [] here: (bill-total [...]). When calling a function, you don't need to repeat its arguments vector's brackets - call it just like you call println or hash-map, because they're also just regular functions.
So in the end, it should be (bill-total (hash-map "z" 1 "b" 2 "a" 3)).
As a final note, there's rarely a need to explicitly use hash-map, especially when you already know the keys and values. So, instead of (hash-map ...) you can use the map literal and write {...}, just like in {"z" 1, "b" 2, "a" 3} (I used commas here just for readability since Clojure ignores them).

Changing the value in a list of vectors

I know that if I define team as:
(def team ["A" "B" "C" "D"])
I can change the value "B" to "E" by
(assoc team 1 "E") > ["A" "E" "C" "D"]
If I now have:
(def teams '(["A" "B" "C"] ["D" "E" "F"] ["G" "H" "I"]))
How would I change "B" to "1" for example
I thought you might have to use assoc-in but that doesn't seem to be working or maybe I made a mistake. I tried
(assoc-in teams [0 1] "1")
You almost had it. You are using a list, when you should be using a vector, to do what you want to do:
(def teams [["A" "B" "C"] ["D" "E" "F"] ["G" "H" "I"]])
The reason for this is that a vector is an associative structure (i.e., it is a structure with key/value pairs). It's like a map, which is associative, except that for a vector, the key is the index in the vector. Contrast this with a list, which cannot be indexed into, and must be iterated to obtain an element. So, to use assoc-in, you need to use an associative structure, and a list won't do.
The error you got was: clojure.lang.PersistentList cannot be cast to clojure.lang.Associative, and a look at the source shows this:
IPersistentList gives:
public interface IPersistentList extends Sequential, IPersistentStack
Contrast with the Associative IPersistentVector:
public interface IPersistentVector extends Associative, Sequential, ...

Using Plumatic Schema to coerce to bigdec

I have incoming data of the type {:loan/amount 1200}.
Would it be possible to use plumatic Schema to coerce this to {:loan/amount 1200M}, ie coercing numbers (or even strings of digits) to bigdecimals?
I don't how to define a new data type (like s/Bigdec) and then make sure it uses clojure.core/bigdec to coerce a certain value to a java.math.BigDecimal.
There are two separate concepts in Schema: validation and coercion.
For the first one you need to define your schema. Classes are treated as schemas so you don't need to create a custom one for java.math.BigDecimal. Your schema might look like:
(require '[schema.core :as s])
(require '[schema.coerce :as c])
(s/defschema Loan {:loan/amount java.math.BigDecimal})
Now you can validate your data against the schema:
(s/validate Loan {:loan/amount 10M})
;; => {:loan/amount 10M}
Now if you have some data that you would like to coerce you need to define a coercion function which is a matcher from a desired target schema (java.math.BigDecimal in your case) to a function that will convert the actual value to the desired bigdec value.
(def safe-bigdec (c/safe bigdec)
schema.coerce/safe is a utility function which wraps the original function and if the original one throws an exception when called, safe will return original input value instead of throwing the exception.
Our matcher function will check if the current schema element is BigDecimal and return converting function or nil otherwise (meaning there is no coercion for other types):
(defn big-decimal-matcher [schema]
(when (= java.math.BigDecimal schema)
safe-bigdec))
And finally we need a coercer for performing actual coercion:
(def loan-coercer (c/coercer Loan big-decimal-matcher))
With all the setup we can now use our coercer:
(loan-coercer {:loan/amount "12.34"})
;; => {:loan/amount 12.34M}
(loan-coercer {:loan/amount 1234})
;; => {:loan/amount 1234M}
(loan-coercer {:loan/amount "abc"})
;; => #schema.utils.ErrorContainer{:error {:loan/amount (not (instance? java.math.BigDecimal "abc"))}}

Clojure, update nested content in map

I am confused about the required syntax for updating a value in a map, where said value is a vector of maps.
Given a map:
{:data-extracts [
{:name "some name"
:data "some data"
}]}
How can I update the value of :data, I know you can use assoc or conj to modify maps (well return new maps) but I'm unsure how this works when nested elements are present.
desired result:
{:data-extracts [
{:name "some name"
:data "new data"
}]}
Is there a way to do something like the following?
(update :data-extracts :data "new data")
How is this achieveable?
I tried the following:
(assoc opts :data-extracts [:name "Secret Escapes"
:data "new data"]))
But that doesn't work as I expected.
When using nested structures, you'll want to use *-in-functions (in this case assoc-in) and specify one key for each nesting level. In case of vectors, it's just index, in case of maps it's obvious:
(assoc-in [:data-extracts 0 :data] "new data")

What's the purpose of the :printed keyword in this Clojure doto macro example?

I've been looking at this Clojure doto macro example from ClojureDocs, and I can't figure out what the purpose of the :printed keyword in the final println.
When I enter the example in a REPL, it prints out the HashMap as I would expect, just with a :printed displayed after the HashMap:
user=> (doto (java.util.HashMap.) (.put "a" 1) (.put "b" 2) (println :printed))
#<HashMap {b=2, a=1}> :printed
{"b" 2, "a" 1}
I figured println needed a placeholder so that it knows to wait for something coming from the doto macro. So I tried seeing what I'd get if I omitted :printed:
user=> (doto (java.util.HashMap.) (.put "a" 1) (.put "b" 2) (println))
#<HashMap {b=2, a=1}>
{"b" 2, "a" 1}
This one prints the same thing, but makes the HashMap without a :printed alongside it. Given this result, shouldn't the doto example give something like this:
#<HashMap {b=2, a=1}>
{"b" 2, "a" 1} :printed
What is the :printed keyword doing?
:printed simply adds " :printed" to the string printed by println.
It does not affect the hash-map.
(println "Bingo" :printed)
=> Bingo :printed