Fork me on GitHub
#beginners
<
2019-10-04
>
Shaitan10:10:12

what is more efficient :`(async/<!! (clojure.core.async/timeout` or some thread sleep ?

jumar12:10:59

with a/<!! you block the current thread anyway so I guess it doesn't matter: if you just want simple sleep I would use Thread/sleep

jumar12:10:24

a/timeout is more useful with alts! and/or inside go block

jumar12:10:38

btw. this is a good question for #core-async

4
Abhinav Sharma11:10:10

Hello Clojurians, I came across a somewhat odd behavior for apply (if I’ve identified correctly) and the behavior of function encapsulated apply seems to be a bit different then standalone Below are the function definition and the state-map

(def mutation-env {:ref   [:person/id 1]
                     :state (atom {:person/id {1
                                               {:person/id 1 :person/name "Dad"}}})})

  (defn update-caller!
    [{:keys [state ref] :as mutation-env} & args]
    (apply swap! state update-in ref args))
Here’s the different response wrt the string Mom.
(update-caller! mutation-env
                  assoc :person/name "Mom") 

;; => #:person{:id {1 #:person{:id 1, :name "Mom"}}}


  (apply swap! (:state mutation-env) update-in (:ref mutation-env)
         assoc :person/name "Mom")

;;=>  #:person{:id {1 {:person/id 1, :person/name \M, \o \m}}}

Am I missing some detail here, please let me know 🙂

jaihindhreddy12:10:57

The body of update-caller! in your first call to it with the arguments substituted is equivalent to this: (apply swap! (:state mutation-env) update-in (:ref mutation-env) [assoc :person/name "Mom"]) As Michiel pointed out, a minimal repro helps. (apply + 1 [2 3]) is equivalent to (+ 1 2 3) And you've done something like (defn my-add [a & args] (apply + a args)), and called it like this, (my-add 1 2 3). In the body of my-add, args is bound to [2 3], which apply splices in the call to + making it equivalent to (+ 1 2 3) and you get the same result.

jaihindhreddy12:10:27

Sorry if the explanation is unclear. Am unable to articulate it any more clearly 😅

lxsli12:10:01

jalhindhreddy explained but here are some more examples: & args collects additional args into an array. apply unpacks its last arg as additional arguments. Let's (defn f [& a] a), similar to vector. Then (f 1 2) -> [1 2] (apply f 1 [2 3]) -> [1 2 3] (apply f 1 ["Mom"]) -> [1 "Mom"] (apply f 1 "Mom") -> [1 \M \o \m] because strings are seqs of characters

Abhinav Sharma12:10:41

Thanks @U883WCP5Z and @U9MDWLP5Y, now I have understood the reason. Regarding the minimal example, hmm, that’s a skill which is still elusive to me 😅 I do think this involves being able to deconstruct the problem already - well, getting there for sure

lxsli13:10:57

It's easiest by trial and error, simply hack bits off until it no longer fails / runs, then put the last thing back and try hacking something else off

jaihindhreddy13:10:26

This might be of interest: https://www.youtube.com/watch?v=FihU5JxmnBg On debugging in general. Lots of interesting reference points mentioned in the talk...

Abhinav Sharma13:10:45

Thanks @U9MDWLP5Y - will keep this in mind!

Abhinav Sharma13:10:34

@U883WCP5Z cool this seems useful - learning to actually use a debugger in clojure in a priority for sure.

borkdude11:10:05

In update-caller you're calling apply with a sequence of args as the last arg. In the bottom example you're providing them as separate args, not wrapped in a seq

Abhinav Sharma12:10:58

Ohh, got it - thanks! Now, I realized that the args in the defn gets in as a seq in this case.

borkdude11:10:25

it often helps to bring these examples down to smaller repros to understand what's going on

👍 4
Mario C.16:10:31

Is it better to return a vector/list from a function instead of a set? If I return a set should I name the variable to reflect that the element binded is a set?

Mario C.16:10:56

Or this a book about best practices out there?

seancorfield16:10:41

@mario.cordova.862 "It Depends". If returning a set makes more sense, do that. If returning a vector makes more sense, do that. If returning a general sequence makes more sense, do that.

seancorfield16:10:08

Do callers care about the order of results? How will they use the result?

