Fork me on GitHub
#beginners
<
2021-01-22
>
babardo07:01:19

Hello, why? I was expecting empty collection!

(vals {}) => nil

phronmophobic07:01:10

the difference almost never comes up. from the vals docs: "Returns a sequence of the map's values, in the same order as (seq map)." and (seq {}) is nil, so it seems to make sense.

🙏 2
phronmophobic07:01:29

some amount of related background can be found at https://clojure.org/reference/lazy

phronmophobic07:01:20

a common mistake when first coming to clojure is to focus too much on specific types whereas idiomatic clojure code tends to focus on interfaces.

phronmophobic07:01:48

for your example, (seq coll) is used to check if a sequence has remaining items and (empty? coll) is used to check if the sequence is empty. both of these idioms will work regardless of the underlying concrete type (eg. map, vector, list, nil)

Tim Robinson07:01:19

I've got a http://java.io.File object and I want to get the path to the file. is it OK to just call str on it ?

phronmophobic07:01:50

seems fine. calling (.getPath f) would also be reasonable

phronmophobic07:01:09

or depending on the use case, (.getCanonicalPath f) or (.getAbsolutePath f)

Tim Robinson08:01:25

All I'm doing is using using me.raynes.fs to split out the filename from the parent folder name, I'm not actually doing any I/O so I think str will be fine. it seems more "string-manipulation-y"

roelof09:01:06

good morning all

👋 2
Tim Robinson09:01:21

a nice easy question now for you :-) I want to extract a vector containing selected values from a map in a specified order e.g. (select-vals {:name "john" :age 37 :height 186} [:height :name]) ==> [186 "john"]. I've come up with (defn select-vals [m ks] (reduce #(conj %1 (m %2)) [] ks)) which does the trick but experience has told me that every time I want to write a reduce there's usually a built-in function I've missed

roelof09:01:41

for me?? why ??

Mno09:01:53

@tim.j.robinson you were correct there's a function called select-keys that does that

Mno09:01:41

Oh.. Not sure about the specified order.. You'd have to call vals after that

Mno09:01:50

You could create a new one with (comp vals select-keys) but it doesn't retain order..

Daniel Stephens09:01:36

((juxt :height :name) {:name "john" :age 37 :height 186}) perhaps

Daniel Stephens09:01:02

juxt takes fns as arguments, and returns a fn. When called it returns a vector whose first element is the result of calling the first fn and so on.

Tim Robinson09:01:14

dang! I wouldn't have found that. yes that's it - thanks 🙂

🎉 2
raspasov10:01:43

When I was starting with Clojure, juxt was one of those functions that blew my mind 🙂 Actually, it’s pretty straightfoward… just for clarity this would be equivalent to the example above:

((fn [m] [(:height m) (:name m)]) {:name "john" :age 37 :height 186})
This is exactly what juxt does in this case. Returns a function like: (fn [m] [(:height m) (:name m)])

simongray11:01:51

juxt is the Spanish inquisition of Clojure

❤️ 1
Yoav Tzfati16:01:23

Hi! first time posting 🙂 I have an issue with Calva where if I evaluate and pretty print a form and the output is several lines, I only see the first (I can see all lines in output.calva-repl). Is there a way to see all lines? I'm running in vscode on WSL2 @viebel

pez16:01:20

The inline display only shows the first line. You can peek definition to peek into the output/repl without opening it. Welcome to #calva !

❤️ 1
hiredman17:01:31

avoid hiccup style formats as internal representations, they are nice for people to write, but because the structure is less regular they are annoying to write programs to manipulate

👍 1
GGfpc18:01:55

