This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-10-01
Channels
- # aws (37)
- # babashka (7)
- # babashka-sci-dev (2)
- # beginners (75)
- # biff (7)
- # calva (85)
- # cider (9)
- # clj-kondo (26)
- # clj-yaml (1)
- # clojure (45)
- # clojure-europe (4)
- # clojure-norway (1)
- # clojure-spec (3)
- # clojure-uk (2)
- # clojurescript (3)
- # core-typed (2)
- # cursive (12)
- # fulcro (3)
- # humbleui (5)
- # jobs (8)
- # malli (1)
- # meander (3)
- # membrane (1)
- # portal (50)
- # squint (15)
- # vim (1)
Hello. I have fork of some repo and “lein run” works well but “lein uberjar” fails. Here is stack trace https://pastebin.com/FnLGLsaZ
I think your issue is https://github.com/ig0revich/mock-mechanics/blob/osx/src/mockmechanics/synthesizer.clj#L2
Caused by: java.lang.NullPointerException: Cannot invoke "clojure.lang.Namespace.importClass(java.lang.Class)" because the return value of "clojure.lang.Var.deref()" is null
at mockmechanics.synthesizer__init.load(Unknown Source)
Putting (ns mockmechanics.synthesizer)
at the top of the file seems to get you past that error. But then other errors are popping up
The new errors are starting in property-mode.clj
but I'm not sure what's going on there. The ns doesn't match the file name and there are a lot of unresolved symbols
project.clj is here https://github.com/ig0revich/mock-mechanics/blob/osx/project.clj
@seancorfield Maybe you can help 🙂
First off, please use threads - post follow-ups to your original message in a thread, not in the main channel. Second, never @ people who have not already engaged in the current thread. It is extremely rude.
In answer to your question, no, I can't help. I haven't used Leiningen for years. I switched to Boot in 2015 and then to the official CLI / deps.edn
in 2018. Which is another reason you should not @ random people about stuff.
well, you can get the price and quantity from the map by calling the keywords on the map, e.g. (:price item)
will return the price
if it helps, you could potentially make a function that takes just an item (one map from the vector) and computes the total price (price * qty) of that item, and then use that in the reducing function
as written, 0 is the initial value, which means it would be the first value of acc
in the reducing function
In a call like (reduce + [1 2 3])
, there's no initial value, and per the docs, that means the function gets called with the first two elements of the collection (in this case, (+ 1 2)
). In the situation you're looking at here, the accumulator has a very different shape from the collection items. We want our reducing function to take consistent arguments, so we don't want to the first call to be on two maps, and then subsequent calls to be on a number and a map, so by supplying an initial value, the first reduction will be called with 0 (our initial value) and the first map in the vector
@U044TFZTBRA I see Eugene gave you the complete answer to this exact same question just a few minutes ago on Zulip... /cc @U2FRKM4TW
Yes he gave me the answer. I came up with question how this works. I am still reading the documentations to understand. I prefer using slack if he is here as well.
Slack is a much bigger community so you'll have more folks to help you here than on Zulip.
Are you learning Clojure from a book or an online tutorial or...?
I think you would make more progress if you had a bit of structure to follow -- the learn Clojure guide on http://clojure.org at a minimum, perhaps the free online Brave and True book (but ignore the Emacs section in it).
Clojure is very different to other languages so trying to learn it without a guide is hard.
One more thing I want to do is to Write a function add-to-bill that accepts two arguments.
The first one is the bill below. The second one is a vector of additional items.
The method returns a new bill with the additional items.
I feel like I need to use into ?
(def bill [{:name "Green Tea Ice Cream" :price 2.5 :quantity 2}
{:price 1.0 :name "Sticky Rice" :quantity 1}])
(defn bill-total [bill]
(reduce (fn [total item]
(+ total (* (:price item) (:quantity item))))
0 bill))
(bill-total bill)
(def items [{:price 2.1 :name "Mango" :quantity 1}
{ :quantity 1:price 1.0 :name "StickyRice" }])
(defn add-to-bill [bill items]
)
(add-to-bill bill items)
;; returns
;; [{:name "Green Tea Ice Cream" :price 2.5 :quantity 2}
;; {:price 1.0 :name "Sticky Rice" :quantity 2}
;; {:price 2.1 :name "Mango" :quantity 1}]
Yes, if you have a vector of items and a second vector of items, into
will produce a single vector with all those items in it.
user=> (into bill items)
[{:name "Green Tea Ice Cream", :price 2.5, :quantity 2} {:price 1.0, :name "Sticky Rice", :quantity 1} {:price 2.1, :name "Mango", :quantity 1} {:quantity 1, :price 1.0, :name "StickyRice"}]
user=>
Note that data is immutable in Clojure so that will not change bill
, it will produce a new value.
Experimenting in a REPL is a great way to learn what all these functions do and you have access to their docs and their source directly in the REPL:
user=> (doc into)
-------------------------
clojure.core/into
([] [to] [to from] [to xform from])
Returns a new coll consisting of to-coll with all of the items of
from-coll conjoined. A transducer may be supplied.
nil
user=> (source into)
(defn into
"Returns a new coll consisting of to-coll with all of the items of
from-coll conjoined. A transducer may be supplied."
{:added "1.0"
:static true}
([] [])
([to] to)
([to from]
...
Ah, you want same-named items to be added together? That's going to be trickier. What we tend to do is to reorganize our data to provide the best structure for operations we want to perform on them. In this case, you really want your bill to be a hash map from the name of each item to the information about it (quantity, price) and then you add an item by either adding a new name -> data entry if it isn't already in the bill or by combining it with the existing data entry if it is present.
For example:
user=> (def bill-by-name (into {} (map (juxt :name identity) bill)))
#'user/bill-by-name
user=> bill-by-name
{"Green Tea Ice Cream" {:name "Green Tea Ice Cream", :price 2.5, :quantity 2}, "Sticky Rice" {:price 1.0, :name "Sticky Rice", :quantity 1}}
user=>
user=> (defn add-item [bill item]
(if-let [existing (get bill (:name item))]
(assoc bill (:name item) (merge-with (fn [v1 v2] (if (number? v1) (+ v1 v2) v1)) existing item))
(assoc bill (:name item) item)))
#'user/add-item
user=> (add-item bill-by-name {:price 2.1 :name "Mango" :quantity 1})
{"Green Tea Ice Cream" {:name "Green Tea Ice Cream", :price 2.5, :quantity 2}, "Sticky Rice" {:price 1.0, :name "Sticky Rice", :quantity 1}, "Mango" {:price 2.1, :name "Mango", :quantity 1}}
user=> (add-item *1 { :quantity 1 :price 1.0 :name "Sticky Rice" })
{"Green Tea Ice Cream" {:name "Green Tea Ice Cream", :price 2.5, :quantity 2}, "Sticky Rice" {:price 2.0, :name "Sticky Rice", :quantity 2}, "Mango" {:price 2.1, :name "Mango", :quantity 1}}
user=>
user=> (defn add-items [bill items] (reduce add-item bill items))
#'user/add-items
user=> (add-items bill-by-name [{:price 2.1 :name "Mango" :quantity 1} { :quantity 1 :price 1.0 :name "Sticky Rice" }])
{"Green Tea Ice Cream" {:name "Green Tea Ice Cream", :price 2.5, :quantity 2}, "Sticky Rice" {:price 2.0, :name "Sticky Rice", :quantity 2}, "Mango" {:price 2.1, :name "Mango", :quantity 1}}
user=>
The add-item
function could be written several ways. It could be explicit about building a hash map with [:price (+ (:price existing) (:price item)) :name (:name existing) :quantity (+ (:quantity existing) (:quantity item))}
for example.
And if you want your original format of bill back from bill-by-name
you can call vals
:
user=> (vals *1)
({:name "Green Tea Ice Cream", :price 2.5, :quantity 2} {:price 2.0, :name "Sticky Rice", :quantity 2} {:price 2.1, :name "Mango", :quantity 1})
user=>
Another approach, without changing the data structure format, would be to map
over the original bill
and if the name of the entry matches the name of the new item, produce an added-up item, else the original.
Edit: this does not work because it does not add new items!
user=> (defn add-item [bill item]
(map (fn [bill-entry]
(if (= (:name bill-entry) (:name item))
(merge-with (fn [v1 v2] (if (number? v1) (+ v1 v2) v1)) bill-entry item)
bill-entry))
bill))
#'user/add-item
user=> (add-items bill items)
({:name "Green Tea Ice Cream", :price 2.5, :quantity 2} {:price 2.0, :name "Sticky Rice", :quantity 2})
user=>
(that uses bill
as a vector of items and the same add-items
I showed above)
Hi, quick question, since I declarer adding items to be items. so in the function parameter, it should be items ?
Oh, you're right. My map
version doesn't work, sorry.
The parameter names don't need to match the global var names. But items
is a sensible name for the parameter.
(I updated my posts above -- I think the name -> data entry format is the best approach here anyway)
I like your map version. is it possible to add else part to add new one if there is not match ?
No, map
always produces the same number of items as the input has. It can't add items.
I played around a bit more and I think I like this version best of all so far:
user=> (defn add-item [bill {:keys [price quantity name] :as item}]
(if (contains? bill name)
(-> bill (update-in [name :price] + price) (update-in [name :quantity] + quantity))
(assoc bill name item)))
#'user/add-item
user=> (defn add-items [bill items]
(-> (reduce add-item
(-> (group-by :name bill) (update-vals first))
items)
(vals)))
#'user/add-items
user=> (add-items bill items)
({:name "Green Tea Ice Cream", :price 2.5, :quantity 2} {:price 2.0, :name "Sticky Rice", :quantity 2} {:price 2.1, :name "Mango", :quantity 1})
user=>
This uses the original bill
(vector of items) and converts it to the "by name" version in add-items
in a way that I think is more obvious than I originally suggested. And the new add-item
here is simpler -- using map destructuring to provide local bindings directly to the pieces inside the item
.See above. add-items
is the answer to Q2.
add-items
in my code is add-to-bill
in the specific question.
It's easier to solve if you break it down into 1) write a function to add a single item (hash map) to a bill 2) write a function that adds multiple items to a bill (using the function from 1).
My last version has a number of things in it that you'll need to understand/learn as you go through your journey: map destructuring (the :keys
/`:as` stuff), threading ->
(and its various siblings ->>
, some->
, cond->
, etc), and update-in
for updating items nested inside hash maps.
There are usually many ways to solve a problem in Clojure because there are so many different ways you can transform data (and so many core functions!).
I deleted (update-in [name :price] + price) part since price should be stays the same until I calculate the total bill.
At least with that update-in
format it's easy to remove just one of the sums.
Now you don't need the (-> bill (update-in ..))
you can say (update-in bill [name :quantity] + quantity)
as the whole arm of the if
Here's a direct link to the OP Q that has since been defeated: https://clojurians-log.clojureverse.org/beginners/2022-10-01/1664661320.776819 -- unfortunately, it doesn't preserve code blocks. However, they'd asked the exact same Q on Zulip, so here that is: I am trying to write a function bill-total whose one argument is a vector of such maps and returns the total of the bill. This is what I have so far. Could you please advice ? Thank you
(defn bill-total []
)
(def bill [{:name "Green Tea Ice Cream" :price 2.5 :quantity 2}
{:price 1.0 :name "Sticky Rice" :quantity 1}])
(bill-total bill) // should returns 6.0
And here's a link to that original thread on Zulip: https://clojurians.zulipchat.com/#narrow/stream/151763-beginners/topic/Write.20a.20Clojure.20function.20whose.20one.20argument.20is.20a.20vector/near/301797379
I want to validate that Person has a field named age and the value of age must be positive integer. Is the use of namespace and :req-un appropriate? If this is not the general way of writing, and you have a better way, could you please let me know?
(defrecord Person [age])
(defrecord Age [age])
(s/def ::age pos-int?)
(s/def :age/age (s/keys :req-un [::age]))
(s/def ::person (s/keys :req [:age/age]))
(s/valid? :age/age (->Age 20))
(s/valid? :age/age (->Age -10))
(s/valid? ::person (->Person (->Age 10)))
(s/valid? ::person (->Person (->Age -10)))
We generally would not use records here, just plain maps. Your person would be a hash map like {:age 42}
And then your specs will be simpler:
(s/def ::age pos-int?)
(s/def ::person (s/keys :req-un [::age]))
user=> (s/valid? ::person {:age 42})
true
user=> (s/valid? ::person {:name "Sean"})
false
user=> (s/explain ::person {:name "Sean"})
{:name "Sean"} - failed: (contains? % :age) spec: :user/person
nil
user=>
Even with records, you only need Person
:
user=> (defrecord Person [age])
user.Person
user=> (s/valid? ::person (->Person 42))
true
user=> (s/valid? ::person (map->Person {:name "Sean"}))
false
user=>
Fair enough. Thank you for your answer ! I understand that in this case using records is not appropriate or too much
You can look at an example of domain modeling using maps and spec here: https://github.com/didibus/clj-ddd-example/blob/main/src/clj_ddd_example/domain_model.clj
Thanks a lot! This is a kind of stuff I have been looking for!! Actually I am reading the book, Domain Modeling Made Functional and trying to convert the code in F# into cloture
That's why I put that repo together, DDD in Clojure is very easy, straightforward, and doesn't really require much code or any amount of cruft. It really just lets you focus on the actual domain problem and logic, and really you barely need to do anything in terms of designing patterns. Like my repo show, simple make-entity
fns for constructing valid domain entities/values/aggregates, with spec as the layer to define and validate domain invariants. And you're pretty much ready to go.