Project

General

Profile

Feature #15192

Introduce a new "shortcut assigning" syntax to convenient setup instance variables

Added by jjyr (Jinyang Jiang) 9 months ago. Updated 6 months ago.

Status:
Open
Priority:
Normal
Target version:
-
[ruby-core:89243]

Description

Motivation:

Introduce a new syntax for convenient setup instance variables for objects.

The problem:

Currently, setup instance variables in Ruby is too verbose.
Basically, we need to write the meaningless assigning code again and again to assign variables

class Person
  def initialize(name:, age:, gender:, country:)
    @name = name
    @age = age
    @gender = gender
    @country = country
  end
end


# we can use Struct to avoiding this

Person = Struct.new(:name, :age, :gender, :country, keyword_init: true)

# let's see a real-world case, which can't use Struct to describe an initializing process, from https://github.com/ciri-ethereum/ciri/blob/748985ccf7a620a2e480706a5a6b38f56409d487/lib/ciri/devp2p/server.rb#L54
# Because we want to do something more than just assigning instance variables

class Server
      def initialize(private_key:, protocol_manage:, bootstrap_nodes: [],
                     node_name: 'Ciri', tcp_host: '127.0.0.1', tcp_port: 33033)
        @private_key = private_key
        @node_name = node_name
        @bootstrap_nodes = bootstrap_nodes
        @protocol_manage = protocol_manage
        server_node_id = NodeID.new(@private_key)
        caps = [Cap.new(name: 'eth', version: 63)]
        @handshake = ProtocolHandshake.new(version: BASE_PROTOCOL_VERSION, name: @node_name, id: server_node_id.id, caps: caps)
        @tcp_host = tcp_host
        @tcp_port = tcp_port
        @dial = Dial.new(bootstrap_nodes: bootstrap_nodes, private_key: private_key, handshake: @handshake)
        @network_state = NetworkState.new(protocol_manage)
        @dial_scheduler = DialScheduler.new(@network_state, @dial)
      end
end


# Introduce a new "shortcut assigning" syntax for convenient setup

class Person
  # use @ prefix to describe instance variables.
  def initialize(@name:, @age:, @gender:, @country:)
  end

  # equal to
  def initialize2(name:, age:, gender:, country:)
    @name = name
    @age = age
    @gender = gender
    @country = country
  end

  # it should also work on position style arguments
  def initialize2(@name, @age, @gender, @country)
  end
end

# Our real-world case can be rewritten as below
class Server
      def initialize(@private_key:, @protocol_manage:, @bootstrap_nodes: [],
                     @node_name: 'Ciri', @tcp_host: '127.0.0.1', @tcp_port: 33033)
        server_node_id = NodeID.new(@private_key)
        caps = [Cap.new(name: 'eth', version: 63)]
        @handshake = ProtocolHandshake.new(version: BASE_PROTOCOL_VERSION, name: @node_name, id: server_node_id.id, caps: caps)
        @dial = Dial.new(bootstrap_nodes: @bootstrap_nodes, private_key: @private_key, handshake: @handshake)
        @network_state = NetworkState.new(@protocol_manage)
        @dial_scheduler = DialScheduler.new(@network_state, @dial)
      end
end

# consider to keep consistency, this "shortcut assigning" syntax should work for non-initialize methods
class Foo
  def bar(@still_works)
    p @still_works
  end
end

Related issues

Related to Ruby trunk - Feature #12820: Shorter syntax for assigning a method argument to an instance variableRejectedActions
Related to Ruby trunk - Feature #5825: Sweet instance var assignment in the object initializerAssignedActions

History

#1

Updated by matz (Yukihiro Matsumoto) 9 months ago

  • Related to Feature #12820: Shorter syntax for assigning a method argument to an instance variable added
#2

Updated by jjyr (Jinyang Jiang) 9 months ago

  • Description updated (diff)

Updated by shyouhei (Shyouhei Urabe) 9 months ago

Matz thinks this is handy only when you write #initialize, which only is not worth adding a new syntax for methods in general. Do you have any situations other than #initialize where this is useful?

Updated by shyouhei (Shyouhei Urabe) 9 months ago

This isn't Matz's but my experience. When "we want to do something more than just assigning instance variables", that "something" tends to include cancellation of creating new object -- maybe because we are returning a cached instance, or because we are raising an exception. In order to properly handle such situations #initialize tends to be too late to "do something". When I write a complex constructor that tends to happen inside of .new directly, and #initialize eventually becomes a series of instance variable assignments. One of such example is: https://github.com/shyouhei/xmp2assert/blob/master/lib/xmp2assert/quasifile.rb

Updated by shevegen (Robert A. Heiler) 9 months ago

I personally tend to use (slower) setter-methods rather than initialization within initialize()
itself. Normally only for somewhat larger classes though; for small classes that do not do much,
I often don't bother writing setter methods; and they often don't need setter methods.

So:

def initialize(*i)
  reset # <- a method to setup the default state for the objects
  set_commandline_arguments(i) # <- and often keeping track of the commandline arguments passed in via ARGV
  # continue with the assignments via method calls, often in a method called run() that I like to use and call here
end

Then again I don't think that my ruby code/style is very commonly used either. :)

We also saw the comparison to Struct in ruby. While I think structs are great, in actual
practice I also rarely use structs. Oddly enough, I tend to really just write out the
definitions of methods on my own, much more often than using any of the attr* "shortcuts"
too.