Let's say I have a map where the keys are Characters and the values are vectors or lists. Let's also say I have a list of tuples like (key_in_map, index_in_vector, value) . For each tuple I want to update the map by setting value provided in those "coordinates". What would be the best way to do this other than mutating the map on each iteration (which I'm not sure is even possible)?

hiredman18:01:38

It is, and that is the way to do it, using reduce

hiredman18:01:09

If the values in the map are vectors it is a reduce using update-in, if they are lists or seqs it is pain

andy.fingerhut18:01:34

It is certainly possible to do it with reduce and the most common way to do that in Clojure doesn't actually mutate the map -- it creates a new map in each step, each of which is nearly the same as the map created in the previous step.

andy.fingerhut18:01:30

There is a feature called transients that can be used that will actually create a mutable map, and mutate it in each step. That is an optional performance enhancement, and the code to do it looks almost the same as if you are using immutable maps.

evocatus18:01:06

can I use names from outer scope in binding forms? So that they are like closures

noisesmith18:01:53

yes, the things we put binding forms into are things that create closures

noisesmith18:01:05

(functions, let, binding, for, loop etc.)

DG20:01:28

Hi, I'm still new to Clojure and I want to transform a result set from:

[{:provider "A" :rate 10 :channel "C1"}
 {:provider "A" :rate 10 :channel "C2"}
 {:provider "A" :rate 10 :channel "C3"}
 {:provider "B" :rate 20 :channel "C4"}
 {:provider "B" :rate 20 :channel "C5"}
 {:provider "B" :rate 20 :channel "C6"}]
To:
[{:provider "A" :rate 10 :channels [{:id "C1"} {:id "C2"} {:id "C3"}]}
 {:provider "B" :rate 20 :channels [{:id "C4"} {:id "C5"} {:id "C6"}]}]
Since the query is a JOIN, provider and rate gets repeated on the result set. I want to group the channels by those. I've tried map, reduce, group-by, partition-by but I haven't gotten exactly to where I want yet. Any pointers?

noisesmith20:01:15

@diego559 I'd start with (group-by :provider ...) - that nearly fixes it, it's just a bit of massaging after that

noisesmith20:01:33

nothing in core does it directly, but I think group-by is the shortest path

noisesmith20:01:17

also, if provider / rate don't match, what happens then?

noisesmith20:01:34

if they separate, you'd want (group-by (juxt :provider :rate) ...)

DG20:01:35

They always do because it's a JOIN, the come from the same record of the same table

DG21:01:43

I think I did it. Does it look fine? Suggestions on how I can improve? Since I'm probably going to use this model in multiple other queries, I would like to get it right.

(->> (group-by :provider v) 
     (map (fn [e] 
            (let [[provider record] e 
                  rate (:rate (first record)) 
                  channels (map :channel record)] 
              {:provider provider :rate rate :channels channels}))))

noisesmith21:01:21

you aren't getting the :id keys like in your example

noisesmith21:01:46

and usually we'd use {:keys [rate]} (first record) instead of rate (:rate (first record))

noisesmith21:01:00

or even [{:keys [rate]}] record

noisesmith21:01:36

but I feel like the implementation detail that the rate always matches the provider is being buried here - if you don't explicitly check, it should at least be documented

DG21:01:48

Thanks!

DG21:01:54

I used juxt like you suggested to be more future proof. Now I need to figure out how to create a map with :id for the channels.

(->> (group-by (juxt :provider :rate) v) 
     (map (fn [e] 
            (let [[[provider rate] record] e 
                  channels (map :channel record)] 
              {:provider provider :rate rate :channels channels})))) 

noisesmith21:01:54

that should be straightforward with map (fn [x] {:id x}) or (partial hash-map :id) or even #(hash-map :id %)

DG21:01:35

I was trying (map #({:id %}) but realized it doesn't work, don't really sure why it differs from (map (fn [x] {:id x}))

noisesmith21:01:35

@diego559 reading without evaluating is informative - ' does that

user=> '#({:id %})
(fn* [p1__155#] ({:id p1__155#}))

noisesmith21:01:01

if we rename the arg:

(fn* [x] ({:id x}))

DG21:01:52

Sorry, don't really understand what you mean. I thought #() and (fn []) where equivalent

noisesmith21:01:53

it's a reader macro, using ' shows how it expands without evaluating it - notice how it adds parens around the body

noisesmith21:01:10

fn has var-args for the body, so an implicit do

noisesmith21:01:18

#() fills in the fn body as a template, and only uses one spot, so prevents using that implicit do

noisesmith21:01:50

imagine if #(foo %) expanded to (fn [x] foo x)

noisesmith21:01:24

that would be following the same rule we'd need for #({:id %}) to become (fn [x] {:id x}) but it clearly isn't what you want

👏 1
dpsutton21:01:08

that's the best and most clear explanation i've seen for this

☝️ 1
DG21:01:09

Makes lots of sense, thanks!

roelof21:01:49

hmm, can I rewrite this to more idomatic clojure

(defn convert-number-to-collection [number]
  (->> number
       str (map (comp read-string str))

noisesmith21:01:51

read-string is a big powerful thing, here it's probably better to use #(Long/parseLong %)

noisesmith21:01:19

or just #(Long/parseLong (str %)) and skip the comp call

roelof21:01:10

but then it would not change for example 12 into (1 2 ) what I need @noisesmith

noisesmith21:01:35

how wouldn't it?

roelof21:01:01

when I do

(defn convert-number-to-collection [number]
  (->> number
       str 
       Long/parseLong))

roelof21:01:15

I get the output 12 instead of (1 2)

noisesmith21:01:17

I never mentioned using it outside a lambda

noisesmith21:01:46

it's a replacement for read-string, not the entire map call

roelof21:01:50

sorry, then I misunderstood you

noisesmith21:01:23

No matching method ParseLong found taking 1 args for class java.lang.Long
(cmd)user=> (defn convert-number-to-collection [number]
  (->> number
       str
       (map #(Long/parseLong (str %)))))
#'user/convert-number-to-collection
(ins)user=> (convert-number-to-collection 12)
(1 2)

Robert Mitchell21:01:45

Alternatively, you could do something like this:

(->> 123 str (map #(Character/getNumericValue %)))

noisesmith21:01:58

@roelof also consider this behavior of the read-string version:

)user=> (convert-number-to-collection "1.23")
(1 . 2 3)

noisesmith21:01:11

@robert.mitchell36 that's a good one - btw map already calls seq

Robert Mitchell21:01:53

Good catch. I edited it out.

yiorgos21:01:46

I am reading the Joy of Clojure and on the chapter about Macros, it says that if is a macro but when I do in the repl (source if) it says source not found

noisesmith21:01:55

it's a special form, those are things implemented in the java code of the compiler

user=> (special-symbol? 'if)
true

noisesmith21:01:14

conceptually it's like a macro (and might have even been one at some point?) - definitely not a macro today

yiorgos21:01:52

oh, I see.

noisesmith22:01:13

the way it's like a macro is that its body isn't pre-evaluated (even though it's the java code of the compiler managing what's done with the body, rather than a clojure function)

noisesmith22:01:49

to see an example, maybe the source of and would help (similar job to if, actually a macro)

👍 1
Max Deineko23:01:59

I am trying to get a grasp of symbol resolution in clojure and have yet to understand behaviour of f (as well as difference to behaviour of g) below:

(def a 2)
(def b {3 3})

(defn f [x] (- a (first (map - [x]))))
(defn g [x] (b x))

((juxt f g) 3)
;; => [5 3]
(with-redefs [a 7, b {3 12}, - *] ((juxt f g) 3))
;; => [4 12]
It seems that the two occurences of - inside f are resolved differently -- is that because in head position it is being compiled at definition time? If so, what differentiates that + inside f from b in g? Both (ifn? +) and (ifn? b) are true for above values..

hiredman23:01:42

+ and - have some special optimizations to try and make math fast

Max Deineko23:01:01

ah, I see, I'll try this out with different functions then

hiredman23:01:06

something to keep in mind is with-redefs is not really intended for us outside of things like stubbing/mocking in tests

hiredman23:01:55

for a function like (defn f [a] a) you can imagine a call like (f 1) gets compiled to something like (.invoke ^clojure.lang.IFn (deref (resolve 'f)) 1)

hiredman23:01:25

the var resolution doesn't actually happen everytime, it is resolved once when the class that the code is attached to, which makes things faster, and usually doesn't behave any different from the above.

hiredman00:01:24

+ and -, and some other functions in some circumstances behave like macros, and sort of rewrite themselves

Max Deineko00:01:36

yes, I see with str in place of - above the behaviour is different

hiredman00:01:04

(+ 1 2) will rewrite itself before compilation to something like (clojure.lang.Numbers/add 1 2)

Max Deineko00:01:34

> it is resolved once when the class that the code is attached to when the class does what?

hiredman00:01:55

whoops, is loaded

hiredman00:01:59

the compiler then rewrites some static method calls (under circumstances I don't recall) to a simpler series of bytecodes

Max Deineko00:01:17

Thanks for explanation -- very helpful!

Max Deineko00:01:08

It's not that I'd want to sprinkle my code with with-redefs s and defns, just trying to explore how mutable clojure functions are and in what contexts guarantees or assumptions can be broken 🙂

hiredman00:01:55

functions aren't mutable at all, what you are mutating are var s which are kind of like a special kind of pointer or handle that is used for linking code together

hiredman00:01:21

when you use def you are creating a var with a name, and setting the initial thing the var points to the given value, subsequent defs of the same name change what that var points to

hiredman00:01:26

alter-var-root! and intern are both other ways to create/mutate vars

1
didibus00:01:37

If I'm not mistaken, its because of a feature called inlining. You can define functions to be inlined, which will well, inline them.

💡 1
1
didibus00:01:51

And - and + are inlined

didibus00:01:57

Its kind of a hidden feature not really meant for general consumption, and it seems that they kind of reserve the right to break how it works in the future. So best to assume its a behind the scene optimization for core functions.

didibus00:01:02

But basically, when the function is directly called, the compiler inlines the call, as if it was a macro, using similar semantics to macros. But if the function is passed as an argument (higher order) and later used, the function is evaluated normally, as a function.

Max Deineko00:01:07

@U0K064KQV thanks for the details, that makes it quite clear

Max Deineko00:01:58

@U0NCTKEV8 by «mutable» I wanted to express the fact that what one might naïvely consider to be value of, say, (fn [x] (str x)) , can change after evaluation, depending on context (as e.g. inside with-redefs). Is there a better term for that? As far as I can see atm (values of) functions are probably best thought of as immutable values which reference other values, with those references being possibly subject to change, and that makes them different from simple immutable values in my understanding. Currently I can't think of other types of values which would use that kind of implicit referential sharing, but I might very well know too little about clojure atm.

andy.fingerhut01:01:10

It reminds me of some discussions about Clojure collections allowing one to include mutable objects inside of them, e.g. the vector [1 2 3 a] after (def a (atom 4)) is a Clojure collection, but I doubt many people would call it an immutable value, or an immutable collection, because it contains an atom.

andy.fingerhut01:01:58

There is a very real sense in which Clojure functions containing Var references are mutable -- if you mutate what the Var refers to, the behavior of the function can change to something it never could have done before.

2
didibus03:01:39

Functions themselves are immutable, but the Var that points to them isn't. Does that help to understand it?

didibus03:01:09

When you redef, you're not changing the function, you're changing the function the Var reference points too

Max Deineko04:01:27

I was thinking more about "vars pointing from them" and what exactly should be considered the immutable value of a function -- as in difference between f and v here:

(def m {0 1})

(def f (fn [x] (m x)))
(def v [m])

((juxt f v) 0)
;; => [1 {0 1}]

(def m {0 2})
((juxt f v) 0)
;; => [2 {0 1}]
-- changing m here does not change v but changes result of evaluating f .

Max Deineko05:01:18

Currently my working model is "function has immutable structure, but everything in its closure is captured by reference", kind of like if we had something like [0 1 2 #always-deref (defn a (atom 4))] to always deref its contents upon evaluation.

didibus06:01:32

Oh I see what you mean. The difference is that Clojure auto-derefs things, but not inside fn:

(def m {0 1})

(def f (fn [x] (m x)))
(def v [#'m]) ;; We tell Clojure not to dereference `m` automatically before creating the vector, by asking Clojure to pass us the Var and not the value the Var points too.

(def m {0 2})
(f 0)
;; => 2
@(v 0)
;; => {0 2}
And similarly, you can chose not to capture the Var in the closure, by forcing it to be dereferenced:
(def m {0 1})

(def f (let [m m] (fn [x] (m x)))) ; Since Clojure doesn't deref inside `fn`, we need to deref it outside, and close over the derefed value
(def v [m])

((juxt f v) 0)
;; => [1 {0 1}]

(def m {0 2})
((juxt f v) 0)
;; => [1 {0 1}]

didibus06:01:41

So its not that everything in its closure is captured by reference, everything is captured as-is. Its that Clojure has triple indirection. The symbol m points to the Var #'m which points to the value {0 1}. So the function does not close over the symbol, but it closes over the var that the symbol points too. But when you do [m], Clojure will first resolve the value pointed by the symbol 'm in the current namespace, which is the Var #'m, and then it will do one more thing (which it doesn't do when inside fn), it will also deref the Var, so it will get the value it points too which is {0 1} and it will pass that to the constructor of vector, thus your vector contains: [{0 1}]. Like I showed in my example, you can prevent this auto-deref of Vars by Clojure by telling it to give you the Var using (var m) or #'m.

👍 2
Max Deineko06:01:38

Thanks for laying it out in detail! And to everyone in this thread -- it's been enlightening 🙂