This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-01-16
Channels
- # ai (3)
- # babashka (3)
- # beginners (252)
- # calva (56)
- # clj-kondo (6)
- # cljfx (7)
- # cljs-dev (2)
- # cljsrn (2)
- # clojure (72)
- # clojure-france (12)
- # clojurescript (13)
- # conjure (60)
- # garden (18)
- # hoplon (16)
- # jobs (1)
- # leiningen (3)
- # off-topic (18)
- # pathom (5)
- # practicalli (1)
- # reagent (4)
- # reitit (1)
- # remote-jobs (1)
- # reveal (3)
- # shadow-cljs (1)
- # spacemacs (7)
- # xtdb (39)
Why does this fail on 4clojure
#(and (contains? %1 %2) (nil? (get %1 %2)))
tests :
Write a function which, given a key and map, returns true iff the map contains an entry with that key and its value is nil.
(true? (__ :a {:a nil :b 2}))
(false? (__ :b {:a nil :b 2}))
(false? (__ :c {:a nil :b 2}))
what error do you get? I remember trying to do something similar and it was not possible to use and like that because it's a macro
maybe the (fn ..) syntax will work
yep, this is working
(fn test [ key map]
(and (contains? map key) (nil? (get map key))))
yeah actually i think i was wrong
you just mixed the order of the arguments
in you first definiton
this works
(true? (#(and (contains? %2 %1) (nil? (get %2 %1))) :a {:a nil :b 2}))
i also found that
#(nil? (get %2 %1 "nope"))
works but it's not really obvious what one should use in the default case
I think the challenge is clear, and using a default value for the get i think makes sense, you just have to put something that is not nil as the default, but i'm not sure what would be the best option
is this a good clojure solution
When retrieving values from a map, you can specify default values in case the key is not found:
(fn test[default sequence]
(into {} (for [x sequence] {x default})))
Something basic to see if I understand correctly. If I want to use a non-standard library: • how to I see it's not standard? doesn't start with "clojure/"? • Do I always have to state my intent in two places? With leinigen in the project.clj, so Leinigen can download it and make it available and in the namespace again so I can use it in the namespace?
I think "standard library" is the content of clojure.core namespace. Everything else is a non-standard. And yes to your second question in case of leiningen
As for the second point, yes, but it’s helpful to note that there are two intentions here: expressing what you intend to use (a concrete version of a library), and where you intend to use it (in this or that ns). You express the first intention to your deps management tool (project.clj for lein, deps.edn for clj, etc), and express the second in the namespace itself.
Why do we need to use partial
function? We can achieve it using normal defn
right? why we go for partial
?
There is never a time where partial
can be used, that one cannot use (fn ...)
or sometimes #( ...)
instead. So in that sense there is no need to use partial
. Some people prefer partial
when it does the job.
I have solved this challenge : https://www.4clojure.com/problem/156
(fn test[default sequence]
(into {} (for [x sequence] {x default})))
Looks good to me. An alternative would be
(fn [default ks]
(zipmap ks (repeat default)))
Maybe be careful with the names - sequence and test are both already core functions.Good warnings on names. Although, the shorter the function, the less likely such shadowing of names is to cause problems, and that is a nice short function.
Most commonly you will see xs
(as in "a collection of x's") to refer to a generic collection of things.
anyone xp with clj.http to make this work instead of slurp `(slurp "https://www.rijksmuseum.nl/api/nl/collection?key=14OGzuak&format=json&type=schilderij&toppieces=True") ?
(client/get ""
{:query-params {:key "14OGzuak"
:format "json"
:type "schilderij"
:toppieces "True"}})
Have you seen the examples in the docs? https://github.com/dakrone/clj-http#get
so something like this :
(client/get ""
{:query-params {:key "14OGzuak"
:format "json"
:object-number :object-number"}})
Is object-number
part of the query string?
https://github.com/metosin/reitit can do "reverse routing": template + path params = http path
(def router (r/router ["/api"
["/foo" ::foo]
["/bar/:id" ::bar]]))
(:path (r/match-by-name router ::foo)) ;; /api/foo
(:path (r/match-by-name router ::bar {:id 1})) ;; /api/bar/1
1st fn, 1st contains call: (contains? "xx" "xx")
=> contains? not supported on type: java.lang.String
I think it will clarify if you unpack the expression. What is #(contains? % "xx")
being applied to in the first case?
Understandable you would think that, but it is not the case here. Check out the docs for https://clojuredocs.org/clojure.core/some
(some pred coll)
Returns the first logical true value of (pred x) for any x in coll,
else nil.
So you are applying #(contains? % "xx")
to every element of the set #{"xx" "yy" "jj"}
Not the set itself.
In other words, (contains? "xx" "xx")
as clyfe mentions.maybe https://clojuredocs.org/clojure.string/includes_q is what you want?
= then
> can we use = in comparing string in clojure? I think in general, yeah you're pretty safe doing that
(defn =
"Equality. Returns true if x equals y, false if not. Same as
Java x.equals(y) except it also works for nil, and compares
numbers and collections in a type-independent manner. Clojure's immutable data
structures define equals() (and thus =) as a value, not an identity,
comparison."
https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#equals(java.lang.Object)
Ppl, what is the purpose of using a new argument with reduce in this example? What he does? Thank you!
@claudioferreira.dev If you don't provide the "initial" value in reduce
, it has some slightly strange semantics. Take a close look at the docstring.
"If val is not supplied, returns the result of applying f to the first 2 items in coll, then applying f to that result and the 3rd item, etc." -- That's fine. But pay attention to this bit: "If coll contains no items, f must accept no arguments as well, and reduce returns the result of calling f with no arguments."
And you you have this case (again, when "initial" value is not provided): "If coll has only 1 item, it is returned and f is not called."
For example:
user=> (reduce println [1])
1 ; println not called, returns first (only) value of the collection
user=> (reduce println [] [1])
[] 1 ; println called -- prints initial value and first element
nil ; returns nil because that's what println produces
Hi I can totally replace ClojureScript inside an existing React client project?
And then with no elements in the collection:
user=> (reduce println [])
; println is called here with no arguments
nil ; returned from calling println
user=> (reduce println [] [])
[] ; doesn't call println, returns initial value
The latter is easier to see with (reduce println [1] [])
which just returns [1]
Does that help @claudioferreira.dev?
Kinda! @seancorfield I understood that relation between f, val and coll. But i cannot understand how can that be useful in our day by day. Which problem does adding " { } " (just like in the pic i sent initially) as a initial value solves or facilitate our life? e.g
(reduce + [1 2 3])
=> 6 ; Great, we added all the values together
(reduce + {} [1 2 3]) ; ??? "{ }" helps nothing here
Execution error (ClassCastException)...
If so, why did the exercise added { } inside that maximum and minimum function? Just for the result be in a map? The book used "accumulator" to define that { }. So, we are taking our values from the coll [5 23 5004 845 22] and putting it inside the "acumulator"? (or "{}"/initial value)
Sorry for the dumb question @seancorfield , have been thinking for hours about this initial value but still didn't found the answer. Thanks for the support!
user=> (reduce + [1 2 3])
6
user=> (reduce + 0 [1 2 3])
6
user=> (reduce + 13 [1 2 3])
19
Hmmmm, just like partial?
Compare these:
user=> (reduce conj [] [1 2 3 4])
[1 2 3 4]
user=> (reduce conj {} [1 2 3 4])
Execution error (IllegalArgumentException) at user/eval173 (REPL:1).
Don't know how to create ISeq from: java.lang.Long
user=> (reduce conj #{} [1 2 3 4])
#{1 4 3 2}
user=> (reduce conj [] {:a 1, :b 2, :c 3})
[[:a 1] [:b 2] [:c 3]]
user=> (reduce conj {} {:a 1, :b 2, :c 3})
{:a 1, :b 2, :c 3}
user=> (reduce conj #{} {:a 1, :b 2, :c 3})
#{[:c 3] [:b 2] [:a 1]}
But, why does the values here go "back" to the initial value if we are not using conj?
(reduce (fn [{:keys [minimum maximum]} new-number]
{:minimum (if (and minimum (> new-number minimum))
minimum
new-number)
:maximum (if (and maximum (< new-number maximum))
maximum
new-number)})
{} ;; <---- The new argument!
[5 23 5004 845 22])
{:minimum 5, :maximum 5004}
And here they dont:
(reduce + { } [1 2 3])
Execution error (ClassCastException)
=> {6} ; The result i expectedIn (reduce + {} [1 2 3])
the first step is (+ {} 1)
which is not legal.
In (reduce + 13 [1 2 3])
the first step is (+ 13 1)
=> 14
. The next step is (+ 14 2)
=> 16
. The final step is (+ 16 3)
=> 19
.
In your min max example, the first step is to call that function with {}
and 5
-- and the function destructures {}
against {:keys [minimum maximum]}
so both minimum
and maximum
are bound to nil
.
And the if
conditions check for nil
: (and minimum (> new-number minimum))
will guard the comparison for non-`nil` values.
So it produces {:minimum 5, :maximum 5}
The next step is to call that function with {:minimum 5, :maximum 5}
and 23
So now we get {:minimum 5, :maximum 23}
and go round again with 5004
and so on.
Does that help?
(reduce + [1 2 3])
is the same as (reduce + 1 [2 3])
so it does (+ 1 2)
and then (+ 3 3)
But (reduce + [])
is going to call (+)
-- with no arguments -- which produces 0
And (reduce + [1])
is just going to return 1
without calling +
.
You can see that +
is not called, in this example (reduce + [:not-a-number])
which produces :not-a-number
(if it tried to call +
on it, you'd get an exception).
THANK YOU @seancorfield!!!! Finaaaally ive understand that!
Cool. This higher-order stuff can be a bit bewildering at first because it's not how folks program in traditional languages.
In Clojure, using sequence and collection functions is far more common than writing loops, for example.
Rich Hickey has said several times that the IReduce
form of reduce
(without the initial value) was a bad idea and he wishes he'd only created the IReduceInit
form -- with three arguments: the reducing function, the initial value, and the sequence.
When I added "reducible queries" to clojure.java.jdbc
, I implemented both forms and it was a mistake. When I implemented next.jdbc
, I very deliberately omitted the IReduce
form.
Yeah, thats make sense about the Ireduce problem.
Thank you @seancorfield!!!! Now i understand this example. I appreciate your patience and attention
How does ClojureScript handle state management inside an existing React project then?
@sebastian_cheung Reagent and Om are ClojureScript libraries that wrap React.js under the hood. re-frame is built on top of Reagent to provide a structured way to manage state changes. You should look at the docs for those projects.
Essentially you would replace all your JS (and React.js usage) with a complete, new ClojureScript app that used re-frame, or Reagent alone, or Om etc
thanks @seancorfield but I will be working in a team, they will not change their React code just for me, so only potentially ClojureScript from my side, interfacing with their React features
@sebastian_cheung My understanding is that is not possible. cljs assumes "whole program".
Couldn’t the CLJS be compiled to an npm module & required by the JS? I’ve never done it, but these docs look promising: https://shadow-cljs.github.io/docs/UsersGuide.html#target-npm-module
Oh, that does look like it might help @sebastian_cheung build stuff with cljs and integrate it into an existing React JS app?
There are several clojurescript projects that work well with React (including react and re-frame). I wouldn't suggest Om for new projects though. From Om's Readme: > NOTE: This project is no longer under active development. If you'd like to use a library that's well maintained that was inspired by some of the ideas presented here see https://github.com/fulcrologic/fulcro
Glad to hear that Om is directing users to Fulcro. Does the same apply to Om.Next?
Yes, afaik
Hmm, that repo is where I end up if I follow links for Om Next so I guess both versions of Om have gone away. Can't say I liked it, compared to Reagent -- at least when we looked at apps built with both Om and Reagent back in 2013/2014 🙂
I've been trying to escape the tyranny of the browser. I was able to get Fulcro to work on desktop. re-frame has implicit dependencies on react. I think that may be the case with reagent as well.
re-frame can be used server-side in Clojure -- it's kind of weird but it's possible...
I meant for building desktop apps.
With... Electron or something similar?
my side project is trying 🤞 to clojurize UI development: https://github.com/phronmophobic/membrane
most of the cool UI development is happening in cljs with react in mind
but where possible, I've tried to use existing UI state libraries without react
I believe using clojurescript to interop with existing react code is possible. I think https://github.com/lilactown/helix might be a good library to start with for that use case.
per its https://github.com/lilactown/helix/blob/master/docs/motivation.md: > The goals of Helix are: > - Provide an ergonomic, well-documented API for building components in ClojureScript > - Have as small of a runtime as possible > - Add as few new semantics on top of React as possible
Fulcro or helix then?
@smith.adriane Reading the docs for Helix, it's not clear to me that you could use it to define React components/hooks and use it as part of an existing JS React.js app?
@smith.adriane @seancorfield @sebastian_cheung it's 💯 possible to use components defined using helix in a ReactJS app. care has to be taken to accept props in a way that is ergonomic for the consumer, though. E.g. you probably won't want to expect that someone using your components are passing in ClojureScript maps, vectors, sets etc.
personally, I would not try and develop a bunch of components in CLJS that are then used in a React app, as the tradeoffs are not worth it. ClojureScript is a good application language. You'll find that when creating libraries that are then used in JS, those libraries are harder to build, to use, and will have worse performance than a JS lib
I have had great success going the other direction, building components in JS that I then use in CLJS. Helix had that in mind when it was built, and allows you to use ReactJS components basically the same as if you defined them using Helix
Thanks for the follow-up @lilactown -- that sort of confirms the impression that I'd gotten about cljs vs JS (use cljs for the app, JS for interop/components as needed) but interesting to hear that you can write components in cljs and use them from JS/React.js but it has caveats...
The tradeoffs are very similar to Clojure vs Java. I would not build a library in Clojure that is consumed in a Java app unless I had some very specific and important reason to
@sebastian_cheung I think Fulcro is also going to assume that it's in charge of the whole app too, like re-frame/Reagent...?
maybe @lilactown could provide better clarification. from this https://github.com/lilactown/helix/blob/master/docs/creating-components.md#interop : > One thing to note is that this conversion of JS objects to CLJS data types is shallow; this means if you pass in data like a JS object, array, etc. to a prop, it will be left alone. > > This is an intentional design decision. It is a tradeoff - on the one hand, it is more efficient to opt not to deeply convert JS data structures to CLJS data, and it means that you do not need to learn some Helix-specific rules when interoping with external React libraries that use higher-order components or render props.
also: > Helix's philosophy is to give you a Clojure-friendly API to raw React. All Helix components are React components, and vice-versa; any external React library can be used with Helix with as minimal interop ceremony as possible.
I'm going through a lot of material and courses right now about getting started with re-frame -- SPAs that make API calls to Clojure on the backend.
is recurring implicitly from tail position the same as using recur
or must you explictly use recur
?
@octo221 You have to use recur
explicitly. Otherwise it is going to use the stack for recursion and you may blow the stack.
recur
is explicit so that you can only use it in a position where it can avoid using the stack.
(if you try to use it in a non-tail position, you'll get a compiler error)
thank you @seancorfield
but if the compiler can give error when`recur`is used in non-tail position, why can't it replace implicit recur with recur
automatically ?
(when called from tail position)
That's a design decision that Rich has talked about. He wanted folks to be able to look at code and immediately tell whether it was doing actual recursion or the stack-friendly "tail call optimization", i.e., recur
.
I see
yes I see that makes good sense
When we see recur
we know it's "safe" and using the optimized form: looping with new bindings rather than actually doing recursive calls.
When we see a recursive function call, we know it's using the stack. So it makes it simple to see what's going on -- and not have to try to discern what the compiler will do with our code behind the scenes.
good explanation thanks
@seancorfield do you have then a good course for me to learn re-frame ?
Ah, sorry, missed that question. Yes, that's probably the one I would have recommended. There's also this one https://purelyfunctional.tv/courses/understanding-re-frame/ (and, yes, you need to pay for these).
oke, I think i try the free course first. Find 200 till 1000 euro very much for a hobby
I also think having implicit recur would give you the false impression that the compiler can detect mutually recursive tail calls, which it cannot. By forcing the use of recur you know it's not possible for you to recurse to another function that will call you back.
how can I convert keys here to keywords :
`->>(client/get ""
{:query-params {:key "14OGzuak"
:format "json"
:type "schilderij"
:toppieces "True"}})
(:body)
(json/parse-string)))
Just pass the right options to json/parse-string
This is data.json
right? It's right there in the README @U0EGWJE3E: https://github.com/clojure/data.json#converting-keyvalue-types
Then read Cheshire's docs -- that readme also covers this.
think I gave up on clojure
(json/parse-string (client/get
(str " " object-number "/tiles")
{:query-params {:key "14OGzuak"
:format "json"}}))))
error:
; Execution error (ClassCastException) at cheshire.core/parse-string (core.clj:207).
; class clojure.lang.PersistentHashMap cannot be cast to class java.lang.String (clojure.lang.PersistentHashMap is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
@U0EGWJE3E look at what client/get
returns -- try it in the REPL.
It is not a string. It's a hash map.
parse-string
expects a string and you're handing it a hash map.
You need to learn to take more careful steps and try things out in the REPL.
do you like this code :
(ns paintings.core
(:require [cheshire.core :as json]
[clj-http.client :as client]
[clojure.walk :as walk]))
(defn image-url-size [image]
(let [data (select-keys image [:width :height])
url (get-in image [:tiles 0 :url])]
(assoc data :url url)))
(defn assoc-image [object-number]
(->> (client/get (str " " object-number "/tiles")
{:query-params {:key "14OGzuak"
:format "json"}})
(:body)
(json/parse-string)
(walk/keywordize-keys)
(:levels)
(sort-by :name)
(last)
(image-url-size)
(merge {:object-number object-number})))
(time (->>(client/get ""
{:query-params {:key "14OGzuak"
:format "json"
:type "schilderij"
:toppieces "True"}})
(:body)
(json/parse-string)
(walk/keywordize-keys)
(:artObjects)
(map :objectNumber)
(map assoc-image)))
Like I just said, you don't need keywordize-keys
. Just tell Cheshire to return keywords.
Be careful about using time
around a map
expression because map
is lazy.
You can use ->
for most of the pipeline and then switch to ->>
for the final steps.
But it's considered poor style to mix threading types -- because it indicates you are changing from "object/thing/collection" functions to sequence functions.
Can I what?
(-> (client/get ..) :body (json/parse-string true) :artObjects (->> (map :objectNumber) (map assoc-image)))
Poor style because you're trying to do too much in a single pipeline. Refactor it into small functions to transform the data.
And don't forget about my comment about time
and map
.
time
will just tell you how long it took to construct the initial lazy seq, not how long it actually takes to do the transformation work.
Compare (time (mapv inc (range 1000)))
and (time (map inc (range 1000)))
(time (doall (map ..)))
if you must use map
. Or use an eager process like mapv
.
In this? "Compare (time (mapv inc (range 1000))) and (time (map inc (range 1000)))" -- no, that's exactly the point I was making: time
of map
doesn't time the work.
Right, because (time (map inc (range 1000)))
is not timing how long it takes to map inc over that range.
It's just timing how long it takes to create an (unrealized) lazy sequence object -- it isn't doing any work.
im now trying to figure out why this is not working
(->> (client/get ""
{:query-params {:key "14OGzuak"
:format "json"
:type "schilderij"
:toppieces "True"}})
:body
(json/parse-string true)
:artObjects
(->> (map :objectNumber)
(map assoc-image)))
(time (doall (map inc (range 1000))))
will force realization of the mapping.
You're using ->>
at the beginning, not ->
It should be (-> (client/get ..) ... (->> (map ..) (map ..)))
Now you've fixed it 🙂 Do you understand what you did wrong?
but this is better style
(defn take-data[image-data]
(->> image-data
(map :objectNumber)
(map assoc-image)))
(-> (client/get ""
{:query-params {:key "14OGzuak"
:format "json"
:type "schilderij"
:toppieces "True"}})
:body
(json/parse-string true)
:artObjects
(take-data))
Better, yes.
(defn assoc-image [object-number]
(->> (client/get (str " " object-number "/tiles")
{:query-params {:key "14OGzuak"
:format "json"}})
(:body)
(json/parse-string true)
(:levels)
(sort-by :name)
(last)
(image-url-size)
(merge {:object-number object-number})))
That's wrong.
You're using ->>
again and you need ->
, and then switch to ->>
later in the pipeline
Oh, you mean you need to rewrite it "the same way" as you rewrote the other code?
Sorry, it's hard to understand what you're asking at times.
sorry, english is not my mother languages and I was years and years ago very bad in languages
NP. I get it now. Good luck tomorrow -- once you've had some sleep!
And remember: take small steps and try each piece out in the REPL.
what do you think
(ns paintings.core
(:require [cheshire.core :as json]
[clj-http.client :as client]
[clojure.walk :as walk]))
(defn image-url-size [image]
(let [data (select-keys image [:width :height])
url (get-in image [:tiles 0 :url])]
(assoc data :url url)))
(defn take-image-data[image-data object-number]
(->> image-data
(sort-by :name)
(last)
(image-url-size)
(merge {:object-number object-number})))
(defn assoc-image [object-number]
(-> (client/get (str " " object-number "/tiles")
{:query-params {:key "14OGzuak"
:format "json"}})
(:body)
(json/parse-string true)
(:levels)
(take-image-data object-number)))
(defn take-data [api-data]
(->> api-data
(map :objectNumber)
(map assoc-image)))
(-> (client/get ""
{:query-params {:key "14OGzuak"
:format "json"
:type "schilderij"
:toppieces "True"}})
:body
(json/parse-string true)
:artObjects
(take-data))
Nice breakdown! Yay!