clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
Ben Sless 2021-05-02T05:18:38.397100Z

I'll try this test again with the JIT off

Jim Newton 2021-05-02T09:34:16.399900Z

what is the best way to understand the different kinds of function definition argument lists and the corresponding call-site argument lists? Simple function definitions with a fixed number of named arguments are easy. But in Clojure, function application also allows exotic argument lists involving optional arguments and key arguments.

Jim Newton 2021-05-02T09:35:05.401300Z

basically if I want to implement a macro which extends the semantics of fn what are ALL the cases I need to cover as far as argument binding and syntax of call-sites ?

borkdude 2021-05-02T09:35:17.401600Z

@jimka.issy There is really one "special" thing in the arg list, which is & which separates the required (fixed number) args from the variadic args.

borkdude 2021-05-02T09:35:59.402900Z

These variadic args are always received as a sequence. But as a convenience you can use destructuring in the arglist, same as in other places like let.

Jim Newton 2021-05-02T09:36:14.403200Z

I’m referring to the recent @didibus comment concerning an argument list like [{:keys [^Int bar]}]

borkdude 2021-05-02T09:37:17.404500Z

So when you have x y & {:keys [a b]} and you pass 1 2 :a 1 :b 2: x = 1, y = 2, rest = (:a 1 :b 2) which is destructured

Jim Newton 2021-05-02T09:38:25.405700Z

great! so are those rules stated anywhere, or does the programmer (me) have to understand lots and lots of interrelated concepts to understand it?

borkdude 2021-05-02T09:38:59.406100Z

