RFC: `&.` within a block

However, you’re not forced to use &.. You’re always free to choose what’s better for readability in every particular situation. I have a use-case where &. is more preferable. You have case where explicit argument is more preferable.

The whole proposed thing is syntax sugar. We use &. when we know what the argument exactly is and assume that the reader knows it too. Multiple block arguments are usually harder to remember (as well as their exact order), so it’s better to name them explicitly anyway. I don’t like Ruby’s new @1, @2 thing, I think it brings much more confusion to the code rather than improving it.

I think I get what you are saying, but I guess my point is that if we use &<int> it solves your use case and the one I presented at the same time. I agree more than one block var can be confusing, but it can be helpful at times too (each with a key/value pair for example)

So your use_case would be:

posts = Onyx.query(Post
  .join(:author) do
    &1.join(:settings) do
      &1.select(:foo)
      &1.where(active: true)
    end
  end
)

IMO that is fairly readable. With that said, even if we do what you said and no &<int> I’d be 100% for it. I’ve also needed similar things in my code

2 Likes

Given the example use case above, I think I would prefer the conventional notation:

posts = Onyx.query(Post
  .join(:author) do |x|
    x.join(:settings) do |y|
      y.select(:foo)
      y.where(active: true)
    end
  end
)

It’s a little bit more verbose, but that added expressiveness is actually a benefit IMO. The alternative (be it &1 or only &) is more confusing. For example, it’s hard to tell on the first look that the first call (#join) actually has a different target than the other two because they’re in a nested block.

posts = Onyx.query(Post
  .join(:author) do
    &.join(:settings) do
      &.select(:foo)
      &.where(active: true)
    end
  end
)

The amount of code saved is not that much, and the expressive version is also more flexible.

In this particular use case I agree. I actually didn’t realize they were different blocks at first glance. However I think the proposal is still useful. For example, imagine there was just 1 block there

1 Like

Well, the same rule is applicable to with yieldblock is the only indicator that something’s happening. However, we’ve got used to it. In fact, I see my proposal as improvement to with yield, which tries to preserve the API syntax but gives flexibility in block argument explicitness:

API.router do
  &.get "/" {}
  &.post "/" {}
end

I think that a core aspect of nice DSL is that the receiver is implicit.

Although allowing the usage of &. multiple times in the body can reduce 3 chars it’s not very different to me. It also introduces ambiguity in the current language.

foo do
  &.first &.second
end

It is not clear in the above example sends #second in the same context as #first or in the object that can be yielded by #first.


On another note, the index versions remind me too much to De Brüijn and it makes me happy, but I don’t think there is a real benefit. If you need to interact with multiple yielded values the code is probably more complex. If the code is more complex I think is better to let it break when moving it around. If we use names that work everywhere that will not happen.


Finally, why this thread is in Offtopic?! :stuck_out_tongue:

4 Likes

Welp. The _ prefix seem to be picked up by Ruby team for numbered parameters in Ruby.

2 Likes

Is this really any more grokkable than naming your variable? As somebody without any context to the library, I have no idea what & is in this case.

3 Likes

For reference. The @ was changed to _. Details https://bugs.ruby-lang.org/issues/15723#note-126

To me

foo do
  &.first &.second
end

would clearly be short for

foo do |x|
  x.first { |y| y.second }
end

because

foo do |x|
  x.first
  x.second
end

would be written more concisely as

foo { &.first; &.second }

That said, I’m personally in favor of &n.method with &.method being an implicit &1.method.

I think it is still too unclear. For example, what if you want to pass those arguments in a different order?

So I agree that doing &n is clearer AND you could use it to pass arguments as is, like in Elixir

usernames.map(slugify(&1)

This would be SOO handy. I think you should still be able to leave off the n if you call a method on it and it is the only argument passed to the block.

I would just LOVE to have this. Another example using try where often I don’t really have a good name for the value to pass. Would be great to do this

email.try do
send_email(&1)
end

So nice. Now I don’t need to come up with a name. I can just pass the argument

Do remember &-> is a thing, which could be used in this context. email.try &->send_email(Email).

Good point @Blacksmoke16! Thanks for the tip. Would still be cool for other types of methods like SomeModule.do_something(&1) but I didn’t think of doing &-> for some of the simpler cases

I guess we could copy Ruby and go with _1 or &1. Maybe &1 is better because it reminds of a block. I think it shouldn’t be that hard to implement… I do wonder whether the end result is more readable or more cryptic.

3 Likes

Yeah I personally like the & more. it stands out visually and is kind of like the existing &. Also matches what Elixir does exactly and that has worked well in that community

I think if abused it could lead to some super hard to read code, but I think that’s an ok risk. People can abuse all kinds of things :P If used well I think it can reduce mental overhead and make writing/reading easier in many cases.

I think in Elixir it’s a bit different because to use &1 and &2 you have to use an extra & before the expression. Like &foo(&1) or &(&1 + &2). So & as a prefix means “convert this to an fn”. In our case… I don’t know how would that work. I guess something like

[1, 2, 3].each_with_index { puts &1 + &2 } 

But it’s not exactly like in Elixir. Using & as a prefix can’t work because that already means “pass this as a block argument”.

1 Like

Oh right yeah you need to capture it in Elixir, but I like the idea of using &1 and &2 as args.

I like your example of each_with_index as well. That’s another common pattern I have.

So are you saying this could be added to Crystal do that or it would not work?

I think it can work. I’m just not sure it’s great for readability.

2 Likes

As you wanted feedback, I’ll give my initial takes on it…this is not meant as an attack but to help you see others’…reaction to the proposed syntax :)

That one is like “what is the magic &? where’s it even coming from?” It kind of grows on you but initially it’s like…what the…

Again my initial reaction is “what the… that is a lot of ampersands…that represent different scopes?” Confusing…maybe I’m just missing something…

Just for fun thought I’d try and see if there are some other alternatives for the use case (though I realize that’s not the actual goal).

Maybe like

  y = Onyx.query(Post)
  posts = y
  .join(:author) do
    y.join(:settings) do
      y.select(:foo)
      y.where(active: true)
    end
  end
)

Or something more railsy like

posts = Onyx.query(Post
  .join(:author)
    .join(:settings)
      .select(:foo) { |foo|
        foo.where(active: true)
      }
  )

But since blocks can return values, may you could just define join and select methods then it’d be:


posts = Onyx.query(Post
  .join(:author) do
    join(:settings) do
      select(:foo) do
        where(active: true)
      end
    end
  end
)

OK that’s a bit scary but anyway. Just brainstorming.

&1 syntax seems better than just &

&.itself seems confusing…

It’s more terse, but is that better? Or maybe it’d be awesome I’d have to think about it and let it soak in.

Just some thoughts.
Cheers!

Personally I never quite got the point of these anonymous block arg thingies, in any language. Also it seems like something to open the door to many style discussions, especially when supporting more than a single one, since whether using them in any particular instance or not being still clear code seems quite subjective And then there will be people like me which will just never use them and then get tons of “why don’t you” on the code reviews.

So I don’t mind too much but if I had the decision, I wouldn’t do this. I definitely don’t miss anything for this.

1 Like