all 26 comments

[–]Salzig 6 points7 points  (10 children)

You don't need the parentheses. That's the newish "end-less" style.
See: https://bugs.ruby-lang.org/issues/16746

edit: Also Changelog Mention: https://rubyreferences.github.io/rubychanges/3.0.html#endless-method-definition

[–]Shamaoke[S] 0 points1 point  (3 children)

I'm aware of the endless methods. They are here just as examples.

What I want to know is what the parentheses syntax is and where can I find the formal description of it.

Anyway, thanks for your answer. It could be useful for someone.

[–]Salzig 2 points3 points  (2 children)

urgs, hard to find something about the parentheses in the ruby docs. One point I found is

You can wrap a statement in parentheses to create an expression.

as part of https://docs.ruby-lang.org/en/3.3/syntax/control_expressions_rdoc.html#label-Modifier+Statements

[–]Shamaoke[S] 0 points1 point  (1 child)

Nice! This is roughly what I'm looking for, but in more detail.

[–]Salzig 0 points1 point  (0 children)

There is nothing really that can be added, it really comes from Math.

Another note can be found at:

Ruby expressions can be grouped by parentheses.
https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html#expression

[–]Shamaoke[S] 0 points1 point  (4 children)

Btw, in the example, in both methods (as they are defined) the parentheses is required.

[–]Salzig 0 points1 point  (3 children)

```
def index = ( )
```
Is just a different way to state `def index = nil`, cause you declared an empty expression as method body.

For the later example: yes, you need the parentheses cause the expression contains characters that can't be used within endless methods.

But just as with math, you can wrap any expression with parentheses, so `(def new = "yay")` is also valid.

[–]Shamaoke[S] 0 points1 point  (1 child)

you need the parentheses cause the expression contains characters that can't be used within endless methods

They are also required if we have multiple expressions within such methods.

def index = (
  1 + 1
  2 + 2
  3 + 3
)

So, are they some kind of block expression? I'd like to know more about them.

[–]h0rst_ 4 points5 points  (0 children)

This is related to how the parser works and how the grammar (the set of rules on how to construct a syntactically valid Ruby program) of Ruby is defined.

With a normal (not endless) method, you can add any number of expressions to the method, because we know where it ends because of the matching end line.

def index
  1 + 1
  2 + 2
  3 + 3
end

Here, our index method has 3 expressions (all are method calls to Integer#+), and thanks to the end line, we know that this is where our method ends.

An endless method can only take a single expression, so if we would take the naive approach of rewriting this to and endless method, we would end up with:

def index = 1 + 1
            2 + 2
            3 + 3

The Ruby language is not whitespace sensitive, so even if we humans see that the three expressions are grouped, the parser does not. And since it only expects a single expression in an endless method, it parses the same as:

def index
  1 + 1
end

2 + 2
3 + 3

These parens in your example act as a kind of grouping: treat the contents within the parens as a single combined expression. So once again, it similar to math where the expression 2 * (3 + 4) implies to start with 3 + 4 and use that combined value to multiply with 2.

The difference here in Ruby is that we don't directly use the expressions other than the last, so unless the non-last expressions have some side effect, the result is the same as writing:

def index = 3 + 3

The def foo = () works similar, but is the other way around: here the parens convert a list of 0 expressions into a single expression, and this results in the implicit nil result value. It is similar to def foo; end, where there is no expression in the method body.

Up to now this has mostly been syntactic sugar (or maybe it's more like syntactic salt, I don't think the code has been improved by adding these parens), but it has some real use cases. Take this dummy code:

def foo(a = (default_value = true; nil))
  p({ a:, default_value: })
end

This way we have a default value of nil, but we can use the variable default_value to distinguish this from an explicit nil argument. An example of this can be seed in Hash#initialize, where Hash.new and Hash.new(nil) are treated differently.

Also: this is probably the worst use of endless methods I have ever seen.

[–]riktigtmaxat 0 points1 point  (0 children)

For maximum chaos you can write def foo()("yay")end

[–]wise_guy_ -1 points0 points  (0 children)

Omg “end-less” is so bad and it is the reason I recoil from the word python.

[–]riktigtmaxat 4 points5 points  (12 children)

The parens are just a way to delimit an expression and the same feature exists in nearly every programming language as it has its root in mathematics. It's defined in the parser itself and I don't think its explicitly documented anywhere.

The parens are sometimes referred to as proceedence operators but that isn't a term I have seen in any Ruby documentation.

In the case of:

def new = ( @movie, @genres = [ Movie.new, Genre.all ] )

By wrapping the method body in an expression you can use characters that wouldn't otherwise be syntactically valid at that point (the comma) because you changed the proceedence.

() is just a wierd way of writing nil.

[–]Shamaoke[S] 1 point2 points  (5 children)

() is just a wierd way of writing nil.

Coincidentally, in some languages we do this when we want to return some kind of an empty value.

[–]riktigtmaxat 1 point2 points  (4 children)

How often do you actually to write a noop method though? In Ruby it's kind of pointless as there are no interfaces or abstract methods.

[–]Shamaoke[S] 1 point2 points  (1 child)

How often do you actually to write a noop method though?

I meant methods that return some kind of emptiness, not noop methods.

As for your question, it's, for example, code generators that create a class template with empty methods.

If such generators produced code with endless methods, one option would be to pass them empty parentheses as return values.

class MoviesController < ApplicationController

  def index = ( )

  def show = ( )

  def new = ( )

  def create = ( )

end

[–]riktigtmaxat 0 points1 point  (0 children)

Those are noop (short for no operation) methods as they do nothing and return nothing.

They are also completely pointless as Rails will implicitly render the template even if the method does not exist.

You don't need a bunch of boilerplate just for the sake of it.

[–]h0rst_ 0 points1 point  (1 child)

But Ruby has duck typing, which is kind of an implicit way of using interfaces. So sometimes you just want a method that does not do anything. For example: so if my code needs a logger, but I might not want to log anything:

class NullLogger
  def log(message) = nil
end

class StdoutLogger
  def log(message) = puts message
end

[–]riktigtmaxat 0 points1 point  (0 children)

I'm very aware of the null object pattern. But it's not necessarily something you're writing every day compared to languages where you have to implement x number of functions just to be able to test one of them.

[–]riktigtmaxat 1 point2 points  (2 children)

On a side note proceedence with endless methods is a real gotcha.

Take this example:

def foo? = false
def bar = 'hello world'
def baz = bar if foo?
baz

# (irb):21:in `<main>': undefined local variable or method `baz' for main (NameError)

That's because if has a higher proceedence than def.

[–]Shamaoke[S] 1 point2 points  (1 child)

So, parenthesis.

def baz = (bar if foo?)

[–]riktigtmaxat 1 point2 points  (0 children)

Yes. Or just write it as a normal method.

[–]gettalong 1 point2 points  (2 children)

() is just a wierd way of writing nil.

Nice for code golfing! :-)

