Fork me on GitHub
#clojure-dev
<
2018-04-01
>
sophiago11:04:03

One of the most useful features in Clojure for me would be nested function literals. I really can't see a problem with them design-wise as in they wouldn't affect my ability to reason about code. Maybe that's the result of working with nested lambdas in Scheme, but is it just an issue of one step more work in the implementation or has an argument been made as for why they should be prohibited in the language?

schmee12:04:33

@sophiago You can't nest anonymous function literals but you can nest (fn []) literals just fine

bronsa13:04:46

@sophiago what would d % in #(#(%)) refer to? the inner or outer one?

bronsa13:04:26

you still can use fn instead of the literal syntax just fine

sophiago21:04:13

@bronsa it would obviously refer to the inner one unless Clojure is no longer lexically scoped. That example is no different than (fn [] (fn [x] x)) and similarly anyone should be able to tell which function x binds to so I don't see the argument here. I find it hard to believe anyone doesn't use the same parameter names in different scopes of their Clojure code (or any other code for that matter).

bronsa21:04:58

@sophiago the difference is that you can do (fn [x] (fn [y] but you can't use a different %

bronsa21:04:07

you'd have to #(let [x %] #(..

bronsa21:04:13

which is longer than the fn version

bronsa21:04:16

so what's the point

bronsa21:04:09

and it's also significantly harder to read

dominicm21:04:16

@bronsa it would help where the inner lexical scope doesn't need to share.

dominicm21:04:59

I think it starts to get really confusing around %1 etc. Though. If you're using lexical scope, depending on how you're called, %2 could refer to the parent.

sophiago22:04:05

As mentioned I don't find it significantly harder to read, in fact I constantly do this and don't realize it until the compiler tells me. What I do find confusing is when bindings are used anywhere other than the level where they're defined. I see this comparison more to how literals would ambiguously desugar to named bindings, i.e. the implementation, rather than them being a confusing language feature.

bronsa22:04:31

making this work requires deleting a check and 2 lines of code. this not working is an explicit design decision

bronsa22:04:37

it's not going away

sophiago22:04:09

That was my original question: was this an explicit design decision?

bronsa22:04:12

i think you're in the minority in finding this more useful than error-prone

sophiago22:04:44

That's what I figured. My guess for the reason would not be that shadowing bindings is a good thing, but rather that it's a bad thing people will do anyway that opens up several avenues as to how to interpret the bindings in literals.

bronsa22:04:02

it would introduce quite a bit of cognitive load when reading code that uses it (writing it might feel natural at first, sure), for no particular extra conciseness

sophiago22:04:41

I guess I just didn't see this as such a contentious issue seeing as: a) Clojure is the only dialect of Lisp where nested lambdas aren't very commonly used, and b) the literal syntax is a purely syntactic (yet very helpful) so I wouldn't think it would represent a verdict on the former. It seems clear from this conversation I had the wrong impression and people do have opinions about the first part of that.

bronsa22:04:24

nested lambdas in clojure are used all the time

bronsa22:04:43

and literal syntax doesn't represent a verdict of -a

bronsa22:04:57

i don't undertand how you jumped to that conclusion from what i've said

bronsa22:04:01

we're discussing syntax

bronsa22:04:11

i've never talked about anything else

sophiago22:04:54

I'm just saying something like #(map #(inc %) %) is much more legible to me than (fn [x] (map (fn [x] (inc x)) x)). In fact, I had to count parens just to type the latter without syntax highlighting.

bronsa22:04:14

#(map inc %)

bronsa22:04:36

it might be more legible now

sophiago22:04:39

You can easily imagine a better example.

bronsa22:04:59

but it's also much more ambiguous and will definitely make it harder to read in 2 weeks time

sophiago22:04:29

I'm saying one or the other is basically unavoidable for me and the latter is the less legible one.

sophiago22:04:00

And it seems we agree one or the other is necessary and it's just a matter of syntax.

bronsa22:04:20

the latter is artificially constructed to be less readable and many wouldn't agree with you anyway

bronsa22:04:42

tons of people prefer to use (fn [x] ..) over #() even when the sugar would be possible to use

sophiago22:04:16

So we're back into usage?

bronsa22:04:25

but ok we can keep arguing subjective preferences all we want

sophiago22:04:42

Yes, not so helpful for either of us I'm sure.

bronsa22:04:14

the short answer is I've given you the rationale as to why it's not allowed and I very much doubt rich would change his mind on it

bronsa22:04:25

for good reasons IMO

sophiago22:04:47

I don't quite see the rationale. nested lambdas are fine, shadowing bindings are fine, yet using literal syntax for either is bad. That doesn't make sense to me. I do think it's an issue of trying to discourage the first two, which is fine even if I disagree with it.

bronsa22:04:53

if you find hard to read nested lambdas I'd suggest just lifting the inner one to a let binding

bronsa22:04:07

@sophiago no that's just not true

bronsa22:04:26

it's nonsense to say that clojure discourages nested lambdas

sophiago22:04:28

Adding more bindings contradicts most of the arguments made here.

bronsa22:04:56

i'm sorry but that just doesn't make much sense

bronsa22:04:08

bindings are explicit

bronsa22:04:13

% is implicit

sundarj22:04:16

nested #() is objectively harder to read because it requires mentally keeping track of which #() you're in to know what % refers to; nested fns do not have that cognitive load

bronsa22:04:34

the implicity of %'s binding is the whole point

bronsa22:04:42

one level is fine

sophiago22:04:51

"Objectively harder to read" doesn't make much sense. I understand I'm in the minority here, but it's very much a matter of opinion.

bronsa22:04:07

more than one and the cognitive load to keep track of which lambda % refers to becomes more than the 3 keystrokes you save from using fn

sundarj22:04:13

readability is not a matter of opinion

bronsa22:04:49

you really don't see much code that does (fn [x] (fn [x] for the same reason

1
bronsa22:04:57

you do (fn [x] (fn [y]

sophiago22:04:39

@bronsa you got to the point about it being explicit. The problem with nested lambdas is generally linking where variables are used to where they're bound. For me, making them implicit eliminates that scoping issue.

sundarj22:04:37

i get where you're coming from, but that only applies to this particular case, with your particular reading style. in general, nested #() would be harder to read

bronsa22:04:46

I understand where you're coming from, I myself wish for nested #() from time to time

bronsa22:04:55

there are certainly few cases where it would be convenient

bronsa22:04:01

but it would be just bad design

bronsa22:04:50

@sophiago meh that argument doesn't make any sense to me, so let's just agree to disagree on that :)

sophiago22:04:05

When you mention lifting them into a let that literally desugars into the case I find confusing 😛

bronsa22:04:56

again, you're in a very small minority with this opinion fwiw, in my ~8 years of doing clojure I've never heard anybody argue that explicit binding is more confusing than implicit locals to them

bronsa22:04:56

but hey, if you feel strongly about this make a ticket

alexmiller22:04:30

we’re not going to do this, so don’t make a ticket.

bronsa22:04:07

and even a patch, as I said it's only a matter of deleting 2 lines

bronsa22:04:39

those two specifically

bronsa22:04:32

it's easier to make a ticket and see what the core team thinks about it than argue endlessly about it

sophiago22:04:38

Well, as I pointed out off the bat I think I'd be in the vast majority if you included all Lisps. And the issue of function syntax is one where Clojure has made great syntactic improvements, but maintains the same semantics. On that note, I'm definitely not making a ticket because I know how those things go...I was just looking for the rationale and I do think you provided that for me after a while: there are multiple ways one could interpret nested implicit bindings. That's a general Rich peeve in my experience. My counterargument, were I ever to design my own Lisp (which I really hope I never choose to), is that picking one interpretation encourages better code style, i.e. using bindings inside deeper scopes as little as possible.

bronsa22:04:34

>Well, as I pointed out off the bat I think I'd be in the vast majority if you included all Lisps but you keep making this point and it doesn't make any sense to me -- #() is shorthand syntax and doesn't dictate at all whether nested lambdas are to be used

bronsa22:04:38

in fact clojure uses them all the time

bronsa22:04:43

just look at how transducers are implemented

bronsa22:04:56

>On that note, I'm definitely not making a ticket because I know how those things go. why? it literally takes 2 minutes

sophiago22:04:20

Not 2 minutes for the people responding to it.

bronsa22:04:32

it's not taking me 2 minutes to answer you either

bronsa22:04:06

and trust me, I think on this issue it would take alex less than 2 minutes to respond to it ;)

sophiago22:04:12

My understanding is that you volunteered in an informal setting 🙂

sophiago22:04:04

I don't think you see my point, but I think I understand yours so I'm happy with that.

bronsa22:04:16

please don't assume what I understand or not, that's a tiny bit insulting, I've already said I get why you're asking this & I've wanted it myself from time to time, I just don't think it's a good idea at all

bronsa22:04:38

no hard feelings

sophiago22:04:27

My bad. Fwiw, I was referring to the issue of matching variables to where they're bound. You didn't seem to relate to that.

bronsa22:04:45

no worries

sophiago22:04:31

Eliminates the need to open a ticket.

bronsa22:04:55

sure but my "no" is not authoritative, if you care and wish to see whether the core team would accept it, a ticket is the only way

sophiago22:04:55

Conclusion: I think I'm going to write a blog post where I CPS a function with nested lambdas to demonstrate why the binding site issue is obnoxious and then show an example with one possible interpretation of nested literals that disallows that usage.

dpsutton22:04:12

I would really like to read this blog post. I'm not following too well so this would be good reading. Also I've seen you chat in slack a bit and you always seem to be doing interesting things so it's just another reason I'd like to read it.

sophiago23:04:04

Oh no, this means I actually have to do it 😛

sophiago23:04:51

I already have one draft of a technical post that's been sitting around for about six months now.

sophiago23:04:55

This one at least I think I know how to do, although there's an actual theoretical point that first made me think about this issue and I'm not sure how well I can explain that. I'm writing the person who relayed it to me rn, actually. Something about the global nature of lambda binders making them undecidable to certain term rewriting systems.

alexmiller22:04:01

we’re not going to do this. don’t make a ticket.

👍 1
bronsa22:04:05

or there you go

seancorfield22:04:11

(FWIW, I've never liked the #(..) anonymous function syntax at all -- I almost never use it, and prefer (fn [x] ..) but when I first started doing Clojure, I did use the #(..) form and I did occasionally want to nest them)

2
bronsa22:04:57

I while ago I had an emacs thing that turned the # into a lambda which made it a bit more bearable but now I just tend not to use it

bronsa22:04:10

apart from in quick snippets

sophiago22:04:26

Tbh it's a breath of fresh air for me vs. other Lisp code. let was essentially invented because of that, although Clojure's let is like let* (at least in Scheme, I think CL too but not sure) so much more useful in that regard.

sophiago22:04:58

I hope we've at least determined it's subjective though 😛