andy.fingerhut16:10:24

And regarding your question about advice on naming things, I still need to read, but have heard good reviews of, the book "Elements of Clojure" https://leanpub.com/elementsofclojure

parens 4
jumar09:10:09

Yep, the first two chapters are relatively clear and practical. The first chapter is mostly useful for some naming things. As regards to @mario.cordova.862’s question I don't think it's a generally used convention to capture return type in the name - use spec and/or document it in docstring.

Mario C.16:10:38

The last line in the function is doing a set intersection but it makes more sense to just return a list but I was just returning the set as is. Wondering if it was best practice to turn the set into a list

seancorfield16:10:07

Vectors are Counted and Indexed. Sets are Counted but not Indexed. Vectors are associative on their indices. Sets are not associative but can be used as predicates (they're "sort of" associative on their elements).

Mario C.16:10:28

Oh I saw the book recommended on hackernews

andy.fingerhut16:10:53

A set can be traversed using all of Clojure's sequence-based functions, just like a vector, map, or list can, so there is no harm in returning it as a set if the order does not matter, if the callers expect it to be a sequence.

4
andy.fingerhut16:10:01

Note that long term, if this function is used by other people on a team, or around the world, some of them may notice that it is a set, and choose to write code that depends upon that fact.

andy.fingerhut16:10:34

But I do not want to place unnecessary worry on your shoulders -- just an observation that others have noticed in software development, too. Just remembered that some people call that Hyrum's Law: https://www.hyrumslaw.com/

Mario C.16:10:23

Got it! Another thing is that I am using that result in another function and doing some set manipulation with it as well. But I can see how someone who is looking at this code might not realize its a set. So my thinking was returning a list and then in the other function explicitly turning that list into a set so its obvious what is happening

Mario C.16:10:32

The isn't a library so its all internal

andy.fingerhut16:10:48

As an efficiency note, it seems better to leave it a set, for your own use. If others only need its elements as a sequence, they can traverse it just fine whether it is a set or list, as mentioned above.

👍 4
andy.fingerhut16:10:31

But the efficiency note might not be very important if your code that would turn it back into a set internally isn't in the 'hot path' of your application.

Mario C.16:10:40

hmm in that case I need to keep it as a set then. Ill probably just some quick notes as comments

seancorfield16:10:25

If some callers (like the use in the other function) require a set then I would document that it returns a set and maybe even add a spec for :ret set? 🙂

bartuka16:10:02

hi, I have a question about multi-methods. consider the following code:

(defmulti test (fn [m] (:type m)))

(defmethod test :type1
  [m]
  (+ (:price m) (:fine m)))

(defmethod test :type2
  [m]
  (- (:price m) (:discount m)))
I find very convenient to use a map key to dispatch over defmethods, however when I am actually using the test function above, the keys necessary to compute the operation may vary and I find it difficult to know that beforehand. In this example the map would need a key called fine *or* discount depending on the dispatch path

bartuka16:10:29

there is anything (other than not using multimethods here) that could help this situation?

andy.fingerhut16:10:03

The function you use in the defmulti definition can be as long and complex as you want. It is not restricted in any way by Clojure. Realize that it will be run on every call of the multimethod, though, so could become a performance issue if it takes a long time to compute.

bartuka16:10:51

I am not understanding your suggestion. My question is almost about how to document better what the dispatched methods expected to receive inside the map

andy.fingerhut16:10:59

sorry, I misunderstood the point of the question, then.

andy.fingerhut16:10:08

If I understand it correctly now, the issue you are describing can occur with normal Clojure functions, too, not only multimethods.

bartuka16:10:25

(test {:type :type1 :price 20 :fine 1}) or (test {:type :type2 :price 20 :discount 5}) both are options of the same function called test to the enduser. However, the user knows that :type2 has a different behavior, but how they will know what keys the input map should have? The only way would be to inspect the code and see that you are using :discount there

andy.fingerhut16:10:37

The two primary techniques I know of for this are: documentation, and in some cases, clojure spec might be able to help

bartuka16:10:19

something like using a :pre hook to validate using a spec?

andy.fingerhut16:10:25

Is your goal to educate programmers who want to call the multimethod before they write code? To throw an exception if they call it incorrectly? Both?

bartuka16:10:45

I would prefer to educate the user before an exception happens

bartuka16:10:32

I think a good documentation on the defmulti would be better for now

seancorfield17:10:18

Your defmulti could dispatch on the presence of :fine/`:discount` if that makes more sense than adding a synthetic :type key. Hard to tell what's "best" based on just a short example like that though.

Mark Bailey17:10:19

Hello, all! I am doing my best to learn Clojure, as my buddy and I are starting a project together. I am writing code as I am learning and I'm running into a bit of tight spot. (defn match-remove [t-words i-words result]

(let [[t-word & t-rest] t-words]
    (if (blank? (str t-word))
      result
      (do
        (conj result (str (map (fn [xs]
                                 (for [x xs]
                                   (if (= x t-word)
                                     nil
                                     t-word))) i-words)))
        (match-remove t-rest i-words result)))))