Clojure has functions like destructure and maybe-destructured (https://github.com/clojure/clojure/blob/b1b88dd25373a86e41310a525a21b497799dbbf2/src/clj/clojure/core.clj#L4504) which are very helpful for re-implementing fn

borkdude 2021-05-02T09:39:32.406700Z

There are only two interrelated concepts here: variadic arguments and destructuring

Jim Newton 2021-05-02T09:40:24.407200Z

what is wrong with this function invocation?

((fn [& {:keys bar}]
   (list bar)) :bar 3)

borkdude 2021-05-02T09:41:07.407500Z

{:keys [bar]}

Jim Newton 2021-05-02T09:41:13.407700Z

OIC, the missing [] yes indeed

Jim Newton 2021-05-02T09:43:25.409500Z

so apparently this syntax [& {:keys [bar]}] allows any number of :bar value pairs at the call site, as well as any number of other unmentioned keys. The right-most value associated with an :bar is bound to the bar variable within the function.

Jim Newton 2021-05-02T09:43:52.409900Z

what if I want to disallow repeated keys and disallow unmentioned keys?

Jim Newton 2021-05-02T09:45:09.410500Z

in Common Lisp I can specify &allow-other-keys but I cannot preclude repeated keys

borkdude 2021-05-02T09:45:35.410900Z

Clojure has an open world philosophy where additional other keys are usually not warned against

borkdude 2021-05-02T09:46:28.411900Z

This is where libraries like spec come in. spec2 will have an option to specify this

borkdude 2021-05-02T09:46:42.412400Z

Although at this point it's unclear if and when spec2 will see the light of day

borkdude 2021-05-02T09:47:20.413400Z

Libraries like malli or prismatic/schema also support this

Jim Newton 2021-05-02T09:47:54.414100Z

Not sure whether you’ve seen the post by @didibus, but which of the following would be applicable ?

(dsfn foo
 ([& {:keys [^Int bar]}]
  (list 'int 'bar bar))
 ([& {:keys [^Int baz]}]
  (list 'int 'baz baz))
 ([& {:keys [^String bar]}]
  (list 'string 'bar bar)))
to a call site such as (foo :baz 42) ? But a literal interpretation, the first would be applicable because unmentioned keys are silently ignored, but that’s not what the caller would expect.

borkdude 2021-05-02T09:49:31.414900Z

It depends on the rules of your dsfn macro, I guess you are the master of your macro

Jim Newton 2021-05-02T09:50:10.415500Z

Yes, I think that the caller/programmer WOULD NOT EXPECT normal clojure evaluation rules to apply.

borkdude 2021-05-02T09:51:55.417100Z

In practice, I think the signature of that function would be unusual: one branch is interested in one key, but another branch is interested in only another key with no overlap. Usually you have a common set of required keys + some optional ones.

Jim Newton 2021-05-02T09:51:55.417200Z

on the other hand, I think there are other specifiers other than :keys which can be used in this syntax. right?

borkdude 2021-05-02T09:53:33.417900Z

(dsfn foo ([& {:required [^String bar]}]))

borkdude 2021-05-02T09:53:48.418300Z

you can make up whatever you want in macros, although this requires custom destructuring logic

borkdude 2021-05-02T09:54:18.418800Z

I guess you could merge the commonalities into one arity, destructure and then check

✔️ 1
Jim Newton 2021-05-02T09:56:28.419800Z

Somewhere I say some extra syntax for inside these braces. syntax that provides default values for example. Is there a section about destructuring in https://clojure.org/reference

Jim Newton 2021-05-02T09:57:23.420100Z

found it https://clojure.org/guides/destructuring

2021-05-02T10:18:47.420300Z

In standard Clojure JVM you're not allowed more than one variadic overload, so normally that scenario would throw:

CompilerException java.lang.RuntimeException: Can't have more than 1 variadic overload

2021-05-02T10:20:34.420500Z

But that's similar to how you're also not allowed two overloads of the same arity. With your macro though, since it can dispatch on type, for this example I do think the expectations would be it calls the second one.

2021-05-02T10:22:10.420800Z

And if someone called it like so (foo :other 10) then I'd expect the first one to apply. Since your macro does already prioritize the first match left to right.

2021-05-02T10:26:20.421100Z

And I'd expect the third one if someone typed (foo :bar "hello")

2021-05-02T10:27:42.421300Z

And finally if someone typed: (foo :other "hello") I'd expect the first one again.

yuhan 2021-05-02T11:23:52.421500Z

I'm nowhere of an expert on language/library design but I think this is what Rich Hickey meant in Simple Made Easy when talking about pattern matching and switch statements being sources of complexity - you can have powerful rules for dispatching different behavior, but having separate semantics of arity, type, key inclusion all mixed together in a single spec leads to these sort of puzzles and edge cases

yuhan 2021-05-02T11:27:20.421700Z

Where the person reading the code has to basically draw an inheritance chart or run a type inferencer in their heads to figure out what their code does, and the notion of "what the caller expects" may not always be clear

1
alexmiller 2021-05-02T12:38:06.422400Z

Destructuring reference is at https://clojure.org/reference/special_forms

Jim Newton 2021-05-02T13:32:13.423300Z

@didibus I have a question for you about a user’s intuition w.r.t. destructuring keyword argument lists

Jim Newton 2021-05-02T13:37:20.424900Z

As I understand Clojure is in general permissive about ignoring information it doesn’t care about. Why does the following form trigger an error? Shouldn’t it simply ignore the unrecognized key/value pair?

((fn [& {:keys [bar] :ignore-other-keys true}]
                        (list bar)) :bar 3)

Jim Newton 2021-05-02T13:37:52.425300Z

why am I disallowed from putting extra keys in this map?

ghadi 2021-05-02T13:40:05.427200Z

When something triggers an error, as a rule you must post the error :)

2021-05-02T13:40:19.427600Z

I wouldn't say Clojure is always permissive about ignoring extra things in syntax. If you are getting a bunch of error messages when you try to do that beginning with "Syntax error macroexpanding clojure.core/fn at ..." then that is from the spec syntax checking of some macro invocations, in this case fn, that was added in Clojure 1.9.0

2021-05-02T13:41:02.427900Z

Another example in a different area:

user=> (if true 5 7 8)
Syntax error compiling if at (REPL:1:1).
Too many arguments to if

Jim Newton 2021-05-02T13:41:30.428300Z

why should fn care if there’s an entry in the map which it doesn’t care about?

Jim Newton 2021-05-02T13:41:52.429300Z

Of course I can filter it out before I expand to the call to (fn …)

2021-05-02T13:42:17.429900Z

Probably because Clojure's destructuring facilities would never use that for anything, and it is likely a sign of a bug in the user's program that they are passing syntax that the Clojure compiler would ignore completely.

ghadi 2021-05-02T13:42:31.430100Z

Post. The. Error

2021-05-02T13:43:31.430600Z

Clojure 1.10.1
user=>       ((fn [& {:keys [bar] :ignore-other-keys true}]
                        (list bar)) :bar 3)
