Crystal: &. within a block

Created on 13 Feb 2019  Â·  15Comments  Â·  Source: crystal-lang/crystal

Got an idea while reading this forum thread -- make &. an alias to the_first_block_argument. within blocks. In my use-case, I have a Query(T)#join(reference, &block) method which yields another Query(U) instance:

query = Query(Post).new
  .where(id: 42)
  .join(:author) do |q|
    q.select(:username)
    q.select(:bio)
  end

It would be much easier to write (and read) this code instead:

query = Query(Post).new
  .where(id: 42)
  .join(:author) do
    &.select(:username)
    &.select(:bio)
  end

Pros:

  • A developer would not need to think of the argument naming anymore
  • Ampersand is highlighted, which allows the brain to quickly extract the call name (compare q.select and &.select in the example above, listen to how your brain reads it)
  • A developer would still be free to choose between explicit block argument and &. whenever they want to
  • It would conform with the existing foo &.bar syntax, as it expands to foo { |x| x.bar }, which is essentially the same
  • It would not affect the with yield syntax

If the incoming block argument is explicitly named, prohibit the &. usage:

def foo(&block)
  yield "foo"
end

foo do |a|
  &.upcase # Compile-time error, the block has explicit arguments
end

foo do
  &.upcase # OK
end

def bar(&block)
  yield "foo", "bar"
end

bar do |a, b|
  &.upcase # Compile-time error, the block has explicit arguments
end

bar do |a|
  &.upcase # Compile-time error, the block has explicit arguments
end

bar do
  &.upcase # OK, omitting arguments except the first one
end

Nested blocks should work fine as well (should use explicit naming if need to distinguish):

query = Query(Post).new
  .where(id: 42)
  .join(:author) do
    &.select(:username)
    &.select(:bio)
    &.join(:settings) do
      &.select(:foo) # This &. is clearly belong to the nested block
    end
  end

It's just some sweet sugar which would improve the Quality Of a developers Life. WDYT?

Most helpful comment

Yeah, @Sija, thanks, I see that you dislike it. A single "confused" emoji would be enough. I get it. Totally get it.

All 15 comments

It's just some sweet sugar which would improve the Quality Of a developers Life. WDYT?

By exchanging explicit block argument over implicit &? I don't see how, aside of shedding in most cases 3 characters (|x|) for the whole block...

foo do |x|
  x.bar
  x.baz
end

# vs

foo do
  &.bar
  &.baz
end

Sorry to interrupt, but can we continue discussing this in the forums? This is an issue tracker and this is not an issue (it's brainstorming).

@asterite it does not interfere with with yield replacement, it's a separate feature request.

What would happen in such case:

foo do
  &.each(&.upcase)
end

?

If I understood correctly, &.each is from suggested syntax, and &.upcase is existing one.

So, if the first argument of foo is Enumerable, then &.upcase would apply to each member of this Enumerable or to Enumerable itself?

@AlexWayfer, consider this code:

["foo", "bar"].try &.each(&.upcase)

How do we know&.upcase would apply to each member of ["foo", "bar"] or to ["foo", "bar"] itself?

Yeah, @Sija, thanks, I see that you dislike it. A single "confused" emoji would be enough. I get it. Totally get it.

["foo", "bar"].try &.each(&.upcase)

OK, that's good example. If Crystal already can deal with it — I don't see a problem with this.

@vladfaust :D yeah, It's confusing for lil' gain and I do dislike it. If having symbols all over the place would be considered beautiful we all should be praising brainfuck ;)

So this proposal is about, instead of writing this:

foo do |x|
  x.foo
  x.bar
end

writing this:

foo do
  &.foo
  &.bar
end

In short:

  • remove the block argument
  • replace all arguments (x in this case) with &

Conclusion: we only save writing the block argument.

I'm not sure that's a great advantage compared to not changing anything at all in the language, also considering that with ... yield has you typing less.

Sorry, closed accidentally (but I still think this should be closed and discussed in the forums, this is not an _issue_, it's a proposal).

Sorry, closed accidentally (but I still think this should be closed and discussed in the forums, this is not an _issue_, it's a proposal).

  1. I don't agree that "issue" is synonym for "problem" or "bug". It's like "ticket", "question" for me.
  2. One of first lines in the CONTRIBUTING.md, current verstion:

    The issue tracker is the heart of Crystal's work. Use it for bugs, questions, proposals and feature requests.

    So…

@asterite, some additions to your conclusions:

  • A developer would not need to think of the argument naming anymore. And this is a problem, you must know it
  • Ampersand is highlighted and is special symbol, which allows the brain to quickly extract the call name. Compare q.select and &.select in the example above, listen to how your brain reads it. In fact, my brain doesn't pronounce & at all, it's like a picture, I just see it, while q is pronounced as _[k'u]_ -- this is subjective, I know

I think this feature worth at least thinking about.

Also with yield is kinda buggy when there is block forwarding included. That's why I sticked with explicit yield in my case.

Sorry, I don't see any benefit from this:

  • A developer would not need to think of the argument naming anymore

That's not a pro. Naming is hard, agreed. But it's also important to explain what the code is doing. That's why it's hard.

  • Ampersand is highlighted, which allows the brain to quickly extract the call name (compare q.select and &.select in the example above, listen to how your brain reads it)

That's a completely random argument which solely depends on some specific behaviour of a syntax highlighter. I don't see a gain whatsoever. The focus thing doesn't even make sense.

  • A developer would still be free to choose between explicit block argument and &. whenever they want to

So what's the point in having two ways instead of one which everyone agrees on? Crystal's goal is to make it easier for developers to not have to think about circumstantial problems (like "which completely arbitrary alternative should I choose today?") but instead focus on meaningful decisions (like "what's the best name to convey the meaning of this variable?").

  • It would conform with the existing foo &.bar syntax, as it expands to foo { |x| x.bar }, which is essentially the same

One syntax is a shortcut for the other. Using the shortcut in the expanded version is confusing.

  • It would not affect the with yield syntax

It would not affect my grandma's cat either.

@vladfaust "issues" you've highlighted are highly subjective, for one I have none of those. "beauty" argument is also pretty overused (especially by yourself) - some see beauty in say, Andy Warhol's "art", while personally I wouldn't pay a dime for any his pieces, so...

A developer would need to learn another - not-so-clear - construct and understand the distinction between .try &.each &.upcase and &.bar calls within a block - for what, shedding 3 characters altogether and satisfying you subjective sense of "beauty"?

Sorry but I think that topics like this are just waste of time and keyboards for the community... Also, that's what forum is for (on which btw, I haven't seen much praises for such idea).

I think the tones are becoming a bit more aggressive/sarcastic and it's not good. Because of that, I'm going to close this and lock the conversation. However, I strongly suggest continue discussing this in the forums. I think it's a bit related to the existing proposals (a way to omit mentioning the first block argument).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oprypin picture oprypin  Â·  3Comments

pbrusco picture pbrusco  Â·  3Comments

relonger picture relonger  Â·  3Comments

asterite picture asterite  Â·  3Comments

will picture will  Â·  3Comments