Project

General

Profile

Actions

Feature #17942

open

Add a `initialize(public @a, private @b)` shortcut syntax for defining public/private accessors for instance vars as part of constructor

Added by TylerRick (Tyler Rick) almost 3 years ago. Updated over 1 year ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:104213]

Description

This proposal builds on the proposed initialize(@a, @b) instance var assignment shortcut syntax described in #15192.

  1. It allows you to add an optional public/protected/private modifier before any instance var parameter. Doing so automatically defines accessor methods (with the given access modifier; equivalent to attr_accessor inside of a public/protected/private block) for the instance var it precedes.
  2. If the visibility modifier is omitted, then it defaults to automatically no getter/setter methods for that instance var (it only does an assignment of that already-private instance var).

Parameter properties in TypeScript language

This is inspired by TypeScript's constructor(public a, private b) syntax, which allows you to write this (REPL):

class Foo {
    constructor(public a:number, public b:number, private c:number) {
    }
}

instead of this:

class Foo {
    constructor(a, b, c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}

(The public/private access modifiers actually disappear in the transpiled JavaScript code because it's only the TypeScript compiler that enforces those access modifiers, and it does so at compile time rather than at run time.)

Further reading:

Differences from TypeScript

I propose adding a similar feature to Ruby, but with following differences from TypeScript:

  1. Use @a instead of bare a. This makes it much clearer that you are assigning directly to instance variables instead of to locals.

    • Rationale: The @ is actually part of the instance variable name, and is inseparable from it. (This is also consistent with how the # is part of the name itself in JavaScript's (Private instance fields).)
    • (public a would be a syntax error because there's no such thing as access modifiers for locals. Okay, I guess there's no such thing as access modifiers for instance vars either, which is why...)
  2. Make the syntax for assigning to instance vars (@a) (the proposal in #15192) and defining accessor methods for those instance vars (public/private) separate/distinct.

    • In other words, rather than make the public/private keywords a required part of the syntax like it is for TypeScript parameter properties, you could omit the modifier and it would still do the instance var _assignment*.
    • The public/private access modifiers be an additional (optional) shortcut when you want to add an accessor method in addition to doing an assignment .
    • Unlike Java and TypeScript where you can add access modifiers to instance variables, in Ruby, public/private can't be applied to instance variables (direct access is only possible from within the instance). So if we're going to allow a public/private modifier here at all, They must refer to methods, specifically accessor methods for those instance variables.
  3. Keep it private by default (which of course @a by itself implies—it is private unless you add a public accessor).

    • (Rather than make it public by default like it is in TypeScript.)
    • Keeping instance variables completely private is probably what people will want most of the time, and we should optimize the ergonomics for the most common case.
    • Private is a safer default, and should be assumed unless you explicitly ask for a public accessor to be added.
    • I bet TypeScript made the public the default mostly to be consistent with JavaScript (which TypeScript compiles to): JavaScript (along with other languages like Java) allows direct access (no getter/setter neede) to instance properties/variables from objects outside the instance. JavaScript doesn't even have a way to make instance variables private (but hopefully will soon with this proposal to add #a syntax for private properties).

So this:

class Thing
  def initialize(public @a, public @b, @c)
  end
end

would be equivalent to this:

class Thing
  attr_accessor :a, :b

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

How is initialize(private @a) different from initialize(@a)?

Even though @a by itself is already private...

  1. This defines a private accessor for that instance var, which lets you write self.a = instead of @a = (if you want).
  2. Having a concise way to do that is helpful, for example if you want to make it a matter of practice/policy to only set an instance variable by going through its setter method. (See discussion here.)

Why not just use initialize(private @a) to be consistent with TypeScript spec?

  • TypeScript's public/private is not standard JavaScript. In fact, if the private methods/fields proposal had existed when TypeScript added parameter properties, I'd like to think that they might have actually made use of the new #b syntax and gone with a terser syntax like constructor(public a, #b) instead of ``constructor(public a, private b)`.

Upsides of this proposal

  1. Removes even more boilerplate (all those attr_accessor lines), much of the time

Downsides of this proposal

  1. Only provides a way to define both getter and setter at once. Doesn't provide a way to just define a getter and not a setter, for example.
    • Doesn't seem like a big deal, however. You can just not use this feature and define the getter with attr_reader :a instead. Or define private getter/setter with private @a and then override with attr_reader :a to add a public getter (while keeping the private setter).

Related issues 1 (1 open0 closed)

Related to Ruby master - Feature #5825: Sweet instance var assignment in the object initializerAssignedmatz (Yukihiro Matsumoto)Actions
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0