From: "nobu (Nobuyoshi Nakada)" <noreply@ruby-lang.org>
To: ruby-core@ml.ruby-lang.org
Subject: [ruby-core:111196] [Ruby master Feature#17942] Add a `initialize(public @a, private @b)` shortcut syntax for defining public/private accessors for instance vars as part of constructor
Date: Sun, 04 Dec 2022 03:54:39 +0000 (UTC) [thread overview]
Message-ID: <redmine.journal-100479.20221204035438.125@ruby-lang.org> (raw)
In-Reply-To: redmine.issue-17942.20210609045504.125@ruby-lang.org
Issue #17942 has been updated by nobu (Nobuyoshi Nakada).
This means you want only `initialize` method to be parsed specially?
And when bypassing this method, e.g., `Marshal.load`, no accessor will be defined?
----------------------------------------
Feature #17942: Add a `initialize(public @a, private @b)` shortcut syntax for defining public/private accessors for instance vars as part of constructor
https://bugs.ruby-lang.org/issues/17942#change-100479
* Author: TylerRick (Tyler Rick)
* Status: Open
* Priority: Normal
----------------------------------------
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](https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8BQ0PWPAdhALgE4Cuw+8hAFAA7EBGIAlsNGAFw7EC2dApoQBpotBs2h0O3PoOGFGANzD5eWST34BKFOkwBfVPqA)):
```js
class Foo {
constructor(public a:number, public b:number, private c:number) {
}
}
```
instead of this:
```js
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:
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)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields#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...)
1. 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](https://www.typescriptlang.org/docs/handbook/2/classes.html#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.
1. 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](https://github.com/tc39/proposal-private-methods) to add `#a` syntax for private properties).
So this:
```ruby
class Thing
def initialize(public @a, public @b, @c)
end
end
```
would be equivalent to this:
```ruby
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](https://stackoverflow.com/questions/25571642/ruby-private-and-public-accessors).)
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](https://github.com/tc39/proposal-private-methods) had existed when TypeScript added [parameter properties](https://www.typescriptlang.org/docs/handbook/2/classes.html#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).
--
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/
next prev parent reply other threads:[~2022-12-04 3:54 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-06-09 4:55 [ruby-core:104213] [Ruby master Feature#17942] Add a `initialize(public @a, private @b)` shortcut syntax for defining public/private accessors for instance vars tyler
2021-06-09 5:25 ` [ruby-core:104214] [Ruby master Feature#17942] Add a `initialize(public @a, private @b)` shortcut syntax for defining public/private accessors for instance vars as part of constructor mame
2021-06-09 5:32 ` [ruby-core:104215] " merch-redmine
2021-12-13 0:43 ` [ruby-core:106627] " LevLukomskyi (Lev Lukomskyi)
2022-12-04 3:54 ` nobu (Nobuyoshi Nakada) [this message]
2022-12-04 6:44 ` [ruby-core:111197] " sawa (Tsuyoshi Sawada)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-list from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.ruby-lang.org/en/community/mailing-lists/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=redmine.journal-100479.20221204035438.125@ruby-lang.org \
--to=ruby-core@ruby-lang.org \
--cc=ruby-core@ml.ruby-lang.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).