Syntax error macroexpanding clojure.core/fn at (REPL:1:2).
:ignore-other-keys - failed: simple-symbol? at: [:fn-tail :arity-1 :params :var-params :var-form :map-destructure :map-binding 0 :local-symbol] spec: :clojure.core.specs.alpha/local-name
:ignore-other-keys - failed: vector? at: [:fn-tail :arity-1 :params :var-params :var-form :map-destructure :map-binding 0 :seq-destructure] spec: :clojure.core.specs.alpha/seq-binding-form
:ignore-other-keys - failed: map? at: [:fn-tail :arity-1 :params :var-params :var-form :map-destructure :map-binding 0 :map-destructure] spec: :clojure.core.specs.alpha/map-bindings
:ignore-other-keys - failed: map? at: [:fn-tail :arity-1 :params :var-params :var-form :map-destructure :map-binding 0 :map-destructure] spec: :clojure.core.specs.alpha/map-special-binding
:ignore-other-keys - failed: qualified-keyword? at: [:fn-tail :arity-1 :params :var-params :var-form :map-destructure :qualified-keys-or-syms 0] spec: :clojure.core.specs.alpha/ns-keys
true - failed: vector? at: [:fn-tail :arity-1 :params :var-params :var-form :map-destructure :qualified-keys-or-syms 1] spec: :clojure.core.specs.alpha/ns-keys
:ignore-other-keys - failed: #{:as :or :syms :keys :strs} at: [:fn-tail :arity-1 :params :var-params :var-form :map-destructure :special-binding 0] spec: :clojure.core.specs.alpha/map-bindings
{:keys [bar], :ignore-other-keys true} - failed: simple-symbol? at: [:fn-tail :arity-1 :params :var-params :var-form :local-symbol] spec: :clojure.core.specs.alpha/local-name
{:keys [bar], :ignore-other-keys true} - failed: vector? at: [:fn-tail :arity-1 :params :var-params :var-form :seq-destructure] spec: :clojure.core.specs.alpha/seq-binding-form
& - failed: vector? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list

2021-05-02T13:43:53.430900Z

Sorry, that was long enough I probably should have put it in a thread comment

ghadi 2021-05-02T13:46:09.431900Z

1. this error represents clojure.spec checking the valid grammar of params and destructuring forms

ghadi 2021-05-02T13:47:07.432900Z

2. these specs get checked at macroexpansion time for certain macros (defn/fn/let)

ghadi 2021-05-02T13:47:26.433800Z

3. it returns a mouthful because of ambiguous parsing

ghadi 2021-05-02T13:47:45.434800Z

and it shows the potential ways something could be short/long/incorrect

Jim Newton 2021-05-02T13:47:47.434900Z

yes, @ghadi, the question is why doesnt the syntax check allow me to put a key/value in the hash that it doesn’t care about, and that hurts nothing?

ghadi 2021-05-02T13:47:56.435100Z

because it does hurt something

ghadi 2021-05-02T13:48:07.435700Z

it's an invalid map destructuring pattern

ghadi 2021-05-02T13:48:36.436700Z

{binding key} <- is valid map destructuring

Jim Newton 2021-05-02T13:48:38.436900Z

