Fork me on GitHub
#beginners
<
2021-01-16
>
roelof10:01:15

good morning all

roelof10:01:29

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}))

Antonio Bibiano10:01:40

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

Antonio Bibiano10:01:15

maybe the (fn ..) syntax will work

roelof10:01:18

no, error on the 4clojure site only a message that I fail the tests

roelof10:01:44

yep, this is working

(fn test [ key map] 
  (and (contains? map key) (nil? (get map key)))) 

Antonio Bibiano10:01:13

yeah actually i think i was wrong

Antonio Bibiano10:01:26

you just mixed the order of the arguments

Antonio Bibiano10:01:35

in you first definiton

Antonio Bibiano10:01:51

(true?  (#(and (contains? %2 %1) (nil? (get %2 %1))) :a {:a nil :b 2}))

jumar10:01:51

You can use find too

Antonio Bibiano10:01:37

i also found that

Antonio Bibiano10:01:54

#(nil? (get %2 %1 "nope")) works but it's not really obvious what one should use in the default case

roelof10:01:14

yep, i agree. the challenge is not clear about it

Antonio Bibiano10:01:48

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

roelof10:01:30

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})))

kimim13:01:26

How about : #(nil? (get %2 %1 :else))

roelof16:01:45

nope, it fails the first case

(= (__ 0 [:a :b :c]) {:a 0 :b 0 :c 0})

Christian14:01:52

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?

delaguardo15:01:58

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

Robert Mitchell15:01:33

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.

popeye15:01:35

Why do we need to use partial function? We can achieve it using normal defn right? why we go for partial ?

andy.fingerhut15:01:14

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.

🙌 2
roelof17:01:44

I have solved this challenge : https://www.4clojure.com/problem/156

(fn test[default sequence] 
   (into {} (for [x sequence] {x default})))

roelof17:01:00

is that good clojure code or can I improve it somehow ?

Joe17:01:49

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.

👍 1
andy.fingerhut17:01:28

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.

roelof17:01:18

thanks both

roelof17:01:35

so ks is a better name for a collection

roelof17:01:42

or maybe col ?

Joe17:01:12

Most commonly you will see xs (as in "a collection of x's") to refer to a generic collection of things.

roelof17:01:44

oke, i know that one from my haskell days

👌 1
roelof17:01:10

there is xs also often used

popeye18:01:50

