Fork me on GitHub
#clojure
<
2022-04-04
>
didibus06:04:18

Is there a way to have a function that takes positional or named arguments? I'm thinking like:

(defn foo
  ([& {:keys [a b]}] (foo a b))
  ([a b] (+ a b)))

(foo 1 2)
;;=> 3

(foo :b 2 :a 1)
;;=> 3

(foo {:a 1 :b 2)
;;=> 3

p-himik06:04:34

Unless you drop &, there's no reasonable way to distinguish between 1 2 and :b 2.

didibus06:04:29

Hum... fair, though in my case both are mandatory, I guess if there was a way to tell Clojure about that it could be distinguished

didibus06:04:13

Similarly, Clojure could know that :b is just my label, and so that will always be hard-coded at call-site, so it could also know from that

p-himik07:04:13

You could employ the JS technique to count the arguments. :D But I myself would rather just not use & at all.

😆 1
Joshua Suskalo14:04:12

So part of what's going on here is that when you use the & {:keys [a b]} syntax there's nothing special about the keys :a and :b from Clojure's perspective. It just wants to collect a map from arbitrary keys to arbitrary values, and there are no "required" keys to make a map. All that's happening is that the arguments are being poured into a map and then you're using a shorthand syntax to bind the symbols a and b to the matching keywords from the map.

Joshua Suskalo14:04:57

And while potentially you could make the clojure compiler care about these, it would be a breaking change unless a brand new "type" of defn were introduced, which seems unlikely at this point.

didibus16:04:28

Would it be breaking if it captured :a and :b ?

Joshua Suskalo16:04:57

Well it would be breaking in this case if the compiler started to treat keys specified as required. Also I want you to keep in mind that you couldn't treat them as interchangeable in many cases, because Clojure focuses a lot on higher order functions, and you don't know if it will be called with named arguments or positional ones until it's actually called.

didibus18:04:09

Ah ya, that one would. But I wasn't thinking it changed to always be required. You'd tag them as so explicitly. I think the other approach is better anyways. The compiler should just be aware of the keys, and if it sees them used in a call it can figure out where to dispatch. It does mean the compiler has to be fancier to track that, but I think that's non breaking and also would speed up dispatch since it can be determined at compile time.

didibus18:04:22

Basically, if you see any call to that method that passed a static keyword as argument at the call site which is one of those keys, you'd know to call the positional one.

Joshua Suskalo18:04:55

I mean you can provide this yourself with Clojure's :inline metadata.

Joshua Suskalo18:04:05

Something like this, for example:

Joshua Suskalo18:04:49

could pretty easily write a macro to generate this code.

Joshua Suskalo18:04:43

Could even extend it to give default values to the different arguments in case a named argument is left off, etc.

Joshua Suskalo18:04:22

The point is though that these sorts of changes are definitely a different interface from defn, and should be treated that way, because there's parts of this that just breaks existing usage. This doesn't just make legal some things that weren't legal before.

didibus07:04:15

Interesting use of inline, I haven't used inlining yet for anything so I might need more time to parse it all. Frankly, I trust you that it possibly is breaking, I've only put limited thoughts into it. My current thoughts were that you could do something like:

(defn foo
  ([a b] (+ a b))
  ([& {:keys [a b]}] (foo a b)))
And defn could capture that in the second case :a and :b could be used used for named arguments, in the way 1.11 supports. So if I call:
(foo 1 2)
Clojure knows that can't be a call to the named one, because there's no argument equal to :a or :b. Where as if I call:
(foo :a 1)
It knows to call the named one, because it sees that one of the argument is :a

Joshua Suskalo14:04:00

Yeah, I get what you intend, and you can have that behavior already with basically what I just did. You'd just need to write a macro that expands to it.

didibus06:04:13

Maybe it's due to Clojure 1.11, before I'd just do:

(defn foo
  ([{:keys [a b]}] (foo a b))
  ([a b] (+ a b)))

(foo 1 2)
;;=> 3
(foo {:a 1 :b 2})
;;=> 3
But now with 1.11, I also want the cool new named arg style of calling, seems I can't get the best of both.

Eugen07:04:26

does anyone know if Clojure works with Quarkus https://quarkus.io/ ? I would like to work on Keycloak and implement some functionality in Clojure. I will probably end up trying that but was curios if there is any prior work. Also if you are interested, please let me know and I will share the progress.

Eugen08:04:46

no I haven't. Thanks

didibus10:04:24

Isn't this a bit strange?

(- 300.34 200)
;;=> 100.33999999999997

p-himik10:04:17

No, that's how floating point arithmetic works.

didibus10:04:33

Hum, I'd have to read up on the details around that again

agile_geek10:04:01

Strictly speaking it's how BINARY represented floating-point arithmetic works

agile_geek10:04:26

If only we had decimal computers????

😂 1
didibus10:04:45

I guess I'm confused though, if 100.34 isn't representable as a decimal of limited precision, why does it print it when I type 100.34 ?

didibus10:04:13

Also why this works:

(+ 100 0.34)
;;=> 100.34

p-himik10:04:56

To add to the motivating confusion:

=> 0.1
0.1
=> (- 0.3 0.2)
0.09999999999999998

😆 1
agile_geek10:04:47

That last one is explained in the second link I shared. I.e. for displaying literal values that can't be represented properly as binary there is a fudge based on the bits available in the representation to display the value AS IF we could represent it. I suspect the same is true of the 0.34 example

👍 1
agile_geek10:04:06

It you want to carry out decimal calculations without losing precision AND you can cope with a fixed decimal point representation (i.e. a precision of n decimal places) you can use BigDecimal

agile_geek10:04:24

The literal representation in Clojure is an M on the end of the literal value. E.g.

(- 300.34M 200)
=> 100.34M

agile_geek10:04:27

P.S. I could have put an M on the end of both the 100.34 and the 200 but I was lazy... Clojure converted 200 (a long) into BigDecimal for me

agile_geek11:04:09

user> (type (- 300.34 200))
java.lang.Double
user> (type (- 300.34M 200))
java.math.BigDecimal

agile_geek11:04:47

user> (.scale (- 300.34M 200))
2

agile_geek11:04:24

user> (.precision (- 300.34M 200))
5

agile_geek11:04:22

I.e. this calculation has returned a BigDecimal with a scale (decimal places) of 2 and a precision of 5 (3 digits to left of decimal place and 2 after)

agile_geek11:04:25

BTW this is the reason financial institutions often represent monetary values as integer values and assume the 2 decimal places for the pennies/cents or whatever the currency subdivision is for a decimal currency

today-i-learned 1
agile_geek11:04:53

Not looking forward to having to divide by 240 when the UK Tory government decides to bring back pre decimal currency! 😂 😉

😂 2
simongray12:04:31

@U0K064KQV Just round to two decimals and pocket the difference! With all those bits you will be rich in no time.

😆 1
agile_geek12:04:31

That was done in the late 60's by a COBOL programmer in a bank

1
agile_geek12:04:07

Strictly speaking it was rounding the fractions of a penny from interest payments.

simongray13:04:46

Interesting factoid! It’s also a major plot device of Office Space 🙂

☝️ 2
Alex Miller (Clojure team)13:04:49

And Superman 3

😆 1
😅 1
andy.fingerhut22:04:32

Also mentioned in one of a Science Fiction book series on a character calling himself the Stainless Steel Rat by Harry Harrison.

andy.fingerhut22:04:52

I have heard that most/all countries have either laws, and/or strict accounting rules, about such things.

olaf10:04:28

Using rewrite-clj I’m trying to append a list to the root node, without success

(require '[rewrite-clj.zip :as z])

(let [x "(defn foo [] 42)"
      zloc (z/of-string x)]
    (-> zloc
        (z/insert-right '(foo))
        z/sexpr))
desired output (defn foo [] 42) (foo). Any suggestion?

Noah Bogart11:04:48

You’ll get answers here and crossposting is frowned upon, but there's also #rewrite-clj

👍 1
olaf11:04:00

Solved thanks to @borkdude

🎉 1
sheluchin12:04:59

In one of his talks Rich goes on about how thinking about DB records as different types of entities and arriving at an ORM-like thing as a result is the wrong approach. I can't seem to find it in the transcripts. Does anyone recall where it's from?

potetm13:04:59

search for parochialism

sheluchin19:04:15

@U07S8JGF7 yes, I think that's the one. Thanks!

icemanmelting14:04:15

Guys, i am kinda new here, but I have been testing/using datomic recently, and I have noticed something, whenever I start the console, to get the ui to be able to query stuff in browser, the transactor just quits on me after some time. Has this ever happened to you? What am I doing wrong?

Alex Miller (Clojure team)14:04:09

you might want to post this over in #datomic and include some more info on version etc

icemanmelting14:04:34

I can’t seem to find #datomic, that’s why i posted it here

Joshua Suskalo14:04:25

@U9BQ06G6T if you click on the highlighted #datomic link in the above message it should take you directly there.

icemanmelting14:04:16

just did that 😄

👍 1
Stel Abrego17:04:52

Does anyone know the history of threading macros in LISP-style languages? I was surprised to find them missing in Scheme's standard library, and from a quick google search it seems like Clojure was the first language to popularize them. But how is that possible? They're so useful, surely someone was using them before Clojure. Racket has a threading module but it was written in 2015.

mpenet18:04:07

ocaml has a somewhat similar syntax/operator with |> if you squint a little. But it is not really a lisp

👍 1
Kelvin18:04:48

To be clear OCaml |> is equivalent to Clojure ->>

Stel Abrego18:04:53

Ok cool I'll take a look at that. I've never messed around with OCaml

Nundrum18:04:06

It reminds me a lot of various constructs in R, particularly in the dplyr package.

👍 1
Noah Bogart18:04:17

They make less sense when the core functions don't have consistent api

🤯 1
Stel Abrego18:04:18

That makes sense! Ty

Stel Abrego21:04:43

Looking through babashka's codebase I learned that spit can accept arbitrary data because it passes the input to str before passing it to the java.io.Writer . That got me wishing for spec definitions for clojure.core functions because the spit docstring isn't super explicit about this whereas a spec annotation from doc would make it perfectly clear. That's probably not going to happen until spec moves past alpha right? It would definitely be an awesome addition to the REPL experience.

rayat04:04:42

Speaking of docstrings - the lack of a convention/standard/consensus on a (opt-in obviously) docstring format intrigues/confuses me. At least in my codebase, I find docstrings and specs so frustratingly rare. Even when docstrings are written, they're often in an unstructured, incomplete or overly prose manner. I often have to read the function body (or some entire chain thereof sometimes 😿). Obviously a conventional format(s) won't stop incomplete docstrings, but they at least open the door for IDEs to offer conveniences like autocomplete block comments with parameters/return prepared, convenient hover and spelling/autocomplete help for individual parameters. Further, the mere ubiquity or presence of such structured docstrings I'd argue can coax people into adding a couple of words or a line or two about parameters or returns, when they'd previously just write a couple words about the function overall. Im thinking about javadoc and jsdoc for inspo. I might also be totally wrong and just didn't do my research about clj equivalent endeavors, but I haven't seen anything to this effect so far 😕

borkdude21:04:56

@stelabrego Check out https://github.com/borkdude/speculative - not sure if it has a spec for spit but it does have a lot

borkdude21:04:50

But the spec would not be so interesting, as the spittable argument can be anything, so it'd be any? probably

Stel Abrego21:04:49

@borkdude Oh sick that looks awesome! Thanks Michiel. Yeah it definitely wouldn't be that interesting because don't all objects have a .toString method? The docstring could just spell out that any object is spittable but writing/reading requirements in prose is so tedious..

Stel Abrego21:04:36

There's definitely much more useful functions to spec in clojure.core

borkdude21:04:12

See here for a fun application based on those specs: https://borkdude.github.io/re-find.web/

🤯 2
Stel Abrego21:04:13

Wha..?? This is amazing!! I gotta see how you did this 😄

borkdude22:04:04

I have a talk about this: https://www.youtube.com/watch?v=Ygrml6tyrq0 It was actually my first Clojure talk ever, I believe

borkdude22:04:09

well, on a conference

borkdude22:04:19

it's only 15 minutes

Stel Abrego22:04:24

Just watched, that was great. Such a creative project. Self-hosted CLJS is soo cool. I really want to go to Clojure conference IRL sometime soon hopefully.

kwladyka23:04:54

Does Clojure have a function like below? I can’t remember such one.

(if (string? open_time)
  (Long/parseLong open_time)
  open_time)
(foo 1) => 1 (foo "1") => 1

p-himik23:04:42

No, but you can use cond-> to shorten it a bit.

Alex Miller (Clojure team)23:04:04

And 1.11 now has parse-long

👍 1
emccue01:04:14

(cond-> open_time
  string? parse-long)
To tie that together

ghadi02:04:33

@U3JH98J4R almost (cond-> open_time (string? open_time) parse-long)

didibus02:04:19

Another instance of the cond that threads in the condition as well. Sean Corfield I believe has a open source lib with such cond.

emccue03:04:15

we have a macro in the codebase that does that^ my brain just short circuited

oddsor08:04:15

A fif-function is a cute addition to any codebase!

(defn fif [pred fun]
  #(if (pred %)
     (fun %)
     %))
(def long-if-string (fif string? parse-long))
(long-if-string "1") ; => 1
(long-if-string :1) ; => :1
But a macro like you mentioned sounds neat as well, @U3JH98J4R :star-struck: