Fork me on GitHub
#beginners
<
2021-12-02
>
Andrew Byala01:12:52

I saw a reference in a thread recently about the Clojure style guide, and I saw this section: “https://guide.clojure.style/#pre-post-conditions” With the popularity of Spec, do people use these function conditionals anymore?

emccue02:12:56

i dont think people ever really used them

emccue02:12:10

i would also question the popularity of spec - from where i’m sitting it doesn’t seem like usage is really widespread. i should find stats to confirm or refute that if they exist

Eddie02:12:12

I use pre (and sometimes post) conditions with some regularity. Some uses reach production, but often I use them during development to run expensive checks that I wouldn't want in production just to help failures happen closer to the root cause.

Andrew Byala02:12:08

Interesting. Maybe I’ve watched too many Stuart Halloway YouTube videos. 😀 I’m just an amateur Clojurian, so it’s interesting to hear what the community actually uses. Out of curiosity, why don’t you use Spec?

seancorfield03:12:11

We've been very heavy users of Spec ever since it appeared (in the prerelease cycle for Clojure 1.9 -- May 2016).

seancorfield03:12:50

I don't really understand why some folks are resistant to it -- it's been incredibly useful for us.

seancorfield03:12:13

As for :pre/`:post`, we hardly ever use that in our code, so I agree with @U3JH98J4R about the popularity of those -- I very rarely see them in library code. Part of the issue is that they are "just" assertions so you get a runtime error rather than an exception.

seancorfield03:12:10

The question is: what do you need to do if your function is passed an illegal argument? Do you want to return useful information or throw an exception (that the caller might be able to do something with)? Or do you want it to "fail fast" (and maybe terminate the program)?

seancorfield03:12:30

Assertions -- including :pre/`:post` -- are the latter.

seancorfield03:12:57

I wrote about our use of Spec in mid-2019 https://corfield.org/blog/2019/09/13/using-spec/ -- TL;DR: we use it in production for a lot of in-function data validation but we also use it a lot in dev/test.

emccue04:12:28

Deriving code from specs

In this case we write specs for data structures, such as rows in database tables, and then we generate named CRUD operations and supporting functionality from the specs themselves -- using macros that take specs as input with some control parameters and expand into a number of defn and other forms. The important aspect of this is that the spec is the "system of record" for the data structure: it can be used for validation, test data generation, and as the source for the keys and "types" that shape the functions needed to operate on them.
What are these specs like?

seancorfield05:12:56

Not sure what you're asking there?

seancorfield05:12:19

I mean, they're Specs of hash maps, because that's what DB records are...

sova-soars-the-sora03:12:38

(internal screaming maximum volume)

Caused by: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,1]
Message: JAXP00010001: The parser has encountered more than "64000" entity expansions in this document; this is the limit imposed by the JDK.

😱 1
sova-soars-the-sora03:12:47

Apparently I can manually do java -Djdk.xml.entityExpansionLimit=0 but how can I also invoke clojure after setting that by the cmdline?

seancorfield03:12:44

clojure -J-Djdk...

sova-soars-the-sora03:12:27

@seancorfield so that gets me closer... but now I get Could not locate clojure/data/xml__init.class

sova-soars-the-sora03:12:11

Gotcha, something like this specifying the dependencies was necessary:

clojure -Sdeps "{:deps {org.clojure/data.xml {:mvn/version \"0.0.8\"}}}" -J-Djdk.xml.entityExpansionLimit=8256000 file.clj

sova-soars-the-sora03:12:22

Now I just have to make the code do the right thing 😅

seancorfield03:12:26

You're on macOS/Linux? You can do

clojure -Sdeps '{:deps {org.clojure/data.xml {:mvn/version "0.0.8"}}}' -J-Djdk.xml.entityExpansionLimit=8256000 file.clj
I find that easier than wrestling with escaped quotes. I also tend to be very lazy when doing one-offs from the command-line and use "RELEASE" instead of specific version if I don't care and just want a recent version.

1
sova-soars-the-sora03:12:39

So if I invoke (prn (parse input)) I get a lot of terminal output for XML parsing

sova-soars-the-sora03:12:56

But when I try to write to a file with (spit file (parse input)) I get a file with the text [email protected]