I understand the shortcut idea behind the proposal, e. g. to get rid of some lines
of code that is used for assignment to instance variable.

Personally I have no strong opinion either way since I can understand both arguments but
I think that aside from what shyouhei wrote, matz is also not too fond of the syntax.

In the discussion in the other thread in particular he did not like the:

def initialize(@foo, @bar)

notation. So I think the additional problem here is clarity of intent and consistency
through idiomatic ruby. People will also of course make use of it a lot in their own
code (once something is made possible, people will use it) and in this case I am not
entirely sure whether that change in particular would be very good, just syntax-wise
alone. (I also tend to watch crystal, which uses a syntax similar to this,
but crystal also diverged in some strange ways, in my opinion, syntax-wise - e. g
"abstract" classes or macros, which I find very strange.)

  • You (or others) could try to re-evaluate the proposals at some later time in the future since sometimes other parts of ruby or the usage may change (for example, see the change that allows unicode used as a constant/name of classes, which was not possible before not that long ago).

Updated by marcandre (Marc-Andre Lafortune) 9 months ago

  • Assignee set to matz (Yukihiro Matsumoto)

This has been requested a lot :-)

I still feel like it would be very practical, introduces no incompatibility, and is also very intuitive (at least to me). I feel that no Rubyist would have trouble learning what def initialize(@something, @some_option: nil) would mean; it would take about 5 seconds.

#7

Updated by mame (Yusuke Endoh) 9 months ago

  • Related to Feature #5825: Sweet instance var assignment in the object initializer added

Updated by mame (Yusuke Endoh) 9 months ago

At the previous deverlopers' meeting (Sep.), I brought #5825 up for discussion. Matz said that he still dislikes this syntax. (Personally I like it.)

Updated by jjyr (Jinyang Jiang) 9 months ago

I am surprised this syntax has been repeatedly requested and rejected since 7 years ago.

Write assigning code maybe is not a big problem to developers, but cause Ruby has Struct to solve the assigning problem, so at least it is a problem worth to solve.

But Struct is so limited, even default values require rewrite initialize method to implement.

IMO this syntax is useful and simple enough.

Updated by matz (Yukihiro Matsumoto) 9 months ago

I still don't agree with the proposed syntax.
The option I can accept is something like (as is not the only option):

def initialize(name: as @name, age: as @age)
  ...
end

Matz.

Updated by shevegen (Robert A. Heiler) 9 months ago

marcandre wrote

I feel that no Rubyist would have trouble learning what def
initialize(@something, @some_option: nil) would mean; it would
take about 5 seconds.

I think this is a bit difficult to say, because we can always reason that one small feature that
is added, is just one more tiny little baby step.

But say that you combine lots of baby steps ... all very simple on their own, but together they
add to the complexity or spaghetti design of a language. Like perhaps PHP.

In Ruby we can also omit () in method definitions like:

def initialize @a, @b: nil, @c: { cat: :tom }

I am not sure if this is an improvement. To me it does not seem very pretty. Of course I am biased since
I also prefer () in method definitions if they have arguments; although I think it is fine that ruby does
not mind omitting the (). For my brain, I like the () for visual separation. I am not sure I like the @foo
syntax that much on the left hand side. What about syntax like @a = @b? I mean, I assume we assign the
value of @b towards @a ... but ideally I'd prefer to not want to see syntax like that in method definitions;
or having to look closely for : { and @. May be a matter of personal preference too.

Once added, it would also be harder to remove the syntax again, in the sense of people who may like
syntax (like @@ class variables) so I am not entirely sure if it's a great idea. But I don't want to
be too discouraging since it is mostly just a difference of opinions.

Before I write too much, I'll finish by saying that I personally am not that fully convinced that it is
such a good idea to have the proposed feature associated with that particular syntax, even though it is
repeated every now and then (but so were ideas such as removing Symbols; I think jeremy evans wrote good
comments about that other situation). I am also not that convinced that a different syntax will be of
more help, either. Perhaps I am becoming more conservative as I become older. (One problem I see with
a longer syntax is that some of the advantage is lost by short-cutting assignment.)

On a side note, since crystal has that syntax and functionality, people could actually try out crystal and
see how that goes in regards to the "shortcut assignment" after a few months. :) (I mean really mostly
unbiased people here, though ideally those who already know ruby, even though that leads to some bias; and
can then compare to crystal, syntax-wise. Personally I feel that the ruby syntax is better than crystal's
syntax, due to various reason, but I don't want to digress here towards another programming language too
much.)

Updated by jsc (Justin Collins) 6 months ago

jjyr (Jinyang Jiang) wrote:

I am surprised this syntax has been repeatedly requested and rejected since 7 years ago.

Write assigning code maybe is not a big problem to developers, but cause Ruby has Struct to solve the assigning problem, so at least it is a problem worth to solve.

But Struct is so limited, even default values require rewrite initialize method to implement.

IMO this syntax is useful and simple enough.

As someone who has been writing Ruby for over 10 years, this syntax is exactly that I would like.

I grow really tired of writing

def initialize(a, b, c)
  @a = a
  @b = b
  @c = c
end

This would be perfect:

def initialize(@a, @b, @c)
end

I'm a little bit sad Matz is against this syntax, as it seems so natural to me.

Also available in: Atom PDF