Mark Bailey17:10:49

That did not work as expected. Sorry I am new to slack.

Mark Bailey17:10:57

(defn match-remove [t-words i-words result]
  (let [[t-word & t-rest] t-words]
    (if (blank? (str t-word))
      result
      (do
        (conj result (str (map (fn [xs]
                                 (for [x xs]
                                   (if (= x t-word)
                                     nil
                                     t-word))) i-words)))
        (match-remove t-rest i-words result)))))

Mark Bailey17:10:22

this is the function I am working on, currently it only produces a empty vector

seancorfield17:10:35

Inside the do you are evaluating two expressions and throwing away the first one.

seancorfield17:10:15

Clojure's conj has no side-effects: it produces a new value, that (in this case) is result with another string added -- but result is unchanged.

seancorfield17:10:28

This is Clojure's immutable data structures at work.

Mark Bailey17:10:43

Ah, yes. Immutability

Mark Bailey17:10:59

Then I misunderstood conj entirely

seancorfield17:10:59

What you perhaps want here is

(let [result' (conj result (,,,))]
  (match-remove t-rest i-words result'))

seancorfield17:10:43

If you come from a statement-based language, it's common to trip up on do and implicitly assume side-effects in a series of expressions.

Mark Bailey17:10:08

Thank you for the reassurance!

Mark Bailey17:10:34

So that fix you suggested produces a bunch of LazySeqs

Mark Bailey17:10:12

And I haven't gotten as far as those yet, in my own learning

seancorfield17:10:18

The other thing to be aware of in this case is that you're using recursion so you may run into a stack overflow if enough calls are made. You could use recur instead of match-remove in that tail call to avoid that.

Mark Bailey17:10:41

Okay! Will change immediately

jaihindhreddy17:10:26

Can you please describe what your function is supposed to do, and maybe provide a couple of example calls to your fn and what you expect it to return

jaihindhreddy17:10:39

So that people can help you better

Mark Bailey17:10:20

Sure! My function is supposed to take in two vectors of strings. Both produced by other functions operating on text files. The function in question is meant to find a word from one vec (i-words) and remove it from another (t-words)

jaihindhreddy17:10:34

What if i-words and t-words have multiple words in common? Are we supposed to remove all of them?

jaihindhreddy17:10:01

Also, if one word from i-words appears multiple times from t-words, are we supposed to remove all occurrences?

Mark Bailey17:10:12

The goal is to remove every occurrence of each word in i-words from t-words

jaihindhreddy17:10:29

Ah, in that case, all we need to do is

(defn match-remove [t-words i-words]
  (remove (set i-words) t-words))

Mark Bailey17:10:56

That's, uh, pretty nifty

jaihindhreddy17:10:09

Checkout the docstring of remove by using (doc remove) in your REPL.

jaihindhreddy17:10:27

What's happening there is that, remove expects the first argument to be a predicate, a fn that returns true or false when applied to a value. It then applies this predicate on each item in the second argument returning removing the ones for which the predicate returned true.

jaihindhreddy17:10:03

We're combining that and the fact that sets act as functions that return an item if it exists in the set and nil otherwise, to use the set directly as the predicate.

jaihindhreddy17:10:49

Meaning we can do stuff like (remove #{"a" "b"} ["a" "a" "b" "c" "b" "d"]) and get back ("c" "d")

seancorfield17:10:33

It's fairly common in Clojure to find that by thinking about collection-level operations, you can avoid loops and recursion altogether 🙂

Mark Bailey17:10:53

Dude Clojure feels so good

24
jaihindhreddy17:10:09

In fact, because this sort of thing is so concise, and doesn't represent any abstraction, we tend to use (remove (set i-words) t-words) inline instead of defining match-remove

seancorfield17:10:14

(but that can be quite a big step to take, depending on your background, and it doesn't hurt to learn the nuts and bolts of low-level stuff)

8
chepprey17:10:26

@mbbailey96 I only get to Cloj in my rare spare time, and almost every time i do, I hear Yoda in my head.... "you must unlearn what you have learned..." And those "That's, uh pretty nifty" moments keep on happening.

😁 4
Mark Bailey17:10:16

I cannot wait to see how beautiful this lang really is

chepprey17:10:18

(24 years of day-job enterprise Java, I have a $@#^-ton of unlearning to do)

Mark Bailey17:10:36

Oh my lord, I can only imagine!

seancorfield18:10:02

Even tho' I started with Java back in '97, I was lucky to have learned several FP languages back at university in the early 80's so Clojure's approach wasn't quite so foreign to me... But after nine years of Clojure (eight in production), going back to non-FP languages is really hard for me now!

chepprey18:10:04

I only had 4 weeks of exposure to CLisp in a "survey of programming languages" college course. I thought it was brutally logical and mind-bending, but it didn't resonate with me. C was all the rage. I was amazed by C, though my only other language experience was Commodore 64 Basic, and Pascal. 😆

seancorfield18:10:40

Sounds a bit like my journey. I came out of college and got a job doing C, then C++, having done a fair bit of BASIC and Pascal at college and at home 🙂

4
chepprey18:10:27

Doing Java starting in 96, and in about 5 years after enduring the horrors of JSP and Struts, I started to believe there's got to be a better way. My (big) company aquired this small company that had some kick-a$$ stuff implemented in Erlang, and I was like hello what's this nonsense? But it didn't take much investigation to have the epiphany of OMG I'm not alone, there really is a better way than Java OOP!!

chepprey18:10:25

I tinkered with Erlang in my spare time, gained love of FP's ideals, and a few years later I started reading about this Clojure language. ❤️

😍 4
walterl23:10:16

Let's say I have some function, let's call it consume-service which consumes some external service. Assuming that the application mode is stored in some global state, what is the Right Way™ to mock out that function based on current application mode? I could convert consume-service to a multi-method, but that seems overly invasive into the API. Currently I'm simply doing

(defn consume-service [& args]
  (if (dev?)
    (apply mock-consume args)
    (original-implementation-goes-here)))

hiredman23:10:03

I think it is pretty clear that the best(simplest?) way to do deal with switching out implementations for tests is if the thing you want to swap out is a parameter (a function argument) instead of a global (a def or defn)

hiredman23:10:14

Usually if I am interacting with an external system I like to think of build a kind of proxy object that represents that external system. Then anything that needs to communicate with the external system will get passed that object, and in tests can be passed a different object if needed. There are a few ways to constuct such an object, a pretty natural one is a deftype or defrecord, with supported operations defined as a protocol

hiredman23:10:28

there are ways to deal with it if you insist on globals (created with defn or def), but they rely on mutating the global for the duration of the test and mutating it back afterwards, which can behave badly if you ever want to do anything with multiple threads

walterl23:10:42

Thanks for that extensive answer, @hiredman! That seems like a great solution for unit testing, but the aim of this is actually for running the app locally, without external service deps (which requires setup and tokens and config and ...). So I'm also trying to keep the impact on the existing ("original") API to a minimum. The service in question is really just a GET request to an external API, and that's the only operation required. Do you think that a protocol is still appropriate?

hiredman23:10:20

you could just make the proxy (service?) object the function that does whatever you need and pass it around

hiredman23:10:51

the main thing is to reify the ability to interact with whatever as some value that gets passed around, so you can pass around a different value if you want

hiredman23:10:09

I am a big fan of component for this kind of thing, and for the same reasons very much dislike mount, but you can hand rolling that kind of thing just fine

walterl23:10:35

Thanks! I'll look into component 🙂