clojure-dev

Issues: https://clojure.atlassian.net/browse/CLJ | Guide: https://insideclojure.org/2015/05/01/contributing-clojure/
john 2019-06-15T15:58:37.112600Z

Does using something like %:some-map-key for destructuring maps in anon fn args sound like a reasonable idea?

john 2019-06-15T16:00:04.113400Z

Or at that rate, %[... and %{... with their destructure semantics

bronsa 2019-06-15T16:01:16.113700Z

no

bronsa 2019-06-15T16:01:26.114100Z

neither of those would be a valid symbol

bronsa 2019-06-15T16:01:58.115Z

so there would need to be special support at the reader level just for some special syntax that doesn't really readability

danielneal 2019-06-15T16:02:16.115600Z

*improve readability

bronsa 2019-06-15T16:02:22.115800Z

ty

danielneal 2019-06-15T16:02:27.116Z

Np

john 2019-06-15T16:03:45.117200Z

right, % can be a valid symbol. So the existing variants don't break things syntactically

bronsa 2019-06-15T16:04:37.118Z

: is not a valid charcter for as ymbol, and [ or { can't be part of symbols either

john 2019-06-15T16:04:43.118200Z

right

bronsa 2019-06-15T16:05:02.118700Z

but even if you picked some other syntax that was a valid symbol, I'd still say not a good idea

bronsa 2019-06-15T16:05:27.119100Z

it doesn't buy you much and adds extra special syntax

2019-06-15T16:07:59.120Z

anything that's about anon-fn-args would have to be implemented in the reader anyhow so "you'd have to do it in the reader" isn't...you know...like...the objection it sounds like...or whatever

john 2019-06-15T16:08:35.120500Z

Well, so a smaller change would be to simply allow %some-map-key to destructure a map key from a passed in map

john 2019-06-15T16:09:14.121300Z

that's a valid symbol and could be contained in the fn* logic that picks out the indexed args

2019-06-15T16:09:16.121400Z

or maybe it wouldn't; I guess you could do it with a macro

2019-06-15T16:09:39.121800Z

the current stuff just happens to be implemented in the reader

john 2019-06-15T16:09:48.122Z

ah

john 2019-06-15T16:11:07.123300Z

It does add extra semantics, but it'd sorta match up the sugar anon syntax with the rest of the language of being able to deal with both indexical and associative parameters

2019-06-15T16:12:25.124400Z

(defmacro % [& crazy-fn-args] ...)

(% (+ %:foo %:bar))
can do it in userland

john 2019-06-15T16:13:31.125Z

would that work with anon fns though?

bronsa 2019-06-15T16:13:39.125300Z

@gfredericks well there's no "special" syntax for #(), % is read as a regular symbol and interpreted in a special way

2019-06-15T16:14:11.126Z

@bronsa the reader expands it based on the largest %400 present

bronsa 2019-06-15T16:14:34.126500Z

right

2019-06-15T16:14:51.126900Z

I mean I'm sure we both understand what's going on and are only going to argue about what you meant by "special"

2019-06-15T16:15:13.127200Z

@john I don't understand your question

bronsa 2019-06-15T16:15:56.127700Z

% is intercepted at read time before it reaches macroexpansion time

bronsa 2019-06-15T16:16:06.128100Z

so if % was a macro it wouln't nest the same way as it does(n't) now

john 2019-06-15T16:16:32.128900Z

@gfredericks oh, I see what you mean, like using that macro instead of using #()

2019-06-15T16:16:34.129100Z

okay you're just saying the name % is bad

bronsa 2019-06-15T16:16:38.129400Z

yep

bronsa 2019-06-15T16:16:43.129700Z

any other symbol would do

2019-06-15T16:16:46.129800Z

@john yes, though as @bronsa mentions it maybe needs a different name

2019-06-15T16:19:04.131300Z

I believe you'd need a similar "can't nest them" restriction as #(), though I don't know how you'd detect/enforce it

bronsa 2019-06-15T16:24:46.132300Z

user=> (let [x (with-local-vars [x nil] x)] (defmacro can't-nest [body] (if (bound? x) (throw (Exception. "can't nest")) (with-bindings {x nil} (macroexpand body)))))
#'user/can't-nest
user=> (can't-nest 1)
1
user=> (can't-nest (can't-nest 1))
Syntax error macroexpanding can't-nest at (REPL:1:1).
can't nest

bronsa 2019-06-15T16:24:51.132500Z

πŸ™ƒ

bronsa 2019-06-15T16:25:49.133Z

(you'd need to macroexpand-all nestedly, but you get the idea)

2019-06-15T16:26:01.133400Z

is the worst part about this that macroexpand-all doesn't work?

bronsa 2019-06-15T16:26:32.134400Z

you could use t.a.jvm/macroexpand-all or ridley if you really felt inclined

bronsa 2019-06-15T16:28:07.134900Z

i suspect it could massively break when used in combinations with some crazy macros like go though

bronsa 2019-06-15T16:28:33.135400Z

not that i'd think it was a good idea even if it didn't break, was mostly a joke

2019-06-15T16:28:53.135600Z

sure

2019-06-15T16:29:23.136100Z

I guess that's the best part about implementing the base parse in the reader -- detecting nesting cleanly

bronsa 2019-06-15T16:30:17.136900Z

yeah, reader macros have the nice property of expanding in the "opposite" order of regular macros

bronsa 2019-06-15T16:31:13.137800Z

not really opposite but can't think of a better way to say it

bronsa 2019-06-15T16:32:09.138200Z

anyway this is going a bit off topic, sorry

2019-06-15T16:37:37.138400Z

they sorta do

john 2019-06-15T16:42:40.139400Z

ah and the %some-key syntax wouldn't work because numbers and the & symbol are valid keys too

john 2019-06-15T16:43:12.139900Z

I didn't think symbols with colons in them were allowed but that apparently works

bronsa 2019-06-15T16:44:57.140400Z

loads of theoretically unreadable symbols are accepted by the reader

john 2019-06-15T16:45:22.141300Z

That's specifically not supposed to be allowed though right?

bronsa 2019-06-15T16:45:25.141400Z

%:foo is not a readable symbol, but the reader accepts it anyway

bronsa 2019-06-15T16:46:01.142Z

yeah, I'm pointing out that just because "it apparently works", doesn't mean it should be used

bronsa 2019-06-15T16:46:29.142600Z

although I'm not sure what the reader allows and disallows is ever going to change so that may not mean much in practice

john 2019-06-15T16:51:42.143700Z

All it really saves you is from having to do (:foo %)... not much. But I don't know, this seems pretty clojurey to me #(Point. %:x %:y %:z)

2019-06-15T17:00:56.144100Z

@bronsa I like the idea of using the terms "de facto" and "de jure" to distinguish these cases πŸ™‚

2019-06-15T17:01:10.144500Z

dunno if I can persuade anybody else of that though

bronsa 2019-06-15T17:01:41.145Z

lol

2019-06-15T17:02:07.145500Z

@john there's also some weird asymmetry in the fact that you presumably can only reference the keys of the first arg, but you can otherwise access other args

john 2019-06-15T17:02:39.145800Z

%4:y πŸ˜‰

2019-06-15T17:02:50.146100Z

I was afraid that would happen

πŸ˜‚ 1
john 2019-06-15T17:02:58.146300Z

But yea, asymmetric

john 2019-06-15T17:03:31.147Z

and %4:y is a little ugly to my current sensibilities

dpsutton 2019-06-15T17:04:03.147900Z

Just from a usability side I would hate deciphering code like that. I don’t like complicated #(...) forms

john 2019-06-15T17:04:58.148300Z

From another angle, one might argue that this is more self documenting #(Point. %:x %:y %:z)

john 2019-06-15T17:05:40.148700Z

But yeah, %4:y is starting to look a little like perl or something

john 2019-06-15T17:06:30.149200Z

But it's only one level deep... not that confusing :thinking_face:

2019-06-15T17:12:51.149400Z

you mean it's not that deep πŸ˜‰

john 2019-06-15T17:16:51.149900Z

It's not a second or third order that, is all I'm saying

john 2019-06-15T17:19:58.151700Z

What does that even mean anyway lol... anyway, good chat. Maybe I'll try the idea out sometime and report back

2019-06-15T18:46:18.155300Z

Speaking of which, what's the reason for not allowing #() to nest?

2019-06-15T18:49:11.156600Z

well % becomes ambiguous; I can't think of any severe ambiguities/problems that would arise from an explicit "inner #() can never reference args from any outer #()" rule

2019-06-15T18:52:42.158100Z

I wouldn't be surprised if the fact that (fn [...] ...) isn't very many more characters, and does not have the restriction or extra-rule-to-learn overhead for reading and understanding, factored into the decision somewhere.

2019-06-15T18:52:52.158400Z

But I didn't design it, so those are just guesses on my part.

2019-06-15T18:55:06.159100Z

yeah it's definitely the more conservative choice; easy to relax the rules later, hard to enstricten them

john 2019-06-15T20:43:10.161800Z

So I tried to do something in user land using just tagged readers, like #m/% #(Point. %:x %:y %:z) or #(Point. #m/% :x ... and neither work πŸ˜•

2019-06-15T20:47:01.162400Z

that looks makeworkable what's it do?

bronsa 2019-06-15T20:51:59.162900Z

% is interpreted too early in the reader in #() for the first to work

bronsa 2019-06-15T20:52:52.164Z

the second may work

bronsa 2019-06-15T20:53:07.164400Z

unless the transformation fro #m/% :x to (:x %s) happens too late in the reader, could be

2019-06-15T20:54:36.164700Z

oh I see, the reader won't just pass %:x through

2019-06-15T20:54:44.165Z

you could imagine it being implemented more leniently

bronsa 2019-06-15T20:54:59.165400Z

user=> (binding [*data-readers* {'m/% (fn [k] (list k '%))}] (read-string "#(Point. #m/% :foo)"))
(fn* [] (Point. (:foo %)))
yeah, in the second the transformation happens too late

bronsa 2019-06-15T20:57:31.166500Z

the reader would need to "re-read" the form after the tagged reader has run

bronsa 2019-06-15T20:57:47.166900Z

since it only interprets literal % symbols in a #() context

bronsa 2019-06-15T20:58:15.167300Z

quite a subtle edge case

bronsa 2019-06-15T20:59:03.168Z

not even sure it's intentional, I'd say it's just a byproduct of the LispReader implementation

bronsa 2019-06-15T21:01:43.168900Z

could definitely be made to work, not sure if it's worth the effort

2019-06-15T21:03:17.170900Z

Right... so it's mostly an arbitrary restriction. I see. I've slowly started to just rely more and more on fn exclusively. There's still the occasional #() when there's a need for a short single fn call, since you don't need to duplicate parens. But I'm guessing the restriction is actually to kinda force people into this pattern

john 2019-06-15T21:05:35.171500Z

I may have done it wrong, but

(#(println #m/% :bob) {:bob 1})
WARNING: Use of undeclared Var lz.core/% at line 1 <cljs repl>
nil
nil

bronsa 2019-06-15T21:06:09.171900Z

that's compiling to (fn [x] (:bob %))

bronsa 2019-06-15T21:06:13.172100Z

which is wrong

bronsa 2019-06-15T21:06:37.172600Z

there's no way to make it work correctly with #()

john 2019-06-15T21:06:42.173Z

yeah

bronsa 2019-06-15T21:06:58.173400Z

#() interprets % args at the char level not at the sexpr level

john 2019-06-15T21:07:04.173700Z

ah

bronsa 2019-06-15T21:07:16.174Z

so it's too early for any custom reader macro, which returns sexprs

bronsa 2019-06-15T21:07:20.174200Z

(well, edn)

bronsa 2019-06-15T21:07:42.174700Z

and too late for the other way round

john 2019-06-15T21:07:43.174800Z

So to try it out, I'd have to play with the reader

bronsa 2019-06-15T21:07:55.175100Z

no, you'd have not to use #() at all

john 2019-06-15T21:08:36.176400Z

I mean, I'd have to change the logic that handles the char level reading of #() forms in clojure

2019-06-15T21:08:41.176500Z

and then you have to write your own walking code and decide what to do about nesting

bronsa 2019-06-15T21:08:56.176900Z

something like #f/% (foo #v/% :foo) could be made to work

bronsa 2019-06-15T21:09:03.177100Z

but the implementation would be extremely complex

bronsa 2019-06-15T21:09:12.177500Z

and you're getting more verbose than the alternative which already works :)

2019-06-15T21:09:19.177800Z

@bronsa data readers and macros are equivalent approaches here, right?

bronsa 2019-06-15T21:09:29.178Z

kinda sorta not really

bronsa 2019-06-15T21:09:42.178400Z

but you're getting into insane edge cases territory if you want to know the difference

1
2019-06-15T21:10:27.178900Z

is there an emoji with a face expressing half horror and half eager anticipation

bronsa 2019-06-15T21:10:37.179100Z

hah, not that fun

bronsa 2019-06-15T21:11:08.179800Z

if it were a macro, other macros would see the unexpanded macro expression vs the fn expression for one

bronsa 2019-06-15T21:11:17.180100Z

and that's a problem if your wrapping macro codewalks

2019-06-15T21:11:43.180800Z

oh right

bronsa 2019-06-15T21:11:54.181200Z

the other problem is more subtle and I can't give you a concrete case where it may break off the top of my head, but it exists and it has to do with the fact that fn* are compiled at analysis time

2019-06-15T21:12:18.182Z

wtf

bronsa 2019-06-15T21:12:47.182500Z

this is all supposing that you're trying to use that macro exactly as you'd use #()

bronsa 2019-06-15T21:12:57.182800Z

it's a similar thing that of course exists with fn being a macro

bronsa 2019-06-15T21:13:17.183300Z

but if you're asking if they're equivalent, then this does exist

bronsa 2019-06-15T21:13:37.183800Z

it's just never going to ever be a problem to anybody ever unless they go look for it though :)

bronsa 2019-06-15T21:14:25.185Z

interestingly this is one of the reasons why in common lisp lambda is special cases in both the compiler and the reader

2019-06-15T21:14:54.186200Z

Isn't it just that the code is first read, then tagged literals are applied. Then reading is done, and now macros are applied? So % being part of the reading means it's already been modified?

bronsa 2019-06-15T21:15:52.187100Z

yes but if you're implementing #() as a macro rather than a reader macro which is what @gfredericks was asking, then it would not be a problem

bronsa 2019-06-15T21:16:13.187600Z

what you're saying is kinda the reason why @john reader macros don't work

2019-06-15T21:16:29.188100Z

Right, cause you could plug yourself before it is applied then correct?

bronsa 2019-06-15T21:16:36.188300Z

yes

2019-06-15T21:17:33.189800Z

I think for @john it be easier he just went with a different tag. Like #f(Point. %key1 %key2). That would be possible I believe

bronsa 2019-06-15T21:17:50.190100Z

yes

bronsa 2019-06-15T21:18:12.190400Z

it's what I was suggesting with #f/% (foo #v/% :foo)

2019-06-15T21:19:11.191400Z

Haha ya I see. I think your choice of namespace confused me πŸ˜‰

bronsa 2019-06-15T21:19:50.192500Z

heh, implementing the args via another tagged reader would be slightly easier than using %key, but both approaches could work

2019-06-15T21:20:06.193500Z

I went down a similar rabbit whole a while ago. And realized there's pretty much nothing shorter then just fn. Everything else is barely saving you anything hehe

bronsa 2019-06-15T21:20:08.193700Z

yours requires code walking of the body though, the tagged reader version wouldn't

2019-06-15T21:20:44.194400Z

Ah ya, I see that's true

bronsa 2019-06-15T21:21:06.195500Z

the tagged reader version would be a bit of a mindfuck too, for ad ifferent reason: the args would be read before the fn tagged reader function would be invoked

bronsa 2019-06-15T21:21:20.196100Z

so they'd have to register to the function, not the other way round which is the intuitive impl

john 2019-06-15T21:21:35.196800Z

Then you have to reimplement #()s logic for %numbers, which I was trying to flow through

bronsa 2019-06-15T21:21:41.197Z

yeah

bronsa 2019-06-15T21:21:55.197200Z

it's really not worth it :)

bronsa 2019-06-15T21:22:16.197400Z

fun to think about though

2019-06-15T21:22:41.198300Z

Ya... but what if your key is 1 πŸ˜‹

john 2019-06-15T21:24:22.199800Z

I was thinking :4:ans/akey... Another illegal form :)

2019-06-15T21:28:10.201200Z

Another plus of (fn), which I've started to use over #(), is you can name the anonymous fn, which helps a lot when exceptions are thrown

βž• 1