Fork me on GitHub
#beginners
<
2022-10-04
>
Yab Mas08:10:24

Meta-data about the return-type of a function doesn't seem to be applied to objects that capture the result of this function. Is this expected?

(def ^Int x 1)
(meta #'x) ; Contains :tag Int in meta-data

(defn my-int ^Int [] 1)
(def y (my-int))
(meta #'y) ; Doesn't contain :tag Int in meta-data
This is just to illustrate the problem. In reality I'm trying to add a ^js typehint to objects that capture the result of a function, but it doesn't seem to work... So this is an Cljs project, don't know if that makes any difference.

Ferdinand Beyer10:10:24

This is to be expected. The type hint metadata is meant as a hint from the programmer to the compiler, not the other way around. Often times the compiler can still infer types, but it will not expose this information as metadata (to my knowledge).

Ferdinand Beyer10:10:24

For example, in this snippet, the compiler will know that f is a java.io.File, and can generate a call to .exists without reflection:

(let [f (io/file "/")]
  (.exists f))
But this will evaluate to nil:
(let [f (io/file "/")]
  (meta f)) ;; => nil

Yab Mas11:10:42

Hmmm, I see. I'm not sure if your example proves the point as this was always gonna return nil according to https://stackoverflow.com/questions/30082585/type-hint-stored-as-metadata-in-clojure. But I think you're right none-the-less as I can't find the type-hint in the meta-data when I do extract it properly. It's not clear to me what this means for my situation though. I'm adding ^js typehints to my code to avoid problems with advanced-compilation with shadow-cljs as explained in https://shadow-cljs.github.io/docs/UsersGuide.html#infer-externs. Would adding the ^js typehint for the return value of the function be enough to avoid this issue. I guess it's save to assume it works if there are no more warnings in the compiler output...? Maybe I should have posted this in the shadow-cljs channel, but I got confused with meta-data in general because of this issue.

Ferdinand Beyer11:10:33

> I guess it's save to assume it works if there are no more warnings in the compiler output...? I'd say so, too. In general, you should use type hints sparingly, and only need them when working with JavaScript code directly. If you do a lot of interop, consider writing a few "wrapper" functions that interop with JavaScript and use type hints. Functions calling these and therefore staying "in ClojureScript land" do not need typehints.

👍 1
Chase13:10:14

I have a question about laziness and for comprehensions. For a small example say I have something like:

(first                                                                                           
  (for [i (range 1000)                                                                            
        :when (even? i)]                                                                         
    i))
where I basically want the first match on a predicate. I know this particular example is silly and you could use some or other better ways to do things like this, but say you wanted a for comprehension for other reasons so you have to do it this way. The way I see it, this is inefficient because the for comprehension will go through the entire range and then you just take the first. But with chunking, where lazy seqs realize the first 32 elements (if I'm describing that correctly) does that mean this will stop and return the first element after only 32 calculations?

Chase13:10:04

The reason I was using for is because there were some other intermediate steps and such so the for makes it more clear what is going on than when I was trying to get it all done with some.

lispyclouds13:10:51

also you could use :while instead of :when. That should perform better as well.

Alex Miller (Clojure team)13:10:30

if you care about the extent of lazy evaluation/chunking, you should not use lazy evaluation - use loop or reduce/`reduced` etc

1
Chase13:10:00

Yep, reduced was the exact approach I was thinking of taking here.

Chase13:10:48

I was discussing it with someone else though and they were thinking that maybe that for would stop evaluating after the first 32 calculations and just take first from that initial chunk.

Alex Miller (Clojure team)14:10:04

it would because it's lazy, but the exact details depend on what sequences are being used at the bottom and which sequence operations are being used under/over the for (also note that long range s are highly optimized and relatively unlike other sequences internally)

Alex Miller (Clojure team)14:10:55

in 1.12 ranges will actually create 1 giant "chunk" of all values (but it doesn't actually compute them until needed). but again, if you care, don't use sequences

Chase14:10:14

Good stuff, thanks for the fast replies! I sort of get the part about the "if you care, don't use sequences" but what trips me up is maybe doing it this way makes the code much clearer so if you aren't going to pay a "laziness price" because it will short circuit then it would be cool to be able to do so. But it is hard for me to wrap my head around when exactly you are paying that price, which is probably the lesson to be learned

lispyclouds14:10:15

Lessons like this make me read the docstrings much more than the code. I do that way more often now. Quite enlightening!

craftybones15:10:45

When in doubt, transducers? 😉

Kevin Izevbigie15:10:55

In javascript, I would use map to create a new array “all at the same time”. Where as, if I wanted to loop sequentially (for async ops) I would use for of In Clojure, does map work the same way? Would it present the entire list of new values in one go? My goal is to figure out the best way to: 1. Read a csv 2. create a new domain based on the csv row 3. Use the new domain as part of a HTTP request 4. return the data for domain 1 5. repeat operation for next item. I am currently stuck on step 3. I know how to send a HTTP request but im not sure how to do it for an entire list of data inside a csv. Here is my code so far:

;;domain - root url for api domain

(def create-api-domain (fn [domain-name]
                         (str domain (first domain-name) path)))

(with-open [file (io/reader "src/frosty/domains.csv")]
  (doall
   (map create-api-domain (csv/read-csv file))))

craftybones15:10:38

And you want this to happen asynchronously?

craftybones15:10:49

so each request fires asynchronously?

craftybones15:10:54

Can you show us how you're fetching a single request?

Kevin Izevbigie15:10:03

In my JS app (which I am recreating) each answer comes back first and is saved in a file. Then the next one fires

craftybones15:10:11

That is synchronous

Kevin Izevbigie15:10:26

Ahh sorry yes I meant sync

craftybones15:10:45

Ok, that's not hard to do here...all you have to do is write a function that accepts a domain, fires the request and returns the data from that call

craftybones15:10:50

and you map over the whole thing

Kevin Izevbigie15:10:30

I see, so would that function replace my current create-api-domain and I just do the whole thing in that function?

craftybones15:10:56

Like this:

(map (comp fetch create-api-domain) (csv/read-csv file))
where fetch is simply a function you write to fetch the data from the url returned by create-api-domain

craftybones15:10:48

Can you show me how you are fetching a single request?

Kevin Izevbigie15:10:03

Sure, ill post it here when i get back to my comp

fvides12:10:07

One thing to consider is that map is a lazy operation, so you must get sure that you realize the result list, or better use doseq if you don't need the results.

popeye15:10:45

I have a variable k, , (def k [[:a :b] {:address "England" :city "London"} [:a :b] {:address "NY" :city "abcd"}  [:a :b] {:address "Canada" :city "Toranto"} ]) 

when I run (println (apply array-map k)) result is {[:a :b] {:address Canada, :city Toranto}}

how to get the resullt something like 

{[:a :b] [{:address "England" :city "London"} [:a :b] {:address "NY" :city "abcd"}  [:a :b] {:address "Canada" :city "Toranto"}]}

dpsutton15:10:53

Bit hard to read your formatting. But you want a map that has the key [:a :b] three times, each with a different value?

dpsutton15:10:22

maps can only hold a single value to a distinct key. it if the map m was as you wanted here, what would (get m [:a :b]) return?

dpsutton15:10:47

since there are three distinct values you want associated with that. How would you say which of the three you wanted

popeye15:10:20

all those threee values will be in the vector {[:a :b] [{:address "England" :city "London"} [:a :b] {:address "NY" :city "abcd"} [:a :b] {:address "Canada" :city "Toranto"}]}

dpsutton15:10:28

so you want a map that has [:a :b] as a key once and its value is a vector of those three maps [{:address "England" …} {:address "NY" …} {:address "Canada" …}]

craftybones15:10:02

(group-by first (partition 2 k))
should do it

craftybones15:10:48

Kind of. Your grouped values will still contain the key

dpsutton15:10:50

(let [k [[:a :b] {:address "England" :city "London"}
         [:a :b] {:address "NY" :city "abcd"}
         [:a :b] {:address "Canada" :city "Toranto"}]]
  (reduce (fn [m [k v]] (update m k (fnil conj []) v))
          {}
          (partition 2 k)))
{[:a :b] [{:address "England", :city "London"}
          {:address "NY", :city "abcd"}
          {:address "Canada", :city "Toranto"}]}

popeye15:10:31

Will take a look

pppaul17:10:05

if you want to use group-by, but don't want certain things on the values-seq, you can use (update-vals) after. (-> (update-vals (partial map second))) in the case of craftybones example.

popeye06:10:31

@U0LAJQLQ1, I have a question here , why do we need partial here? this question will help me to understand more about partial here

craftybones06:10:20

@U01J3DB39R6 partial creates a partial function where some of the arguments are supplied initially and the remaining arguments are supplied at call time. For instance

(def add-two (partial + 2))

(add-two 3) ;; 5

craftybones06:10:14

update-vals accepts a function that takes an old value for a given key and returns the updated value for that. This is done for every key in the map. For instance

(def nums {:a 1 :b 2})

(update-vals nums inc) ;; {:a 2 :b 3}

craftybones06:10:51

our group-by function returneda map whose values were partitioned lists as follows:

{[:a :b] ([[:a :b] {:address "NY"}] [[:a :b] {:address "Canada"}])}

craftybones06:10:54

We want to take the map above and turn it into

{[:a :b] ({:address "NY"} {:address "Canada"})}

craftybones06:10:22

Honestly, I recommend you try each of these functions in isolation and understand them thoroughly before applying them on your example

popeye07:10:51

@U8VE0UBBR Thanks for the detailed reply, my question was why (partial map....)

skylize23:10:16

Is there any case where a list could contain the function defined here as foo, but then comparing to the symbol foo inside the function would fail to filter it out? (Maybe is there a way for the function to refer to itself that does not rely on a var?)

(defn foo [m]
  (let [c (filter (fn [x] (not= foo x)) m)]
    c))

(foo [foo :bar])

hiredman23:10:04

depends, but that does what you would expect in clojure, it returns (:bar)

hiredman23:10:35

to be clear, you aren't using symbols at all there at runtime

hiredman23:10:33

the symbols used there are identifiers in code that the compiler uses when compiling, but don't exist at runtime

skylize23:10:51

Cool. So what is the "depends" part? I tried calling (my-ns/foo [my-ns/foo :bar]) in another namespace, and that worked fine, and even after indirection of a new name (def fee my-ns/foo) (fee [fee :bar]). So I think I am okay? Just somehow feels like it might be brittle, and I don't know enough about internals to know if that fear is real.

hiredman23:10:22

function identity is by reference, and functions are closures, so it is possible to construct multiple instances of "the same function" (for some value of sameness), and they won't compare as equal

hiredman23:10:09

it is unlikely you will run into that if are always dealing with def'ed functions

skylize00:10:25

Ok. You mean something like this, right?

(defn make-foo [] (fn foo []))
(= (make-foo) (make-foo))  ;;  false
That is not an issue here. Only created once. gratitude-thank-you

hiredman00:10:28

a var is also a mutable reference, and you have two references to it (the two foos, one as the function to call and one as part of the argument), so technically that is a race condition, the value of the var could change between when each reference gets it

skylize00:10:05

> so technically that is a race condition Yeah. That worries me.

skylize00:10:34

Is there an alternative?

skylize00:10:24

I don't intend to mutate it. So expect I'm probably fine. But I would much rather a bulletproof alternative.

hiredman00:10:11

It depends on what you are doing, if you look at something like add-watch, you see it makes you supply a key that becomes the id of your function

hiredman00:10:53

Which is maybe the most flexible approach

skylize00:10:59

I have a recursive process that repeatedly steps through a list of computations, this function being one of them. That list is defined by the user, with this function being probably included. Whenever this function is called it needs to start a new loop, but with itself not in the list. I don't need to be able to change the function, I just want to be sure that when it tries to removes itself, it actually does. In the happy path, user just makes a list, which includes this fn, and starts the loop. Maybe I am just overthinking this?

craftybones01:10:09

Perhaps. Also, consider remove

craftybones01:10:43

(defn foo [m] (remove #{foo} m))
is all you need

skylize01:10:08

Oh yeah. That's a lot more clear. (Doesn't address the question though.)

quoll16:10:59

I know I’m late to the party, but I’d like to add something to @U0NCTKEV8’s comment that a var is a mutable reference. Yes, they’re mutable. That said, it’s a serious code smell if you’re mutating vars. They’re typically created with the expectation of staying unchanged. It’s useful to change them for testing, instrumentation, etc, but it’s typically not a good idea in standard development.