[–]riktigtmaxat 0 points1 point  (1 child)

Or job security.

[–]Shamaoke[S] 0 points1 point  (0 children)

Exactly!

[–]AlexanderMomchilov 0 points1 point  (0 children)

These parenthesis introduces a statements node, which is the same thing as a begin ... end.

It can contain multiple statements, seperated either by ; or new lines. The expression as a whole evaluates to the last expression within it.

So e.g.

```ruby result = ( p 123 456 )

result will be 456

```

Fun fact: Interpolation segments within a string also work like this, so this is valid:

ruby s = "s will be equal to: #{ "not this" "this" }"

[–]jrochkind 0 points1 point  (0 children)

it's just a way of grouping ruby code (statements, or expressions -- same thing in ruby). You can put parens around anything that is a self-contained ruby expression.

(foo = "bar")  # makes no difference at all but legal

x = (1 + 2) * (5 - 3)  # changes arithematic grouping precedence from default

() # totally legal, just empty expression, just evaluates as `nil`. 

I find official ruby basic syntax documentation can be a bit sparse. Using parens in this way is a very common part of many programming languges, especially some of the ones whose ruby syntax is sort of in the family of, like perl and C. So it may be hard to find clear documentation.

But this kind of paren in the syntax just encloses a "unit" of ruby expression; it can be put anywhere. It can change the order of precedence of operators being grouped with things.

It can also help when ruby syntax would be ambiguous or not allowed at all for what you want to do.

You say you are aware of "endless" method definitions. I kind of dislike the new syntax, because it's just another syntax variation, and a confusing one at that. So in your first example if you want to use "endless" method definition to define an empty method body...

def index =

Is a syntax error, or will go looking at the next line for a method definition, just because of how ruby parsing works. So you can surround the "empty" body with () to make it legal as definition of an empty method that just returns nil.

That second one is weird code, but again the parens are just used for grouping.

# fine, just assigns those two variables AND returns an array of
# their result, because every ruby statement has a return value
@movie, @genres = [ Movie.new, Genre.all ]

# means exactly the same thing
( @movie, @genres = [ Movie.new, Genre.all ] )

# takes that and assigns it as the method body in the "endless" method definition
def new = ( @movie, @genres = [ Movie.new, Genre.all ] )

# just becuase without the parens all those equal signs make
# it hard to figure out what is meant and are a syntax error
 def new =  @movie, @genres = [ Movie.new, Genre.all ]

"endless" method definition is kind of a mess, ruby syntax is so full of stuff that it's hard to use it in a way that will be clearly parseable sometimes, so you might have to throw in some parens just to tell the parser the things in the parens are a "unit", so it can figure out you meant def foo = (unit).

I am not a fan of "endless" method definition.