RFC: `with ... yield` replacement

Agree. I was more concerned not about using some particular word, but removing noise. Also it is already taken by Spec, so we shouldn’t use it.

The with App.routes do looks really nice to me, also I think it can cause confusion, at least when seeing it first time and mostly because of this particular word with and it’s meaning in other languages.

OTOH it might actually be a quick thing to get accustomed to. Also finding other word for this case might be tricky.

Should we also care about not only the first yielded argument but having the pass other arguments?:

with App.routes do |two, three|
  get :foo, two, three
end

Will be the same as:

App.routes do |it, two, three|
  it.get :foo, two, three
end

And yielder:

class App
  def routes
    yield it, two, three
  end
end

Another idea.

What if we use some special name for first block argument to mark it as implicit receiver?

Would it make quite obvious from first look what’s going on here? :thinking:

App.routes do |it, two, three|
  get :foo, two, third
end

Or maybe…

App.routes do |&., two, three|
  get :foo, two, three
end

Or…

App.routes do |&, two, three|
  get :foo, two, three
end

Also works with

App.routes { |&, two, three|  get :foo, two, three }

Calling multiple methods on implicit receiver is nice (the &. doesn’t allow this):
[1, 2, 3].map {|&| "#{to_s} : "{to_s(16)}" }

Expands to:
[1, 2, 3].map {|n| "#{n.to_s} : "{n.to_s(16)}" }

1 Like

Reading all of the responses persuades me that it would be better to leave it as is. Almost any solution makes the code uglier. I think it’s OK to have such an implicit feature (with yield), because most of the time it’s used with DSL, and DSLs tend to be well-documented.

I like the idea of |&|, it could even be possible to use it for not-the-first argument if the user of the dsl ever want that:

foo do |one, &, three|
  bar one, three
end

The only drawback I see would be that we loose the name of the argument along the way, but as we already don’t have it in this case maybe it’s not a problem

@bew Not sure about good use case but we could keep argument name for explicit use. Something like:

foo do |one, two&, three|
  bar one, three
  baz one, two, three
end

Or this. Reminds of instance var, but probably “at two” kind of makes sense to me.

foo do |one, @two, three|
  bar one, three
  baz one, two, three
end
foo do |one, @, three|
  bar one, three
end

How about just accepting self in block args?

setup_routes = ->(self, global_prefix) do
  get "#{global_prefix}/foo"
  post "#{global_prefix}/bar"
end

App1.routes("/app1") &setup_routes
App2.routes("/app2") &setup_routes
1 Like

The problem with with expr do is that the arguments of expr will need to have parenthesis.
So I would prefer to leave expr be top called method.

@asterite If we go with & it should be on method name to keep the parenthesis optional. Even if that is a lookahead in the parser since that is only in the method name, while parsing identifiers. It’s in a well defined place.

We need to support arguments without noise as much as possible to have a good DSL. Something as similar as:

div class: "foo" do
  p "bar"
end

The with for any object will be expr.tap ... with whatever syntax could be.

I haven’t used DSL where the yielded grammar block has many arguments. I would not mind leaving that out of scope and using arg.tap ... to mimic that.

Back to syntax. I agree using a new keyword looks cleaner. But we will lose the chance to use expr { }. I can accept that. It will also allow better error reporting. Let’s say we use doit, then if the block wants or the body yields other than 1 argument, then, there is a compile time error.

I also thought that does might play good as a keyword.

Let’s try the discourse polling to collect the opinions.

  • method& (supporting do/end and { })
  • do& / {&
  • Use doit instead of do (not supporting { })
  • Use does instead of do (no supporting { })
  • Change nothing
0 voters

Where’s my &. proposal in that poll? :slight_smile:

@vlazar Yes, the intention is that foo { &. something } looks similar to what we have right now. In fact I wouldn’t mind foo { &.something } to work too. Or we could change it to be before the block like you propose (making &. a keyword of sort), so foo &.{ something }. I think I like your proposal the best, since:

foo &.something

can be seen as a shortcut to:

foo &.{ something }

with that &.{ ... } meaning "use the first block argument implicitly in the entire block.

Now, to use &. you can do:

foo &.something

but if you have arguments you have to write it like:

foo 1, 2, &.something

Notice the comma before &.. That’s because & is a block argument, it’s not a block, and arguments come separated by comma. So overloading &. might bring a lot of confusion, but I’m not sure.

With do it’ll be:

App.routes &. do
  get :foo 
  post :bar
end

App.routes 1, 2 &. do
  ...
end

App.routes(1, 2) &. do
  ...
end

But not this:

App.routes 1, 2, &.do
  ...
end

because &.do expands to block_arg.do… unless we consider that as an exception, but exceptions are bad.

So I still don’t know which one I prefer. Probably &. after do/{ is better because it can’t be confused with &. before the block.

1 Like

Actually, I’m thinking that having &.do and &.{ as special constructs might be the best because, as I said above, they can be seen as applying &. to the entire block. Now I don’t mind having &.do not mean block_arg.do.

1 Like

@asterite Have you noticed my other idea of having a special name for block argument like & to mean the same thing? Could it work?

With clean DSL argument in mind above everything else… The doit or does are cleaner than using special characters but reads a bit weird and are long words.

I remember the feeling when I was learning Ruby first when everything was looking so nice, but some things like do ... end were just too noticeable in Ruby after less noisy { ... } in other languages. Get used to it eventually, but it took me quite some time.

So it’s time for more crazy Friday ideas! :wink:

Are there any good two letter English words we can use to convey the meaning? There are as, in and of already https://github.com/crystal-lang/crystal/wiki/Crystal-for-Rubyists#available-keywords

With some other candidates from https://en.oxforddictionaries.com/explore/two-letter-words/

word meaning
at expressing location or time
my belonging to me
on supported by or covering
to expressing direction in relation to a location

We can do:

div class: "foo" at
  p "bar"
end

div class: "foo" my
  p "bar"
end

div class: "foo" on
  p "bar"
end

div class: "foo" to
  p "bar"
end

And maybe add of if current meaning can be removed:

div class: "foo" of
  p "bar"
end

Not sure if any of these make sense, but the shorter the keyword the less noise we will have in DSL.

EDIT:
We can also use yo (roughly meaning “with yield … do”). The popularity would probably skyrocket! :joy:

div class: "foo" yo
  p "bar"
end

@vlazar Yes, but the idea is also to not have to type too much. Having to do:

html do |&.|
  body do |&.|
  end
end

will quickly become tedious (having to write |&.|). Instead:

html &.do
  body &.do
  end
end

is a bit shorter and IMO feels more natural.

@asterite Right, it’s tradeoff. If clean DSL is the most important thing, then making something work for all cases wouldn’t do as it’s more generic and noisy. Unless we consider to add both of course, but probably not since we are trying to simplify things, not make them more complex.

Another thing with do |&.| is that it could allow the possibility to make any block argument be the implicit one… but I’m not sure how useful would that be, specially because it doesn’t have a name. But it’s definitely more “powerful” than the other approaches.

Also we could probably think of something for named implicit argument RFC: `with ... yield` replacement

Oops, :-) let’s keep brainstorming then.

It seems there is more consensus to move out of with ... yield on to something explicit at the time of using the method.

We need to find a sweat spot syntax for that.

I think it’s akin to a shorthand notation of sorts, and should be optional. It’s consistent with how block forwarding works here: "1,3,3".split(",").map &.to_i, and the more explicit version "1,3,3".split(",").map{|x|x.to_i}

But also I feel

App.routes& do
  get :foo
  post :bar
end

is less explicit/intuitive and more confusing for several reasons:

  1. The whereabouts of get and post (or any method inside that block) could be coming from anywhere and are unknown. Unless the developer knows in advance what methods App.routes has, but now the developer needs to sift through another class in their brain to remember if that method exists for that class, instead of just looking at the passed blocked and seeing the .methodname.
  2. When a developer glances over the code, they might think one of the methods are not bound to c's object (basically back to point 1)
  3. Not passing the c parameter and using dot notation does not exemplify crystal’s object orientation approach (visually)

So I was shuffling different chars again. Mostly : and . as less noisy for cleaner DSL.

Putting only : or better . near do or { from either side but without spaces does look better to me than & or &.

  • :do and :{ - can even have a name like “mustached block” :smiley:
  • do: and {:
  • .do and .{
  • do. and {.

Based on

How about trying this?

  1. Reserve : or . for marking block argument as implicit receiver. It shouldn’t necessarily be the first argument.
foo do |., second, third|
end
foo { |first, ., third| }
# maybe even this
# second is a named argument now, but also an implicit receiver
foo { |first, .second, third| }
  1. Special case syntax just to drop extra spaces and || in case of single (first) argument.

I like that . goes not directly after a method call, but inside a block, closer to where we actually use an implicit receiver.

ary.map do.
  "#{to_s} : #{to_s(16)}"
end
ary.map {. "#{to_s} : #{to_s(16)}" }
ary.map &.to_s # seems to expand nicely, & basically just adds {} around
ary.map .to_s  # or maybe replace by this instead, but break Ruby-like parsing?

Real example where noise quickly becomes annoying.

html do.
  p class: "foo", 2 do.
    "Lorem ipsum"
  end
end

Compare it with some of the previous ideas:

html& do
  p& class: "foo", 2 do
    "Lorem ipsum"
  end
end

html &.do
  p class: "foo", 2 &.do
    "Lorem ipsum"
  end
end

html do &.
  p class: "foo", 2 do &.
    "Lorem ipsum"
  end
end
1 Like

But event with . and if it’s even possible to make it work in Crystal

html do.
  p class: "foo", 2 do.
    "Lorem ipsum"
  end
end

I still would prefer the visual look without any specia chars like

html it
  p class: "foo", 2 it
    "Lorem ipsum"
  end
end

Too bad it is taken by Spec. But some other two-letter words like at or on might make sense too.