the type
function works in clj and cljs
Hello, I am new to clojure coming from the Java and Javascript world. I think its a beautiful, expressive language and I am loving learning it. I am building a small headless restaurant app. My data is : menu ( a map of id -> { name, price, quantity } ), orders (a map of id -> [ { name, quantity } ] ). Both menu and orders are atoms I am trying to write a function that takes in a list of order-items and creates an order. So, I need to deduct the ordered quantity from the overall inventory from the menu map and then create an order. Below is my implementation. I see that only the orders atom gets updated, and the menu atom isnt. However, if I separate the swap atom code for menu into a separate function, it works. Please help pointing out what is wrong
(def orders (atom {}))
(def menu (atom {}))
(defn create-order [{:keys [order-items]}]
{:pre [(s/assert ::order-items order-items)]}
(for [item order-items]
(swap! menu update-in
[(keyword (slugify (:name item))) :quantity]
#(- % (:quantity item))))
(swap! orders
assoc
(keyword (str (UUID/randomUUID))) order-items))
(create-order {:order-items [{::name "chilly parotta"
::quantity 30}
{::name "mutton biriyani"
::quantity 3}
{::name "masal dosa"
::quantity 1}]})
@prabu.rajan for
is lazy. Functions evaluate each expression but only return the last expression. So a lazy expression is only “evaluated” at the top level, effectively.
(so your for
doesn’t get realized so it doesn’t do anything)
In general, you should not mix laziness with side-effects so you should use doseq
(imperative) here rather than for
(which produces a lazy sequence).
Also, you should try to write your functions as pure functions that don’t have side-effects where you can.
In this case, create-order
could be passed the current menu and orders values and return the new values of both, for the caller to keep track of.
(defn create-order [orders menu {:keys [order-items]}]
[(assoc orders (keyword (str (UUID/randomUUID))))
(reduce (fn [m item]
(update-in m [(keyword (slugify (:name item))) :quantity] #(- % (:quantity item))))
menu
order-items)])
;; and then:
(let [[new-orders new-menu] (create-order orders menu {:order-items [...]})]
(println new-orders)
(println menu))
and each time you call create-orders
with the current orders and menu values you get a pair of new values back to use in the next call.@prabu.rajan I would consider creating order and doing a dec
on the inventory to be one atomic operation.
Thanks @munichlinux you say both orders and menu (inventory) should be maps in the same atom in terms of data structure?
I was advocating for using ref when you perform the transaction.
I am a beginner, and just started learning for 2 weeks now. Are you talking about this? - https://clojure.org/reference/refs
ref
usage is very, very rare in the wild. A lot of programs can work just fine with a single atom of their state if they really need mutable data at all.
The nice thing about writing your function as a pure transformation of input to output is that you can usually adapt it for use with swap!
on a single atom.
In this case, you could have (atom {:menu {...} :orders {})
and then #(let [[orders menu] (create-order (:orders %) (:menu %) %2) {:orders orders :menu menu})
could be your adapter:
(swap! restaurant #(let ..) {:order-items [..]})
(off the top of my head)(or just rewrite create-order
a bit to accept the restaurant hash map as input — first argument — and return an updated hash map)
Thanks @seancorfield makes sense. I like the idea of minimizing code that deal with side effects as much as possible. I was mainly using atoms here, since that seems to be the clojure idiomatic way to deal with mutable state and this is a small single module app anyways. In larger apps, there might be better ways to handle mutable state like caches? I haven’t gone to that extent so far
https://github.com/clojure/core.cache — expects to be used with an atom for each cache. The clojure.core.cache.wrapped
namespace is the easiest/safest way to use it.
For a reference point, here’s some stats about our codebase at work:
Clojure build/config 19 files 224 total loc
Clojure source 350 files 89404 total loc,
3579 fns, 904 of which are private,
574 vars, 30 macros, 92 atoms,
858 specs, 33 function specs.
Clojure tests 379 files 23537 total loc,
4 specs, 1 function specs.
We have no ref
’s at all. We have 136 agent
’s, which are mostly used to track metrics that we report via New Relic.(we have another ~50 atom
’s in our test code which we don’t bother to report in our stats)
Great! I was going through the usages and the articles related to this library. Will try to put that to use soon in my next web app REST APIs
HTH?
(and, FWIW, I miss dosas… a local restaurant had a southern chef for a while and he had a variety of dosas on the menu, but he left and they haven’t had much southern food for a while 😞 )
Thanks a lot @seancorfield, really helpful. As you can see, it takes a lot to unlearn the imperative Java / Javascript way of coding and learn the new clojure functional way! but I hope I will get better at it over time!
Sad to hear and wishing you get to eat your favourite dosas soon!
(that code isn’t quite right but should give you some guidance… I’m now trying to get it right in the REPL!)
You are so helpful, I think you shouldn’t worry since I got the idea. Let me swim the river and learn to swim 🙂
dev=> (defn slugify [s] (str/replace s " " "-"))
#'dev/slugify
dev=> (def orders {})
#'dev/orders
dev=> (def menu {:chana-masala {:quantity 2} :paneer-bhurji {:quantity 1}})
#'dev/menu
dev=> (defn create-order [orders menu {:keys [order-items]}]
#_=> [(assoc orders (keyword (str (UUID/randomUUID))) order-items)
#_=> (reduce (fn [m item]
#_=> (update-in m [(keyword (slugify (:name item))) :quantity] #(- % (:quantity item))))
#_=> menu
#_=> order-items)])
#'dev/create-order
dev=> (let [[new-orders new-menu] (create-order orders menu {:order-items [{:name "paneer bhurji" :quantity 1}]})]
#_=> (println new-orders)
#_=> (println new-menu))
{:f3666dfc-a9f2-4432-8979-bdffedb83b80 [{:name paneer bhurji, :quantity 1}]}
{:chana-masala {:quantity 2}, :paneer-bhurji {:quantity 0}}
nil
dev=>
(this is making me hungry!)
wow! great, thanks! looks like you are a huge fan of indian food!
You can try these. I have tried and are ok. https://goo.gl/maps/YYG71XBb8sW3QE3K9 https://g.page/annachikadaimv?share https://goo.gl/maps/HzQcUcpHoJrAEhecA https://goo.gl/maps/THwHTHNa8BRMzyki9
Thank you! A couple of those are a bit far away for me — I’m in Castro Valley in the East Bay — but Dosa Hut is pretty close and I see there’s another Saravanaa Bhavan nearby at 3720 Mowry Ave, Fremont, CA 94538.
oh ok. I stay in Fremont. Yes, you can try the Mowry Avenue Saravana Bhavan, but their sambar and chutneys have lost their touch, but dosas are still good.
Born and raised in the UK — it’s the national dish 🙂 Now I live in the San Francisco Bay Area and good Indian food is hard to find. I’m lucky to have a fairly decent restaurant fairly locally but I’ve mostly been very disappointed with Indian restaurants in America.
Oh nice! I live in the SF bay area too. Having moved from south India to the bay area just 3 years back, I absolutely echo your sentiments about food! That is the biggest thing I miss about india here!
If you have any local recommendations, I’m all ears! 🙂
Hi, I'm looking at https://github.com/redstarssystems/context/blob/master/src/org/rssys/context/core.clj
What is that convention to prefix var
with *
, for example *ctx
?
hi. What syntax is this, the #: part.
(let [m #:domain{:a 1, :b 2}
{:domain/keys [a b]} m]
[a b]) ; 1, 2
basically just a different (shorter, easier to read/write) representation of map containing namespaced keys
user=> {:domain/a 1, :domain/b 2}
#:domain{:a 1, :b 2}
It can only be applied if all the ("top-level") keys in that map share the same namespace. If you use more than one key namespace, this form cannot be applied
user=> {:domain/a 1, :domain/b 2, :anotherdomain/a 3}
{:domain/a 1, :domain/b 2, :anotherdomain/a 3}
documentation: https://clojure.org/reference/reader see this part
Map namespace syntax
Added in Clojure 1.9
btw it's also possible to use #::
form, didn't know about that :thumbsup:
user=> #::{:some-key "some value"}
#:user{:some-key "some value"}
btw there's also a nice page with description of "weird characters", very handy https://clojure.org/guides/weird_characters
I have a custom data type that I want to print in the REPL as if it were an IPersistentMap. It implements everything needed to print as an IPersistentMap but it doesn't implement everything to act as an IPersistentMap. How can I do that? I'm looking over https://github.com/clojure/clojure/blob/master/src/clj/clojure/core_print.clj don't see an obvious way.
link/paste what you have so far and describe how it is not acting as a map there are a lot of interfaces and methods to implement to get it to work well
(core_print will not be helpful as it precedes the definition of deftype/reify during clojure's load)
https://github.com/eihli/clj-tightly-packed-trie/blob/main/src/com/owoga/tightly_packed_trie.clj It's ILookup. I want the seq of key/values to print. But it's "read-only". Can't assoc into it or empty it or anything.
oh I thought you said it was printing
Perhaps I'm using the wrong terms. When I evaluate the TightlyPackedTrie in my editor, I see:
;; => #object[com.owoga.tightly_packed_trie.TightlyPackedTrie 0x21f76d8d "com.owoga.tightly_packed_trie.TightlyPackedTrie@21f76d8d"]
I want to see (which is what I see with the same data as a https://github.com/eihli/clj-tightly-packed-trie/blob/main/src/com/owoga/trie.clj#L57https://github.com/eihli/clj-tightly-packed-trie/blob/main/src/com/owoga/trie.clj#L57https://github.com/eihli/clj-tightly-packed-trie/blob/main/src/com/owoga/trie.clj#L57):
;; => {(1 2) 12, (1 3) 13, (1) 1}
Yes. Thanks
(deftype Foo [a b c]....)
;;
(defmethod print-method Foo [my-foo ^java.io.Writer w]
... my custom foo printing method)
clojure has a multimethod that allows you to participate in the printing system in a custom way for your deftype
you don't have to manually write to the Writer, you can make the map representation of your dreams and print that by delegation
(defmethod print-method Foo [my-foo ^java.io.Writer w]
;; delegate to calling print-method on your desired map
(print-method (function-that-returns-the-desired-representation my-foo) w))
s/Foo/TightlyPackedTrie
Bingo. Thanks! That's exactly what I was looking for.
(defmethod print-method TightlyPackedTrie [trie ^java.io.Writer w]
(print-method (into {} trie) w))
often you will also want to override print-dup in a similar way
Is print-method/print-dup like Pythons __str__
vs __repr__
, the former being the human-readable repl format and latter is the string-serialized representation to recreate the object?
I don't know python enough to answer that part, but sounds like the same idea
in clojure print
vs pr
Hi, everyone. I am a clojure newbie and am super excited about learning and playing with this beautiful language! I am working my way through a roman numerals clojure exercise. I have
(def roman-numerals {"M" 1000 "D" 500 "C" 100 "L" 50 "X" 10 "V" 5 "I" 1})
and I'd like to convert let's say "XVI" to numbers - as a start. But
(map #(println %) (sequence "XIV"))
prints
X
(nilI
nilV
nil)
How can I get only the character and not the nil value so that something like this
(map #(get roman-numerals %) (sequence "XIV"))
produces numbers and not
(nil nil nil)
?
@aaandre what you see in the repl is the mixing of the side effect of printing with the value returned by printing
further, map always calls seq on its input for you and #(println %)
can be replaced by println
user=> (run! println "XIV")
X
I
V
nil
How would I get (map #(get roman-numerals %) (sequence "XIV"))
to produce the values of the hash correspoonding to the keys?
run! is like map but for side effects
if you want to process and not print, use the hash as a function
And, how would I convert (\X \I \V) to ["X" "I" "V"] ?
user=> (map {\X 10 \I 1 \V 5} "XIV")
(10 1 5)
you don't need to? or you can map str
user=> (->> "XIV"
(map str)
(map {"X" 10 "I" 1 "V" 5}))
(10 1 5)
If (map {\X 10 \I 1 \V 5} "XIV") works, why doesn't (map roman-numerals "XIV") ?
or you can be a functional programming nerd:
user=> (map (comp {"X" 10 "I" 1 "V" 5} str) "XIV")
(10 1 5)
#(roman-numerals (str %)) maybe in this way easier to understand (str chararcter type)
@aaandre seq of a string provides characters, not strings
@sb oh yeah that's how a normal person would write that huh :D
So roman-numerals has strings for keys and seq produces characters, and characters are not strings?
right
(I am coming from ruby 🙂
@aaandre my first approach would be to just use characters as keys since the input is characters
the string as key is just a complexity introduced by your intuition about ruby IMHO
Got it. Changed roman-numerals to (def roman-numerals {\M 1000 \D 500 \C 100 \L 50 \X 10 \V 5 \I 1}) and now (map roman-numerals "XIV") works as intended
but that's just a style thing, converting the characters to strings also solves the problem
You mean converting each character from the provided string to a string? how would I do that?
I guess the fun part is going to be the state machine for the prefixes hu
Yes, one step at a time 🙂
You'll be hearing from me again!
Loving clojure so much and how it makes me think about problems. It. just. flows.
@aaandre for a standalone seq to seq: #(map str %)
but it's usually more helpful to use the function rather than the mapping as the building block
and how would I convert character to symbol - if I am to have a proper hash keys?
so that's why (map roman-numerals input)
can become (map (comp roman-numerals str) input)
or (map #(roman-numerals (str %)) input)
@aaandre this is a thing that's different in clojure - we call :foo
a keyword, not a symbol, and 'foo
is a symbol
a "proper" map can have keys that are any immutable type
often a character or a string as a key is better than a keyword
got it thank you
(map keyword (map str "XIV"))
in fact adding code complexity by converting to keywords is a big pet peeve of mine (but once again that's a style thing and reasonable programmers can disagree)
@aaandre Yeah it's sweet, I remember doing a integer -> roman numeral parser and the solution ended up representing the logic of the conversion, and not a hard coded table of all the symbols (which is what you'll basically strictly find with a google search), which was neat
I mean, what actually makes {:a 0}
better than {\a 0}
? if your domain is characters the second one is more straightforward IMHO
so defining the hash with keys closest to the data at hand is good practice despite data at hand may not be keywords - got it'
right - keywords IMHO are great for when you have them as repeated constants throughout your code, but I don't think that's the role of the hash here
in my case it's roman numeral -> integer and it'll need logic, too as roman numbers sometimes indicate addition and sometimes subtraction, depending on positioning.
right - but the subtask here is token -> integer
why put keyword in the middle?
yeah a state machine would be one way to do that
thank you for the help! I will be back when I need more guidance. Haven't be so excited about a language since I encountered Ruby.
or “simply” with one transducer, like a csv parser
Ok, another question: (map roman-numerals "XIV")
produces (10 15 1)
but anything I try to do with this results in an error. I don't even know how to call this structure - integers as expression? - and not sure how to convert it into something workable? Confused by map not returning a vector for example. What is the proper way to use the result of a map? Should I convert it to a vector? If yes, how?
a list, when presented to the compiler, becomes an invocation
invoking a number is an error
(that's a lazy-seq not a list, but () as input makes a list)
how do I work with invocations consisting of the result of a map?
by calling another function that expects a sequential input
the ->>
macro can help a lot here
(ins)user=> (->> "abc")
"abc"
(ins)user=> (->> "abc" (map {\a 0 \b 1 \c 2}))
(0 1 2)
(ins)user=> (->> "abc" (map {\a 0 \b 1 \c 2}) (filter even?))
(0 2)
Interesting, even (map (1 2 3))
errors out.
right, (1 2 3) is not a valid input to the compiler
as I stated above, the compiler treats (...) as asking for invocation
it's a valid printed form for a result, of course
it's only a problem when you try to copy and paste the output of an evaluation
you can use [1 2 3]
which will have the same behavior under map / filter / etc.
(filter even? [1 2 3])
works but (filter even? (1 2 3))
produces an error
@qythium good point, you can use *1
to get the return value of the last expression
@aaandre right as I said (...) asks for invocation, invoking numbers is invalid
OK, so map returns an invocation with all of its elements - how do I work with this?
no, it returns something sequential
@aaandre in a repl you can use def
to assign a name to a return value
just typing in what the last form printed will not usually be the same as using the value that was printed from
yup, you can also quote lists to prevent them from being evaluated like '(1 2 3)
but this might cause more confusion in the short term
right - but that's pretty much never useful, since you can use [1 2 3]
or (list* [1 2 3])
if you really need list instead of vector
I see, "something sequential" meaning "you can push this into anything that accepts a sequence"
right
But... (map even? (1 2 3))
produces an error and (map even? '(1 2 3))
does not
right, that's because the '
operator prevents all evaluation
but what I get out of (map roman-numerals "XIV") is (10 1 5) without '
right, because '
is a reader facility, not a data type
'(foo)
expands in the reader to (quote foo)
and quote
is a special instruction to the compiler that says "my arg is to be used as is and not compiled"
how can I turn (1 2 3) into [1 2 3] ? ... and should I?
you are still misunderstanding the relationship between what the repl prints and the data itself
what you get out of (map roman-numerals "XIV")
is a lazy sequence whose printed representation happens to be (10 1 5)
you can use [1 2 3] in a repl for experimenting
a-ha! so I can't copy-paste the repl data because it's "printed"
when you enter (10 1 5) into the repl, clojure interprets it as "call the function 10
with the arguments 1 and 5"
right - the printed form is sometimes but not always valid for evaluation
thank you again 🙂
in your code structure you can use let
to bind results of expressions to names (that you can then use in all following forms in the let block), or directly wrap the expression in its consumer (with helpers like -> / ->> to avoid deep () nesting)
(10 1 5)
=> nil
;
; Execution error (TypeError) at (<cljs repl>:1).
`(10 1 5)
=> (10 1 5)
if you use a single quote / tick mark before the list, it'll not be considered a function
or you can transform it as you mentioned:
(into [] `(10 1 5))
=> [10 1 5]
(reduce conj [] `(10 1 5))
=> [10 1 5]
several ways to accomplish it
What's the Right Way to do (update m :key conj 123)
, ensuring that :key
's value is a vector (rather than a list) even if it didn't exist before?
I.e. if m
is {}
, I want the result to be {:key [123]}
and not {:key '(123)}
fnil
95% of my fnil usage is either (fnil conj [])
or (fnil conj #{})
please teach a novice like me the ways of fnil foo
(fnil fu)
Wonderful, thanks! (update {} :key (fnil conj []) 123)
fnil
is one of those super useful core functions that I don't use enough for its to "stick"
TIL fnil
exists