(some #(contains? % "xx") #{"xx" "yy" "jj"})

popeye18:01:59

this is giving me an issue

popeye18:01:20

but this is returning result (contains? #{"xx" "yy" "jj"} "xx")

popeye18:01:34

is there any issue with the 1st function?

clyfe18:01:04

1st fn, 1st contains call: (contains? "xx" "xx") => contains? not supported on type: java.lang.String

Joe18:01:12

I think it will clarify if you unpack the expression. What is #(contains? % "xx") being applied to in the first case?

popeye18:01:44

i am passing set in place of %

Joe18:01:05

Understandable you would think that, but it is not the case here. Check out the docs for https://clojuredocs.org/clojure.core/some

popeye18:01:27

I am trying to do same as

(some #(= 5 %) [6 7 8 9 10])

popeye18:01:36

which is mentioned in document

popeye18:01:06

or, is it just taking first element from set?

Joe18:01:25

(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.

popeye18:01:44

(some #(= % "xx") #{"xx" "yy" "jj"}) worked

👍 1
popeye18:01:37

can we use = in comparing string in clojure?

popeye18:01:18

does it check string content ? or reference?

popeye18:01:51

include does not suit my requitement

popeye18:01:06

want to check exact string

Joe18:01:23

> can we use = in comparing string in clojure? I think in general, yeah you're pretty safe doing that

popeye18:01:41

does it check the string content?

popeye18:01:56

(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."

popeye18:01:05

yeah this is i got from core code

Claudio Ferreira19:01:50

Ppl, what is the purpose of using a new argument with reduce in this example? What he does? Thank you!

seancorfield19:01:26

@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.

seancorfield19:01:14

"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."

seancorfield19:01:47

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."

seancorfield19:01:24

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

Sebastian Cheung19:01:10

Hi I can totally replace ClojureScript inside an existing React client project?

seancorfield19:01:12

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]

Sebastian Cheung19:01:11

How does ClojureScript handle state management inside an existing React project then?

seancorfield19:01:18

@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.

seancorfield19:01:04

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

Sebastian Cheung19:01:41

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

seancorfield19:01:26

@sebastian_cheung My understanding is that is not possible. cljs assumes "whole program".

Robert Mitchell19:01:30

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

seancorfield19:01:33

Oh, that does look like it might help @sebastian_cheung build stuff with cljs and integrate it into an existing React JS app?

phronmophobic19:01:34

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

seancorfield19:01:18

Glad to hear that Om is directing users to Fulcro. Does the same apply to Om.Next?

seancorfield19:01:59

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 🙂

phronmophobic19:01:14

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.

seancorfield19:01:52

re-frame can be used server-side in Clojure -- it's kind of weird but it's possible...

phronmophobic19:01:07

I meant for building desktop apps.

seancorfield19:01:19

With... Electron or something similar?

phronmophobic19:01:27

my side project is trying 🤞 to clojurize UI development: https://github.com/phronmophobic/membrane

phronmophobic19:01:08

most of the cool UI development is happening in cljs with react in mind

phronmophobic19:01:55

but where possible, I've tried to use existing UI state libraries without react

phronmophobic19:01:35

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.

phronmophobic19:01:33

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

Sebastian Cheung19:01:44

Fulcro or helix then?

seancorfield19:01:32

@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?

lilactown20:01:10

@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.

lilactown20:01:43

it's also possible to do this with Fulcro, it requires more steps and care though

lilactown20:01:21

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

lilactown20:01:13

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

👍 1
seancorfield20:01:30

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...

lilactown03:01:45

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

seancorfield19:01:21

@sebastian_cheung I think Fulcro is also going to assume that it's in charge of the whole app too, like re-frame/Reagent...?

phronmophobic19:01:16

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.

roelof19:01:14

see that web development is now a lot about react and sons

phronmophobic19:01:27

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.

roelof19:01:05

so mayb in the futuure look for a coure how to make a html site into a react site

seancorfield19:01:37

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.

octahedrion19:01:44

is recurring implicitly from tail position the same as using recur or must you explictly use recur?

seancorfield19:01:53

@octo221 You have to use recur explicitly. Otherwise it is going to use the stack for recursion and you may blow the stack.

seancorfield19:01:20

recur is explicit so that you can only use it in a position where it can avoid using the stack.

seancorfield19:01:53

(if you try to use it in a non-tail position, you'll get a compiler error)

octahedrion19:01:47

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 ?

octahedrion19:01:07

(when called from tail position)

seancorfield19:01:23

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.

octahedrion19:01:22

yes I see that makes good sense

seancorfield19:01:30

When we see recur we know it's "safe" and using the optimized form: looping with new bindings rather than actually doing recursive calls.

seancorfield19:01:16

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.

octahedrion19:01:12

good explanation thanks

roelof20:01:20

@seancorfield do you have then a good course for me to learn re-frame ?

seancorfield02:01:00

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).

roelof10:01:11

oke, I think i try the free course first. Find 200 till 1000 euro very much for a hobby

didibus20:01:54

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.

roelof20:01:53

how can I convert keys here to keywords :

`->>(client/get ""
                                          {:query-params {:key "14OGzuak"
                                                          :format "json"
                                                          :type "schilderij"
                                                          :toppieces "True"}})
           (:body)
           (json/parse-string)))

seancorfield20:01:08

Just pass the right options to json/parse-string

roelof20:01:37

nope, this is cheshire

seancorfield20:01:08

Then read Cheshire's docs -- that readme also covers this.

roelof20:01:36

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')

seancorfield20:01:50

@U0EGWJE3E look at what client/get returns -- try it in the REPL.

seancorfield20:01:58

It is not a string. It's a hash map.

roelof20:01:20

chips, I see it

seancorfield20:01:26

parse-string expects a string and you're handing it a hash map.

roelof20:01:38

never code just before goto sleep

seancorfield20:01:51

You need to learn to take more careful steps and try things out in the REPL.

roelof20:01:26

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)))

seancorfield20:01:50

Like I just said, you don't need keywordize-keys. Just tell Cheshire to return keywords.

roelof21:01:50

yep but then I think I cannot use ->> but then I have to use ->

seancorfield21:01:27

Be careful about using time around a map expression because map is lazy.

seancorfield21:01:51

You can use -> for most of the pipeline and then switch to ->> for the final steps.

roelof21:01:26

can I ? never seen that

seancorfield21:01:26

But it's considered poor style to mix threading types -- because it indicates you are changing from "object/thing/collection" functions to sequence functions.

roelof21:01:07

I learned I have to use a function to use -> in a ->> pipeline

seancorfield21:01:31

(-> (client/get ..) :body (json/parse-string true) :artObjects (->> (map :objectNumber) (map assoc-image)))

roelof21:01:56

okek but that is a poort style

roelof21:01:00

so better not use it

seancorfield21:01:46

Poor style because you're trying to do too much in a single pipeline. Refactor it into small functions to transform the data.

seancorfield21:01:07

And don't forget about my comment about time and map.

roelof21:01:44

I will remember that the outcome is not the real time

seancorfield21:01:48

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.

roelof21:01:07

then I will delete it

seancorfield21:01:25

Compare (time (mapv inc (range 1000))) and (time (map inc (range 1000)))

roelof21:01:26

I was hoping it would say how long it all took

seancorfield21:01:25

(time (doall (map ..))) if you must use map. Or use an eager process like mapv.

roelof21:01:06

the map is much faster then the mapv one

seancorfield21:01:27

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.

roelof21:01:58

I see a big difference

roelof21:01:03

that is all I mean

seancorfield21:01:52

Right, because (time (map inc (range 1000))) is not timing how long it takes to map inc over that range.

seancorfield21:01:22

It's just timing how long it takes to create an (unrealized) lazy sequence object -- it isn't doing any work.

roelof21:01:43

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)))

seancorfield21:01:48

(time (doall (map inc (range 1000)))) will force realization of the mapping.

seancorfield21:01:13

You're using ->> at the beginning, not ->

seancorfield21:01:52

It should be (-> (client/get ..) ... (->> (map ..) (map ..)))

roelof21:01:54

time for me to sleep

roelof21:01:00

but the code is working

seancorfield21:01:23

Now you've fixed it 🙂 Do you understand what you did wrong?

roelof21:01:10

again I want to fast

roelof21:01:16

very bad old habit

roelof21:01:33

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))

roelof21:01:38

oke, tomorrow I try to rewrite this part

roelof21:01:07

(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})))

roelof21:01:14

the same way

roelof21:01:16

I know that it's wrong

seancorfield21:01:24

You're using ->> again and you need ->, and then switch to ->> later in the pipeline

roelof21:01:37

that what I rewrite it as you learn me afew moments ago

seancorfield21:01:45

Oh, you mean you need to rewrite it "the same way" as you rewrote the other code?

seancorfield21:01:56

Sorry, it's hard to understand what you're asking at times.

roelof21:01:08

that is what I try to say that I will try to make that work tommorow

roelof21:01:50

sorry, english is not my mother languages and I was years and years ago very bad in languages

seancorfield21:01:23

NP. I get it now. Good luck tomorrow -- once you've had some sleep!

seancorfield21:01:37

And remember: take small steps and try each piece out in the REPL.

roelof21:01:23

yep, could not let it wait till tomorrow

roelof21:01:35

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))

🎉 1
seancorfield22:01:38

Nice breakdown! Yay!

roelof22:01:09

oke, then now study ring and computure to make the back-end

roelof22:01:20

and then decide what to use for the front-end