:allow-other-keys` means nothing to fn, so it would be nice if the syntax checker simply ignored it

ghadi 2021-05-02T13:48:45.437100Z

binding = symbol, and you've provided a keyword

borkdude 2021-05-02T13:48:56.437400Z

ad 2. for every macro I think, it's all or nothing, can be disabled using a system property (or dynamic var?)

ghadi 2021-05-02T13:49:43.438500Z

let's say the invalid syntax was accepted, who/what would act on that flag?

2021-05-02T13:50:04.439Z

I believe it would be possible to write your own macro, similar to but not the same as fn or defn that are built in, that do different syntax checking than they do.

Jim Newton 2021-05-02T13:51:21.440700Z

@ghade, if nothing would act on it then it would be harmless. The idea is that I have a macro which expands to fn, my macro understands :allow-other-keys and I’d like to simply pass along my map to fn without having to rebuild it.

Jim Newton 2021-05-02T13:51:43.441400Z

of course I can code walk the expression and remove :allow-other-keys, it just seems unnecessary

2021-05-02T13:53:01.442900Z

In this case, the Clojure compiler / macro-expander is trying to be helpful to developers who misunderstand the correct kinds of destructuring forms that are allowed, and letting them know that they did something useless, which is likely a bug in their code.

borkdude 2021-05-02T13:53:14.443200Z

I think you should code walk as it will trip up people's programs otherwise. In these very fundamental building blocks it's actually nice that clojure has some sanity checks (although the output can be overwhelming to some people)

2021-05-02T13:54:25.444200Z

Jim, you started this discussion with the sentence "As I understand Clojure is in general permissive about ignoring information it doesn’t care about." I tried to reply that this is not a general rule that applies to everything in Clojure. Yes, there are some things it will ignore, but there are others where it checks and gives errors/warnings.

✔️ 1
borkdude 2021-05-02T13:54:57.444800Z

It applies to data in/out but not so much to fundamental syntax like defn or ns

2021-05-02T13:55:55.446100Z

I'm sure there are several open JIRAs with requests to tighten up checking even more than it is checked now. The kinds of checks done aren't quite static, but they do not change rapidly across Clojure versions, either. Clojure 1.9 introduced quite a bit of additional syntax checks like this.

yuhan 2021-05-02T13:59:45.448100Z

I'm glad that Clojure throws a compile-time error for that particular case - 99% of the time I destructure using :keys, and the other 1% I'll often accidentally reverse the order of the bindings just because having the keyword on the left is more familiar:

(let [{:a a} {:a 1}]
  (inc a))
so it makes sense to prevent this sort of user mistake up front by throwing an error

Jim Newton 2021-05-02T14:06:17.448200Z

You commented that you would like dsfn to be able to handle something like the following.

(dsfn 
 ([&amp; {:keys [^Int bar]}]
  (list 'int 'bar bar))
 ([&amp; {:keys [^Int baz]}]
  (list 'int 'baz baz))
 ([&amp; {:keys [^String bar]}]
  (list 'string 'bar bar)))
The question is whether the pattern matcher should allow other keys or not. For example if the call-site argument list is (:bar 1 :xyzzy 2) should the first clause match or be rejected. The matcher can be written to have either behavior. In Common Lisp, there is a syntax for determining which behavior you want. I considered using a syntax like the following:
(dsfn 
 ([&amp; {:keys [^Int bar] :allow-other-keys true}]
  (list 'int 'bar bar))
 ([&amp; {:keys [^Int baz] :allow-other-keys false}]
  (list 'int 'baz baz))
 ([&amp; {:keys [^String bar]}]
  (list 'string 'bar bar)))

Jim Newton 2021-05-02T14:09:37.448900Z

yes, I’ll just filter it out before passing the map on to the clojure primitive.

Jim Newton 2021-05-02T14:58:18.449500Z

why shouldn’t the second apply if you typed (foo :bar "hello") This seems to be implied by your suggesting that the first should apply on (foo :other 10) , what’s the difference between the two cases?

Jim Newton 2021-05-02T15:10:08.449700Z

Imagine that the two ladder cases were not there:

Jim Newton 2021-05-02T15:12:34.449900Z

(dsfn foo
 ([&amp; {:keys [^Int bar]}]
  (list 'int 'bar bar)))
In this case the clause would match if given (foo :other 10), right. The idea is that the first possible match is taken. Adding clauses later on won’t make something match a later clause if it already matches an earlier one. That’s how pattern matching works.

Jim Newton 2021-05-02T15:14:10.450100Z

The way I’ve implemented this is I’ve added an additional keyword :allow-other-keys which can be used as follows

(dsfn foo
 ([&amp; {:keys [^Int bar] :allow-other-keys true}]
  (list 'int 'bar bar)))
This means that the clause is allowed to match even if there are other keys at the call site which are not listed in :keys […]

Jim Newton 2021-05-02T15:14:59.450300Z

:allow-other-keys defaults to false, so the clause won’t match if there are keys at the callsite not mentioned in :keys [ … ].

zendevil 2021-05-02T21:44:06.456800Z

I want to send an http request to my localhost:3000 server from a custom ip address. And apparently this can be done with a custom dns resolver.

(client/get "<http://localhost:3000/api/v1/subscribe/ETH-USD>" {:dns-resolver (doto (InMemoryDnsResolver.)
                                                                              (.add "localhost" (into-array[(InetAddress/getByAddress (byte-array [127 0 0 1]))])))})  
and nothing in the byte-array but 127 0 0 1 is getting evaluated in the repl. I want to eventually run a sequence of multiple requests to simulate multiple clients making those requests.

p-himik 2021-05-02T21:48:01.456900Z

A custom DNS resolver is supposed to resolve localhost as something else. You're trying to resolve it as 127.0.0.1, which should already be done by your system. I don't think you can properly simulate multiple incoming IP addresses this way, although I'm not an expert here.