sova-soars-the-sora03:12:04

any pointers for what i'm missing?

seancorfield04:12:48

(split file (pr-str (parse input)))

seancorfield04:12:14

I think spit assumes you've given it a string and doesn't try to convert it to one. That clojure.data.xml.Element object has a .toString() method I expect but prn and pr-str will try to convert arguments to strings.

sova-soars-the-sora04:12:49

Cool. Thank you, it appears to have worked 😄

1
fappy06:12:54

why is

(seq x)
preferrable to
(not (empty? x))
at least according to the linter I have running?

seancorfield06:12:42

@fappy because empty? is defined as (not (seq coll)) so (not (empty? x)) is (not (not (seq x)))

thanks2 1
seancorfield06:12:15

seq is idiomatic in Clojure -- because of nil-punning -- so it's something you'll get used to.

seancorfield06:12:19

Also, if you find yourself wanting (if (not (empty? x)) y z) you can always write (if (empty? x) z y) or, of course, (if (seq x) y z) which is more idiomatic 🙂

Lycheese08:12:45

Is there a way to use pre/post conditions in protocol methods? The following code throws Unable to resolve symbol: % in this context :

(defprotocol Test
  (testing [_]))

(defrecord Tester [kw]
  Test
  (testing [_]
    {:post [(string? %)]}
    (str kw)))

🙌 1
Fabio Francisco08:12:29

It throws that error because '%' is a valid character only when inside an anonymous function starting with '#('

Lycheese09:12:21

% is supplied inside post conditions normally

Lycheese09:12:40

Try

(defn test [a b]
 {:post [(number? %)]}
 (str a b))

Lycheese09:12:29

Or this for sth not quite so useless:

(defn test [a b]
  {:post [(< (count %) 5)]}
  (str a b))

(test "a" "c")
(test "adsdfsfd" "d")

Lycheese08:12:07

And is there a particular reason why (keyword "a" "b c d") returns :a/b but {:a (keyword "a" "b c d")} returns {:a :a/b, c d} in cljs? Edit: Also (count {:a (keyword "a" "b c d")}) returns 1 but (count {:a :a/b 'c 'd}) returns 2 (maybe because the symbols in the first sexp don't resolve to anything?)

popeye10:12:19

I am started with my first clojurescript project ans I am getting below error? can anyone help me please

Stuart10:12:47

What does your namespace declaration look in? At the top of the file it can't compile, will look like

(ns foo.bar
  )

popeye10:12:48

(ns raw-dom.core)

(def cnt-holder (.getElementById js/document "clicks"))
(def reset-btn (.getElementById js/document "reset-btn"))
(def cnt (atom 0))


(defn inc-clicks! []
  (set! (.-innerHTML cnt-holder) (swap! cnt inc)))

(defn reset-clicks! []
  (set! (.-innerHTML cnt-holder) (reset! cnt -1)))

(set! (.-onclick js/document) inc-clicks!)
(set! (.-onclick reset-btn) reset-clicks!)

popeye10:12:55

is my cljs file

Stuart10:12:17

The error looks to be in base64_vlq.clj, which won't be one of your files Have you seen this? https://github.com/nrepl/piggieback/issues/103

popeye10:12:28

oh!, i upgraded everything to latest version and it start giving result

popeye10:12:13

btw, there os nothing to load namespace for js here ? (.getElementById js/document "reset-btn") , doe it load by default ?

Łukasz Trojanowski10:12:17

Hi. I'm trying to learn clojure by doing the advent of code lessons and I'm a bit stuck on syntax. `(->> (slurp "../day1/data1.txt") (clojure.string/split-lines) (map read-string) (partition 3 1) (map (reduce +)) ;; can't get this line to work (partition 2 1) (filter (fn [x] (< (first x) (second x)))) (count))` The code reads a file, converts it into a list of numbers, groups it into a list of lists and then I'm stuck trying to sum the inner lists with a wrong number of args exception. Could anyone help?

Stuart10:12:20

Try

(map #(reduce + %))
Insted.

Stuart10:12:40

map takes a function, so you need to either use

(map (fn [x] ... ))
Or the shorthand version
(map #(...))
Just doing
(map (reduce +))
It will treat (reduce +) just as an argument and try to evaluate it, hence an arrity error.

Łukasz Trojanowski10:12:18

Thanks a lot that was it

walterl12:12:16

@seancorfield Very useful https://clojurians.slack.com/archives/C053AK3F9/p1638417446148700?thread_ts=1638414884.145500&amp;cid=C053AK3F9 about "RELEASE" as :mvn/version. Is it a special deps value, a Maven shortcut, or a common convention for Clojure/Java libs?

alexmiller13:12:15

It's a special virtual version understood by Maven but there are several caveats

alexmiller13:12:29

First, it's only as good as the Maven repository metadata. With Maven central and Clojars those are updated by batch processes so lag slightly but are generally reliable. But s3 repos typically do not have this metadata and will tell you wrong answers

alexmiller13:12:31

Second, this will violate some assumptions of the Clojure CLI classpath cache, namely that if the deps.edn doesn't change, the classpath doesn't change. So you need to -Sforce to notice new things. But for a tool install or a one off, this probably doesn't matter

walterl13:12:16

Great notes, thanks @alexmiller

alexmiller13:12:48

I think that's just for plugins

👍 1
alexmiller13:12:45

Should still work in this context (you're using Maven 3 libs here)

Benjamin13:12:04

(defn fo [a & {:as opts}]
  [a opts])

;; What is the best way prior to clojure 1.11-alpha something
;; to pass opts?

;; I made use of this: in my code base a lot

(fo "a" {:b :c})

;; and now I want to downgrade to clojure 1.10

Benjamin14:12:38

(apply fo (into ["a"] cat m )) is the best I can come up with

Colin P. Hill14:12:14

Why did transducers get implemented as single multi-arity functions, when those arities are each doing a fundamentally different thing? My first thought in that context would have been to use a protocol, but I'm guessing there's a good reason not to do it that way that I'm not seeing.

alexmiller14:12:50

semantically, they are "like the seq version, but without the seq"

alexmiller14:12:48

oh, you're asking about the impl spi side

alexmiller14:12:13

it could have been a protocol, but that's a set of functions. as a single function, it's much easier to pass around the package.

Colin P. Hill14:12:25

I guess it would have been very OO – the transducer itself would wind up not representing any data, and would be a protocol function argument that does essentially nothing but determine the dispatch. I'd just never seen multiple arities used before to represent totally distinct operations. It "feels wrong", but that might be a malformed intuition on my part (and I'm assuming that it probably is, since y'all have much more experience with this stuff than I do).

Colin P. Hill14:12:55

Maybe my hesitation is that it seems strictly coincidental that the three operations take different numbers of arguments. I don't think I would have ever thought to rely on arity when, in principle (even if it's strictly hypothetical) two of the operations could have taken the same number of arguments

Ben Sless16:12:41

I happened to read some papers on streams a few days ago and it seemed to me that transducers and operators fusion map almost 1-1, am I correct in that observation?

alexmiller16:12:46

they have similar goals

Ben Sless17:12:02

More than the goals, I even went and read the implementations of the Rx streams operator factories (🤢 ), when you cut down all the java hand waving, it's the same

Ben Sless17:12:25

It's amazing how what Clojure does in 10 lines Rx needs 1000

alexmiller14:12:08

and ruby, and python. forgot about all those

true.neutral14:12:14

Love - being non-sarcastic - how, for Python, the last commit was 7 years ago, there are 200 stars on the repo and just one issue opened. Meaning, well, it just works.

Shmarobes15:12:30

Hello. I wrote a function that takes a vector of dimensions and returns a corresponding matrix (a nested vector) with random elements (0 or 1). Although my solution works, I'm really interested in alternative ones, which may be more idiomatic. My code:

(defn random-grid
  [[d & ds]]
  (vec
   (repeatedly
    d
    (if (seq ds)
      #(random-grid ds)
      #(rand-int 2)))))

Shmarobes15:12:44

I feel like there's something that can be done using partition, but not sure how to do this for nested vectors.

pithyless15:12:38

Maybe using postwalk?

(require '[clojure.walk :as walk])

  (walk/postwalk
    (fn [x]
      (if (coll? x)
        x
        (vec (repeatedly x #(rand-int 2)))))
    [2 [1 [3]]
     2 [1 [4]]])
;;
;; [[1 1] [[1] [[1 1 0]]]
;;  [0 1] [[0] [[1 1 0 0]]]]

Shmarobes15:12:53

Hmm, I think I wasn't clear enough about what I'm trying to achieve. The dimensions is not a nested vector. If it's [3 4] than I would like to get a 3x4 matrix, e.g.: [[0 1 1 1] [1 0 1 1] [0 0 0 0]]

tschady15:12:20

(let [dims [2 3 4]]
  (first
   (reduce (fn [m d] (partition d m))
           (repeatedly #(rand-int 2))
           dims)))
;; =>
(((0 0) (1 0) (0 1))
 ((0 0) (1 1) (0 1))
 ((0 0) (0 0) (1 0))
 ((1 0) (1 1) (1 1)))

👍 1
Shmarobes15:12:42

@U1Z392WMQ Yes, this works, but the result is a sequence, of sequences, not a nested vector.

tschady15:12:18

just turn the lists into vectors

Shmarobes15:12:22

Any advice on how to achieve this?

Shmarobes16:12:47

Oh, postwalk

Shmarobes16:12:48

I got it, but the solution now looks more complex than my initial one. Thanks for an alternative one though.

tschady16:12:41

you could generate a random binary number with the right number of bits , then partition. takes infinity out of it, so vectors are fine.

Shmarobes16:12:37

Got it. But somehow I feel like there should be a more straightforward way... Are nested vectors even idiomatic in clojure? My goal is to have something that I could index like (grid [0 1 3]) or (get-in grid [3 5 2]). Should I use a map, maybe?

tschady16:12:37

you can just treat the bit array as a flat array and index multidimensionally with your dims

tschady16:12:33

if you’re doing complex math, core.matrix might be useful

👍 1
Shmarobes16:12:11

No complex math, but I'll look into core.matrix to see if it fits my use case. Thanks a lot for your replies.

tschady17:12:52

core.matrix.random has what you need.

user=> (sample-rand-int [3 3 3] 2)
[[[1 0 1] [0 0 0] [0 0 1]] [[0 1 0] [1 1 0] [0 1 1]] [[0 0 0] [0 1 0] [1 0 1]]]

NoahTheDuke15:12:25

this is somewhere between a clojure and java question, so my apologies if this is the wrong channel. I’m trying to move off clj-time to cljc.java-time, and I’m not sure if I should be using Instant or LocalDateTime as my base unit. I’m not doing anything super fancy: i’m marking chat messages with timestamps, I’m querying mongodb with dates 3 months ago, etc.

NoahTheDuke15:12:51

I should say that I read this excellent SO answer (https://stackoverflow.com/questions/32437550/whats-the-difference-between-instant-and-localdatetime) about the difference between the various date types, I’m just unsure of exactly when is best to use them.

alexmiller15:12:57

Instants are computer time (secs since the epoch), LocalDateTime is human time (Y/M/D stuff). So, different semantic points of view (latter maps to former based on time zones, laws, leap seconds etc).

alexmiller15:12:01

I think I'd say people want people time on their messages (but in storage you might want instants)

👍 1
dpsutton15:12:10

> Where possible, applications should use LocalDate, LocalTime and LocalDateTime to better model the domain. For example, a birthday should be stored in a code LocalDate. Bear in mind that any use of a time-zone, such as ‘Europe/Paris’, adds considerable complexity to a calculation. Many applications can be written only using LocalDate, LocalTime and Instant, with the time-zone added at the user interface (UI) layer. • https://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html

👍 1
dpsutton15:12:41

(i’m not adding my own advice here but just directing to the quite extensive documentation they have for the package)

Muhammad Hamza Chippa15:12:59

is there any way to generate the weekly dates from 2017-2021. For example today is thursday how to generate dates of Thursdays from 2017-2021 ?

Martin Půda16:12:16

(import java.time.LocalDate)

(->> (LocalDate/parse "2017-01-05")
     (iterate #(.plusDays % 7))
     (take-while #(> 2022 (.getYear %))))

ghadi16:12:26

(import java.time.LocalDate)
(import java.time.DayOfWeek)

(let [lo (LocalDate/of 2017 1 1)
      hi (LocalDate/of 2022 1 1)]
  (->> (iterate #(.plusDays ^LocalDate % 1) lo)
       (take-while #(.isBefore ^LocalDate % hi))
       (filter (comp #{DayOfWeek/THURSDAY} #(.getDayOfWeek ^LocalDate %)))))

pjones15:12:12

I have a general question about clojure and navigating the clojure environment. Google has generally returned github results for clojure package searches. Some of these repo's haven't had a commit for 4-7 years. So my question is two fold. How stable/reliable are packages like these (this may be a how long is a piece of string question...though just building intuition here)? Is there a general index for up-to-date packages?

Sam Ritchie16:12:17

The language is very stable so many of these will be great. Same with Java packages that haven't seen updates in a while

Sam Ritchie16:12:24

If it does what you need, go ahead!

pjones16:12:24

Awesome! Coming from R/Python where a lack of commits can be a red-flag for some packages, this is a great comfort. Appreciate the response.

👍 3
walterl16:12:05

Coming from Python, I was also astounded by this notion of packages being (essentially) completed, but it really is the case. Many of the most used packages see very few commits, often "non-code" changes like fixing typos in docs.

walterl16:12:30

I rather look at open issues and PRs as an indicator of the state of a Clojure package: high numbers could mean that there are significant issues with it that are not being addressed.

pjones17:12:29

That makes sense. I wonder if the immutable nature inherent in clojure results in more robust packages, thereby removing the need for constant debugging and commits.

Andrew Byala17:12:29

Is there a name for a function that creates a function, like a builder or factory? I'm thinking of something that follows this pattern:

(defn make-something []
  (fn [request] {:status :ok}))

Ben Sless17:12:46

Any function in Clojure will happily do this for you, obvious question is why / what's the use case?

Andrew Byala18:12:36

Thanks, @martin. I didn’t know if there were different names for higher-order functions that take in function arguments, vs functions that return functions. I guess not! @UK0810AQ2 - I did this in my Advent Of Code solution, but I also see them all the time in Reitit code, for instance.

Ben Sless18:12:15

@U01HHBJ56J1 take a look at malli, too, it's full of them. There's really no difference between a factory and a higher order function in this sense. Moreso, with Clojure functions just being class instances, I find it amusing. You can call it a factory function and no one will look at your weird

👍 1
Antonio Bibiano18:12:24

I have a long list of calculation that take quite some time but I can simply map over the inputs and collect the result

Antonio Bibiano18:12:17

sometimes though one of these calculation might fail but i still want to keep the other results

seancorfield18:12:29

(map #(try (calculation %) (catch Throwable t t)) data) would give you results where the calculation succeeds and exceptions where it failed.

seancorfield18:12:22

Then you can either (remove #(instance? Throwable %) results) or report on the failures or whatever you wanted.

Antonio Bibiano18:12:14

ah yeah i should have thought of that before I started the calculation again 😄

Antonio Bibiano18:12:51

what if instead I wanted to "stop" the map on the first exception? would you use something like take-while?

Mno19:12:41

I would've used an atom to store intermediate results like: (run! #(swap! *storage-atom conj (calculation %)) data) or something like it. (the atom should probably be a vector as an initial value: (def *storage-atom (atom [])))

noisesmith19:12:20

I don't think an atom helps here at all

noisesmith19:12:32

with reduce you can use reduced to stop early

noisesmith19:12:22

(reduce (fn [results input] (try ... (catch Exception _ (reduced results)))) [] inputs)

noisesmith19:12:01

you can do similar with loop but I think reduce makes it easier to understand

noisesmith19:12:14

(defn parse
  [inputs]
  (reduce (fn [results input]
            (try (conj results (Long/parseLong input))
                 (catch Exception _
                   (reduced results))))
          []
          inputs))
(ins)user=> (parse ["1" "2" "3" 4 "5"])
[1 2 3]

Antonio Bibiano19:12:08

yeah reads very nicely

Antonio Bibiano19:12:47

thanks for the explanation

Antonio Bibiano19:12:34

I also was thinking some kind of mutable thing like @hobosarefriends mentioned

Mno19:12:17

If you insert a try-catch inside of the mapping function you can even keep going through all of them if one fails it can return nil or something recognizable to filter them out.

noisesmith19:12:26

that's true - keep is like map except it ignores nil

noisesmith19:12:00

and map is simpler than reduce - I only suggested reduce because it looked like early exit was wanted

Antonio Bibiano19:12:18

yeah that was exactly what I meant in my second question

Antonio Bibiano19:12:13

but I think what @hobosarefriendssuggested was like let it explode and all the previous calculation will still be in the atom

Mno19:12:20

(let [*storage-atom (atom [])]
   (run! (fn [x] (try 
                    (swap! *storage-atom conj (calculation x))
                    (catch Exception _ nil)))
     data)
@*storage-atom)
but yeah the other one would exit early as well, because it throws an error.. I wouldn't call it graceful, but it definitely works 😅

alexmiller19:12:11

don't do that :)

dpsutton19:12:14

(`run!` is implemented as reduce that ignores the accumulator. so it makes a lot of sense to use reduce and not ignore the accumulator rather than introduce another accumulator in an atom)

noisesmith19:12:09

also if your goal is not to stop at an exception, just use

(keep (fn [x] (try (calculation x) (catch Exception _))) input)

noisesmith19:12:24

the returning nil is implicit, and keep skips the nils

Mno19:12:27

I recommend taking everyone else's advice over mine, they have good reasons for recommending against it. (and much more experience) My solution is merely a quirk, because I have an easier time reasoning about it with atoms.

noisesmith19:12:59

atoms let you solve things the way a classic algorithms book would :D

Antonio Bibiano19:12:19

yeah i see where you come from

noisesmith19:12:54

but I think most here would agree it's worth the extra effort to learn the immutable constructs (and learn how they substitute for the classic mutable patterns)

❤️ 1
Antonio Bibiano19:12:16

for me force of habit makes me reach for mutable stuff

Antonio Bibiano19:12:04

but when stuff is simple I hear a voice "Clojure has probably a better way to do this.."

sova-soars-the-sora20:12:27

Hi, is there a way to partially load a file? For example, I converted an 8.8MB XML file to a 180 MB edn file 😅 And I'd like to load just some portion of it for examination. 1. Possible to do this? 2. Possible to do this without breaking things?

tschady20:12:11

checkout iota/vec from https://github.com/thebusby/iota for random access.

noisesmith20:12:32

I think the harder part is reading parts of an edn file as clojure data structures without loading the whole thing

noisesmith20:12:09

(you didn't explicitly say that was needed but it seems like that would be part of the task)

sova-soars-the-sora04:12:49

It's just too big to load into a text editor without bringing my machine to a crawl, I don't know how to start funking around with it

noisesmith06:12:15

there are editors that don't load the entire file into RAM - eg. sam (which is very old and weird) - I wonder if there are any newer ones

Andy Nortrup20:12:22

So I'm trying to use :post assertions to be sure that a function is returning valid values. It's failing but I'm trying to figure out if I can see the value that failed. I'm new to spec and I'm not sure if I have a problem with my logic or my spec definition.

(defn flow-efficiency
  "Calculate flow efficiency for a single issue"
  [issue]
  {:post [(s/valid? :flow/flow-efficiency %)]}
  (let [active-time (active-time issue)
        flow-time (flow-time issue)]
    (if (= 0 flow-time)
      0
      (float (/ active-time flow-time)))))

Andy Nortrup20:12:29

I get the following error:

; error: java.lang.AssertionError: Assert failed: (s/valid? :flow/flow-efficiency %) (core.clj)
Which tells me where it failed, but doesn't really help me understand what happened.

randomm char04:12:39

What does the s/def :flow/flow-efficiency look like?

sheluchin20:12:22

What's the cleanest way to express something like:

(foo 1 2 3 4) 
(bar 1 2 3 4) 
Is there something more idiomatic than just keeping it as is or using:
(map #(% 1 2 3 4) [foo bar])

Jon Boone20:12:48

Is there a larger context around the calls to foo and bar that you can share?

sheluchin20:12:43

Not really. I just see myself apply the same arguments to a set of functions often enough that I wonder if there's a better expression for it... kinda like a reverse partial or something?

Jon Boone20:12:41

Are those function calls all happening within the scope of another function you have defined?

sheluchin20:12:22

Yes, the calls are usually in a shared scope.

Jon Boone20:12:48

So, why not capture the relevant parameters in a list bound to a symbol (say, baz) and then just apply the functions to that parameter list?

Jon Boone20:12:52

That way it's clear you expect the function applications to happen on identical parameter values…

sheluchin20:12:39

(let [args [1 2 3 4]] 
  (apply foo args)          
  (apply bar args))         
It's okay...

sheluchin20:12:06

No specific construct to shorten it even more?

Jon Boone20:12:55

Why would you want to? Would that somehow improve the readability?

Jon Boone20:12:31

Also, you can just use: (foo baz) (bar baz)

Jon Boone20:12:47

Or args, in your example

sheluchin20:12:20

Don't you have to apply the args so it gets passed in as separate items instead of as a collection?

Jon Boone20:12:43

I think it is unnecessary in Clojure…

Eddie20:12:43

It depends if the function is defined to take a collection or individual args.

Cora (she/her)20:12:43

apply, that is, you need it

Cora (she/her)20:12:02

the earlier examples @alex.sheluchin gave has positional args instead of a collection

👍 1
Eddie20:12:47

Personally, I like the map example.

Cora (she/her)21:12:19

if it's just two, and I really wanted to make sure the args stay in sync, I'd do let with apply

Cora (she/her)21:12:42

but if it's an unknown number of functions to call with the args, or more than two, the map is good

☝️ 1
sheluchin21:12:43

I'm not sure https://clojurians.slack.com/archives/C053AK3F9/p1638478479270500 is better than just repeating the args in two calls. map is okay, but I still feel like there's some function I'm not aware of made just for this 🙂

Eddie21:12:12

Are some or all of the functions side effects?

Cora (she/her)21:12:31

it's better than repeating the args if you really want to make sure the args stay in sync across calls

sheluchin21:12:58

@erp12 my question is not specific to a particular use case right now, so if there are different answers here depending on side-effectfulness, I'd like to understand both sides.

Cora (she/her)21:12:33

you're not doing anything with the result of the first call, so the assumption is you're doing it for the side effects

Cora (she/her)21:12:49

side effects can include just printing things out

Eddie21:12:01

Yup, what @corasaurus-hex said. Also I wanted to raise the usual warning about lazy evaluation using map and similar iteration functions.

sheluchin21:12:14

True, my simplified example does imply side-effects. Good point.

Eddie21:12:53

For example, if your functions are something like: log-to-file and println , you should use doseq instead of map so that you can be sure the side effects actually happen.

Eddie21:12:11

or let and apply.

sheluchin21:12:08

I think I understand that common point of caution around laziness well enough. I usually just define the iteration with stuff with for and consume it with reduce or doseq if I need side-effects. I haven't yet familiarized myself with dorun, doall, run!, etc.. I thought there might be something hiding there that would be the answer for my question.

Antonio Bibiano21:12:22

you mentioned a reverse partial

Antonio Bibiano21:12:48

maybe something like

(defn reverse-partial [& args]
  (fn [f]
    (apply f args)))

(def rp (reverse-partial 1 2 3 4))

(rp -)
(rp +)

sheluchin21:12:24

The framing of my question was bad. How about this:

(let [x (foo 1 2 3 4)  
      y (bar 1 2 3 4)] 
  ...)                 

sheluchin21:12:41

@antbbn thanks. I was thinking something along those lines too, but I don't feel like it simplifies anything here unless it's a common enough pattern in the codebase to merit a helper function.

👍 1
Martin Půda21:12:34

https://clojuredocs.org/clojure.core/juxt:

(let [[x y] ((juxt min max) 1 2 3 4)]
  (println x y))
1 4
=> nil

👍 4
ghadi21:12:35

user=> (map (juxt min max) [0 5 10] [1 6 11] [2 -2 5])
([0 2] [-2 6] [5 11])

👍 3
ghadi21:12:43

jinx on juxt @mapuda

😄 1
sheluchin21:12:35

I think I like that one the most. I know about juxt but keep forgetting about it where it can be useful.

sheluchin21:12:04

Thanks for the input everyone.