Feature #17942
openAdd a `initialize(public @a, private @b)` shortcut syntax for defining public/private accessors for instance vars as part of constructor
Description
This proposal builds on the proposed initialize(@a, @b)
instance var assignment shortcut syntax described in #15192.
- 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 toattr_accessor
inside of apublic
/protected
/private
block) for the instance var it precedes. - 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:
- https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties
- https://basarat.gitbook.io/typescript/future-javascript/classes#define-using-constructor
- https://kendaleiv.com/typescript-constructor-assignment-public-and-private-keywords/
Differences from TypeScript¶
I propose adding a similar feature to Ruby, but with following differences from TypeScript:
-
Use
@a
instead of barea
. 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...)
- Rationale: The
-
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 apublic
/private
modifier here at all, They must refer to methods, specifically accessor methods for those instance variables.
- In other words, rather than make the
-
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).
- (Rather than make it
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...
- This defines a private accessor for that instance var, which lets you write
self.a =
instead of@a =
(if you want). - 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 likeconstructor(public a, #b)
instead of ``constructor(public a, private b)`.
Upsides of this proposal¶
- Removes even more boilerplate (all those
attr_accessor
lines), much of the time
Downsides of this proposal¶
- 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 withprivate @a
and then override withattr_reader :a
to add a public getter (while keeping the private setter).
- Doesn't seem like a big deal, however. You can just not use this feature and define the getter with