https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112019-08-24T06:55:07ZRuby Issue Tracking SystemRuby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=809682019-08-24T06:55:07ZEregon (Benoit Daloze)
<ul></ul><p>This sounds interesting to me.<br>
What would a simple implementation of Struct::Value.new look like in Ruby code?<br>
I'm not quite sure what the available API is since it's all described as Struct - some methods.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=809692019-08-24T10:17:21Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/772">@Eregon (Benoit Daloze)</a>, here is rendered version of class' docs: <a href="https://zverok.github.io/ruby-rdoc/Struct-Value.html" class="external">https://zverok.github.io/ruby-rdoc/Struct-Value.html</a></p>
<p>Basically, it is what is said on the tin: like Struct, just leaner.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=809702019-08-24T10:37:04Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><blockquote>
<p>What would a simple implementation of <code>Struct::Value.new</code> look like in Ruby code?</p>
</blockquote>
<p>Oh, I've probably answered the wrong question... But I am not quite sure I understand yours.</p>
<p>Theoretically, it is just something like this (ignoring the fact that <code>Struct</code>s implementation has optimized storage and other tricks, and any input validation):</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Struct::Value</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">new</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="ss">keyword_init: </span><span class="kp">false</span><span class="p">)</span>
<span class="nb">name</span><span class="p">,</span> <span class="o">*</span><span class="n">members</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">String</span><span class="p">)</span> <span class="p">?</span> <span class="n">args</span> <span class="p">:</span> <span class="p">[</span><span class="kp">nil</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">]</span>
<span class="no">Class</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="k">do</span>
<span class="vi">@members</span> <span class="o">=</span> <span class="n">members</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">new</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="n">allocate</span><span class="p">.</span><span class="nf">tap</span> <span class="p">{</span> <span class="o">|</span><span class="n">o</span><span class="o">|</span> <span class="n">o</span><span class="p">.</span><span class="nf">__send__</span><span class="p">(</span><span class="ss">:initialize</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="n">members</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">define_method</span><span class="p">(</span><span class="n">m</span><span class="p">)</span> <span class="p">{</span> <span class="nb">instance_variable_get</span><span class="p">(</span><span class="s2">"@</span><span class="si">#{</span><span class="n">m</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="p">}}</span>
<span class="k">end</span><span class="p">.</span><span class="nf">tap</span> <span class="p">{</span> <span class="o">|</span><span class="n">cls</span><span class="o">|</span> <span class="nb">const_set</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">cls</span><span class="p">)</span> <span class="k">if</span> <span class="nb">name</span> <span class="p">}</span>
<span class="k">end</span>
<span class="c1"># ....</span>
</code></pre>
<p>So, (if that's what you've asking) it produces object of different class, <code>Struct::Value</code>, unrelated to <code>Struct</code>, but sharing most of the implementation.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812632019-08-29T08:01:58Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Feedback</i></li></ul><p>The typical solution is <code>Struct.new(...).freeze</code>. This doesn't require any enhancement. The other option is <code>Struct.new(..., immutable: false)</code>. It looks simpler than the proposed <code>Struct::Value</code>.</p>
<p>Matz.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812652019-08-29T09:35:30Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> Sorry for not sharing more detailed reasoning which led to the current proposal (I explained the "final reasons" in its text, but it is too terse).</p>
<p>So, it went as following:</p>
<p>1. First, I really wanted just <code>Struct.new(..., immutable: false)</code> (and even experimented for some time with a private monkey-patch, doing just that)</p>
<p>2. But in fact, to be a proper convenient "value object", it is also bad for container to mimic Enumerable, and especially bad to implement <code>to_a</code>. Simple example:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Result</span> <span class="o">=</span> <span class="no">Struct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:success</span><span class="p">,</span> <span class="ss">:content</span><span class="p">)</span>
<span class="c1"># Now, imagine that other code assumes `data` could be either Result, or [Result, Result, Result]</span>
<span class="c1"># So, ...</span>
<span class="n">data</span> <span class="o">=</span> <span class="no">Result</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="kp">true</span><span class="p">,</span> <span class="s1">'it is awesome'</span><span class="p">)</span>
<span class="no">Array</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1"># => expected [Result(true, 'it is awesome')], got [true, 'it is awesome']</span>
<span class="c1"># or...</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
<span class="nb">p</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span>
<span class="k">end</span>
<span class="n">foo</span><span class="p">(</span><span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="c1"># => expected [Result(true, 'it is awesome'), nil], got [true, 'it is awesome']</span>
</code></pre>
<p>3. And generally, some random value object "duck typing" itself as a collection seems not really appropriate.</p>
<p>4. The same, I believe, is related to supporting <code>[:foo]</code> and <code>['foo']</code> accessors: convenient for "general content object" that <code>Struct</code> is, but for "just value" it could seem an unnecessary expansion of the interface.</p>
<p>5. Finally, empty-member <code>Value</code> is allowed, while empty-member <code>Struct</code> somehow does not (I don't know if it is by design or just a bug, as I am mentioning above, <code>Struct.new('Name')</code> IS allowed, but <code>Struct.new</code> is NOT).</p>
<p>So, considering all the points above, it could be either <em>multiple</em> settings: <code>immutable: true, enumerable: false, hash_accessors: false</code> (the (5) probably could be just fixed for Struct, too) -- which is not that convenient if you are defining 3-5 types in a row, and requires some cognitive efforts both from writer (errm, what options did I used last time to set it as a "good" value object?..) and reader (ugh, what's this struct with so many settings?..).</p>
<p>So I eventually decided to propose going another way.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812692019-08-29T12:50:21ZDan0042 (Daniel DeLorme)
<ul></ul><p>If I understand correctly, the idea is to have <code>X=Struct::Value.new(:x,:y,:z)</code> which is strictly equivalent to</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">X</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="kp">nil</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="kp">nil</span><span class="p">,</span> <span class="n">z</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span>
<span class="vi">@x</span><span class="p">,</span><span class="vi">@y</span><span class="p">,</span><span class="vi">@z</span> <span class="o">=</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">,</span><span class="n">z</span>
<span class="k">end</span>
<span class="nb">attr_reader</span> <span class="ss">:x</span><span class="p">,</span> <span class="ss">:y</span><span class="p">,</span> <span class="ss">:z</span>
<span class="c1">#and other methods based on x,y,z attributes:</span>
<span class="c1">#def ==(other)</span>
<span class="c1">#def eql?(other)</span>
<span class="c1">#def hash </span>
<span class="k">end</span>
</code></pre>
<p>Or was there some nuance I didn't catch?</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812702019-08-29T12:55:35Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/11019">@Dan0042 (Daniel DeLorme)</a> you are (probably) missing <code>#inspect</code>, <code>#==</code>, <code>#eql?</code>, <code>#hash</code>, <code>#to_h</code> and a bunch of other methods that are pretty trivial, but also important for the "value object".</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812712019-08-29T14:14:43Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>I couldn't understand what is "value object", and I found: <a href="https://martinfowler.com/bliki/ValueObject.html" class="external">https://martinfowler.com/bliki/ValueObject.html</a><br>
Please do not assume that everybody knows such an essay ;-)<br>
No one pointed out the article during the developer's meeting, so we cannot understand what you want.</p>
<p>I have some comments:</p>
<ul>
<li>Why don't you start it with a gem? It may be useful for your case, but I'm not sure if it is useful for many people so that it deserves a built-in feature. And the design of <code>Struct::Value</code> is not clear to me (e.g., non-Enumerable is trade off; is it really useful for many cases?).If your gem become so popular, we can import it as a built-in feature.</li>
<li>The behavior of <code>Struct::Value</code> is too different from <code>Struct</code>. Another class name (like <code>ValueClass</code> or <code>NamedTuple</code> or what not) looks more suitable.</li>
<li>What you (first) want is called "structural equality" in other languages (OCaml, F#, C#, TypeScript, Kotlin, as far as I know). Also it resembles "namedtuple" in Python. You may want to study them.</li>
</ul>
<p>BTW, I understand the motivation of the proposal. I want "structural equality" in Ruby. Personally, I often write:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Point3D</span>
<span class="kp">include</span> <span class="no">StructuralEquality</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span>
<span class="vi">@x</span><span class="p">,</span> <span class="vi">@y</span><span class="p">,</span> <span class="vi">@z</span> <span class="o">=</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">foo1</span> <span class="o">=</span> <span class="no">Point3D</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="n">foo2</span> <span class="o">=</span> <span class="no">Point3D</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="nb">p</span> <span class="n">foo1</span> <span class="o">==</span> <span class="n">foo2</span> <span class="c1">#=> true</span>
<span class="n">h</span> <span class="o">=</span> <span class="p">{</span> <span class="n">foo1</span> <span class="o">=></span> <span class="s2">"ok"</span> <span class="p">}</span>
<span class="nb">p</span> <span class="n">h</span><span class="p">[</span><span class="n">foo2</span><span class="p">]</span> <span class="c1">#=> "ok"</span>
</code></pre>
<p>(The definition of <code>StructuralEquality</code> is here: <a href="https://github.com/mame/ruby-type-profiler/blob/436a10787fc74db47a8b2e9db995aa6ef7c16311/lib/type-profiler/utils.rb#L8-L31" class="external">https://github.com/mame/ruby-type-profiler/blob/436a10787fc74db47a8b2e9db995aa6ef7c16311/lib/type-profiler/utils.rb#L8-L31</a> )</p>
<p>But, I'm unsure if it deserves a built-in feature.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812722019-08-29T14:46:50Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/18">@mame (Yusuke Endoh)</a> I understand your concerns. I'll update the description today or tomorrow to include all the terminology and detailed rationale behind the proposal.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812772019-08-29T20:33:03Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/81277/diff?detail_id=54961">diff</a>)</li></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/18">@mame (Yusuke Endoh)</a>, <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a>, I updated the description, tried to include a proper rationale for every design decision made.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812792019-08-30T00:09:44Znaruse (Yui NARUSE)naruse@airemix.jp
<ul></ul><blockquote>
<p>I believe that concept is that simple, that nobody will even try to use a gem for representing it with, unless the framework/library used already provides one.</p>
</blockquote>
<p>I'm using immutable_struct.gem.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812852019-08-30T07:05:31Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/5">@naruse (Yui NARUSE)</a> Of course, there <em>are</em> several good gems with more-or-less similar functionality. But, from the hard experience, large codebases tend to look with great caution on the "small utility" gems to avoid dependency bloat and tend to depend only on large non-trivial functionality. But if it is a part of the language core, it is beneficial for everyone.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812892019-08-30T13:15:27ZDan0042 (Daniel DeLorme)
<ul></ul><p>Question: you say "Doesn't think of itself as almost hash" but at the same time you say it should have <code>to_h</code>. Isn't that a contradiction? What exactly are you looking for?</p>
<p>Naming suggestion: BasicStruct (in parallel to Object and BasicObject)</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812912019-08-30T14:55:45Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/11019">@Dan0042 (Daniel DeLorme)</a></p>
<blockquote>
<p>Question: you say "Doesn't think of itself as almost hash" but at the same time you say it should have <code>to_h</code>. Isn't that a contradiction?</p>
</blockquote>
<p>Nope. An object that has <code>to_h</code> is not "something hash-like", it is just "something that can be represented as a Hash" (for example, to save it to JSON). The same way that all Ruby objects respond to <code>to_s</code> but that doesn't make them "something like String".</p>
<p>But "mimicking" some of the Hash API (with <code>[]</code> and <code>values</code> and <code>values_at</code>) makes the object responsibility less focused.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=812962019-08-30T18:26:07ZDan0042 (Daniel DeLorme)
<ul></ul><p>Ok I see what you meant. BTW Struct#values_at follows the Array rather than Hash API, because Struct also thinks of itself as a tuple :-/</p>
<pre><code> Struct.new(:x).new(42).values_at(0) #=> [42]
Struct.new(:x).new(42).values_at(:x) #=> TypeError
</code></pre> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=813762019-09-03T15:56:41Zpalkan (Vladimir Dementyev)dementiev.vm@gmail.com
<ul></ul><p>zverok (Victor Shepelev) wrote:</p>
<blockquote>
<p><strong>Why not a gem?</strong></p>
<ul>
<li>I believe that concept is that simple, that nobody <em>will even try</em> to use a gem for representing it with, unless the framework/library used already provides one.</li>
</ul>
</blockquote>
<p>If a concept is popular and there is a well-designed gem that implements it then people use it. For example, a lot of people use <code>dry-initializer</code>, which is also dead-simple and provides the functionality that could easily be implemented from scratch (and even could be useful as a part of the standard library).</p>
<p>If there is still no such a gem then there is no enough demand for the feature itself.</p>
<p>So, why pushing it to the core?</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=814522019-09-07T19:56:35Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/14299">@palkan (Vladimir Dementyev)</a> I have a strong <strong>feeling</strong> of "value object notion should be a part of the language, not an externally implemented optional thingy", but it is not easy to rationalize it.</p>
<p>Maybe the thing is that "value object" is a notion most useful at API borders (and it is not just utility usability, but conceptual one, "our API accepts this and that type of value objects and return this and that type of them"). And I believe "this is a concept of the language" makes a huge difference in using, documenting and explaining your APIs, compared to "well, we use that external gem, developed by some random dude, to bloat our depenencies, because it is <em>tinsy bit more convenient.</em>"</p>
<p>In other words, I am proposing to introduce the concept, not implementation.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=823742019-10-30T02:46:37ZDan0042 (Daniel DeLorme)
<ul></ul><p>zverok (Victor Shepelev) wrote:</p>
<blockquote>
<p>So, considering all the points above, it could be either <em>multiple</em> settings: <code>immutable: true, enumerable: false, hash_accessors: false</code></p>
</blockquote>
<p>I think that's a great idea. That way it's possible for everyone to mix and match the behavior they want in their structs. For example let say I want a struct to be mutable but not enumerable (because of the <code>Array(mystruct)</code> bug shown above), the <code>Struct::Value</code> approach doesn't work. If you find yourself always repeating the same options, it's trivial to write your own <code>ValueStruct</code> helper function.</p>
<p>Or maybe Struct could include a few built-in helpers</p>
<ul>
<li>Struct.Value => immutable: true, enumerable: false, hash_accessors: false</li>
<li>Struct.Basic => immutable: false, enumerable: false, hash_accessors: false</li>
<li>Struct.Tuple => immutable: false, enumerable: true, hash_accessors: false</li>
</ul> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=828582019-11-28T08:40:04Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><p>I like the idea of helpers in <a href="https://bugs.ruby-lang.org/issues/16122#note-18" class="external">https://bugs.ruby-lang.org/issues/16122#note-18</a>.<br>
We need to discuss further the combination of attributes (immutable, enumerable, etc.)</p>
<p>Matz.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=849582020-04-08T09:03:35Zk0kubun (Takashi Kokubun)takashikkbn@gmail.com
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-6 priority-4 priority-default closed" href="/issues/16769">Feature #16769</a>: Struct.new(..., immutable: true)</i> added</li></ul> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=850112020-04-10T12:09:42ZEregon (Benoit Daloze)
<ul></ul><p>We already have <code>Struct.new(..., keyword_init: true)</code>.<br>
I think having other variants like <code>immutable: true, enumerable: false, hash_accessors: false</code> is consistent and flexible.</p>
<p>Having only the helpers like <code>Struct.Value</code> would restrict to a few combinations, and still need to handle <code>keyword_init:</code>.</p>
<p>I think <code>Struct::Value.new</code> could be a nice helper for <code>immutable: true, enumerable: false, hash_accessors: false</code>.<br>
The others seem more specific, less common to use, and I would rather let people choose the configuration they want with keyword arguments for <code>Struct.new()</code>.</p>
<p>Implementation-wise and conceptually, I think it's also nicer if <code>Struct::Value.new(...)</code> is implemented as as <code>Struct.new(..., immutable: true, enumerable: false, hash_accessors: false)</code>.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=850122020-04-10T12:13:45ZEregon (Benoit Daloze)
<ul></ul><p>In my view, <code>Struct.new</code> is the perfect example to generate a custom class in Ruby.<br>
I think making it customizable with new keyword arguments is both elegant and simple.</p>
<p>OTOH I think having N "subclasses" with different behaviors invites to confusion about what differs between them and enforces duplication in implementation code.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=958712022-01-11T07:29:55Zko1 (Koichi Sasada)
<ul></ul><p>I don't use <code>Enumerable</code> features of <code>Struct</code> classes, but I don't have any trouble by having <code>Enumerable</code>.<br>
Why do you want to remove <code>Enumerable</code> features?<br>
I can not find any benefits.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=958722022-01-11T07:43:30Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/17">@ko1 (Koichi Sasada)</a>, the initial ticket provides some explanations:</p>
<blockquote>
<p>For example, this code snippet shows why <code>to_a</code> is problematic:</p>
</blockquote>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Result</span> <span class="o">=</span> <span class="no">Struct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:success</span><span class="p">,</span> <span class="ss">:content</span><span class="p">)</span>
<span class="c1"># Now, imagine that other code assumes `data` could be either Result, or [Result, Result, Result]</span>
<span class="c1"># So, ...</span>
<span class="n">data</span> <span class="o">=</span> <span class="no">Result</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="kp">true</span><span class="p">,</span> <span class="s1">'it is awesome'</span><span class="p">)</span>
<span class="no">Array</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1"># => expected [Result(true, 'it is awesome')], got [true, 'it is awesome']</span>
<span class="c1"># or...</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
<span class="nb">p</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span>
<span class="k">end</span>
<span class="n">foo</span><span class="p">(</span><span class="o">*</span><span class="n">data</span><span class="p">)</span> <span class="c1"># => expected [Result(true, 'it is awesome'), nil], got [true, 'it is awesome']</span>
</code></pre>
<p>That's about just <code>#to_a</code> method, but I think that in general, considering duck typing, it is undesirable that the object that the developer thinks of as an "atomic" will be duck-typed as a collection (<code>#respond_to?(:each)</code>). In general, you never know when "is it one thing, or is it an enumeration of things" will be crucial in code, and I think it is important to underline <code>Struct::Value</code> is <em>one</em> thing.</p>
<p>I believe there are good reasons why <code>#each</code> was removed from <code>String</code>, for example.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=958752022-01-11T07:47:09Zko1 (Koichi Sasada)
<ul></ul><p>zverok (Victor Shepelev) wrote in <a href="#note-24">#note-24</a>:</p>
<blockquote>
<p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/17">@ko1 (Koichi Sasada)</a>, the initial ticket provides some explanations:</p>
</blockquote>
<p>Sorry I found it just after commented.</p>
<p>It seems not related to "immutability".</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=958762022-01-11T07:52:36Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/17">@ko1 (Koichi Sasada)</a></p>
<blockquote>
<p>It seems not related to "immutability".</p>
</blockquote>
<p>Yes, I covered this, too (I know it is a large wall of text, sorry!), in <strong>Concrete proposal</strong> section:</p>
<blockquote>
<p>Class API is copying Structs one (most of the time -- even reuses the implementation), with the following exceptions (note: the immutability is <strong>not</strong> the only difference)</p>
</blockquote> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=958842022-01-11T16:16:10ZDan0042 (Daniel DeLorme)
<ul></ul><p>matz (Yukihiro Matsumoto) wrote in <a href="#note-19">#note-19</a>:</p>
<blockquote>
<p>I like the idea of helpers in <a href="https://bugs.ruby-lang.org/issues/16122#note-18" class="external">https://bugs.ruby-lang.org/issues/16122#note-18</a>.<br>
We need to discuss further the combination of attributes (immutable, enumerable, etc.)</p>
</blockquote>
<p>Having helpers would definitely provide a nice easy experience. But since the important thing here is the optional settings, disagreement/bikeshed on the helpers should not prevent or delay a decision on the immutable/enumerable/hash_accessors settings. It should be ok to first decide on those settings, and in a second step decide on the helpers. After all once the settings are available, it's trivial for anyone to define their own helpers.</p>
<p>So regarding those helpers I was thinking of something like <code>Struct.Value(:x, :y)</code> but there's also the <code>Struct::Value.new(:x, :y)</code> syntax that simulates a subclass. Having a <code>Value</code> is the main topic of this ticket, but personally I'm more interested in <code>Basic</code> that behaves more like a simple C struct. It's easier to use and reason about if you don't have to worry about accidential conversion to array and auto-splatting bugs. I'm not particularly attached to <code>Tuple</code> but I thought it was a good name to make it explicit when we want a splattable struct where the ordering of the fields is important, like <code>x,y,z = *tuple</code>.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=962412022-01-29T08:31:43Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>Discussed on the dev-meeting.</p>
<p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> is now negative to allow settings. Having various semantics in one Struct class will bring confusion rather than usability. <code>keyword_init</code> settings will be no longer needed after Ruby 3.2. (See <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: Struct#initialize accepts keyword arguments too by default (Closed)" href="https://bugs.ruby-lang.org/issues/16806">#16806</a> and <a class="changeset" title="Initialize Struct by calling with keyword arguments" href="https://bugs.ruby-lang.org/projects/ruby-master/repository/git/revisions/c956f979e5d05900315d2753d5c3b1389af8dae4">c956f979e5d05900315d2753d5c3b1389af8dae4</a>)</p>
<p>Instead, he seems positive to provide one strict version of Struct. His current preference is:</p>
<ul>
<li>Has: field reader methods, <code>deconstruct_keys</code>, <code>deconstruct</code>, <code>==</code>, <code>eql?</code>, <code>hash</code>
</li>
<li>Does not have: field writer methods like <code>writer=</code>, <code>each</code> and Enumerable, <code>to_a</code>, <code>each_pair</code>, <code>values</code>, <code>[]</code>, <code>[]=</code>, <code>dig</code>, <code>members</code>, <code>values_at</code>, <code>select</code>, <code>filter</code>, <code>size</code>, <code>to_h</code>
</li>
</ul>
<p>But he couldn't seem to decide on a name. <code>Struct::Value</code> seems acceptable to him, but he wanted to seek a better name. Devs suggested <code>Tuple</code>, <code>NamedTuple</code>, and <code>Record</code>, but none of them seemed to fit for him.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=962652022-01-30T12:53:17ZEregon (Benoit Daloze)
<ul></ul><p>ValueStruct or ImmutableStruct or FrozenStruct maybe?<br>
ImmutableStruct would probably only make sense if values are made immutable too, which doesn't seem proposed here.</p>
<p>I think the nesting of <code>Struct::Value</code> feels a bit weird, especially with the existing behavior of <code>Struct.new("Foo", :a)</code> defining <code>Struct::Foo</code>.<br>
But not really against it.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=962672022-01-30T20:39:05Zmatheusrich (Matheus Richard)matheusrichardt@gmail.com
<ul></ul><p>Some more alternatives to get the ideas rolling: <code>Unit</code> and <code>Item</code> (might be paired with Struct)</p>
<p>I also like <code>Box</code>.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=962682022-01-30T20:57:45ZDan0042 (Daniel DeLorme)
<ul></ul><p>Eregon (Benoit Daloze) wrote in <a href="#note-29">#note-29</a>:</p>
<blockquote>
<p>ValueStruct or ImmutableStruct or FrozenStruct maybe?</p>
</blockquote>
<p>Those are good ideas. Or to highlight the "pared-down" aspect of this strict version of Struct: SimpleStruct / PlainStruct / BasicStruct (parallel to Object vs BasicObject).</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=962702022-01-30T21:48:59Zmyronmarston (Myron Marston)myron.marston@gmail.com
<ul></ul><p>I'm quite fond of this proposal--I basically never use Struct unless I specifically need mutability and have been using the values gem for years, which has a simple implementation of about 100 lines:</p>
<p><a href="https://github.com/tcrayford/Values/blob/master/lib/values.rb" class="external">https://github.com/tcrayford/Values/blob/master/lib/values.rb</a></p>
<p>It offers a number of core features that I'd hope any stdlib equivalent would also provide:</p>
<ul>
<li>Instantiation via positional arguments (<code>ValueClass.new(1, 2)</code>)</li>
<li>Instantiation via keyword arguments (<code>ValueClass.with(foo: 1, bar: 2)</code>)</li>
<li>Ability to make a copy with one or more attributes updated: <code>value.with(foo: 1)</code>
</li>
<li>==/eql?/hash defined for value-based equality semantics</li>
<li>Readable to_s/inspect/pretty_print</li>
<li>Easy conversion to a hash with to_h</li>
</ul>
<p>Most engineers I've worked with have referred to this category of objects as "value objects" so I think "Value" in the name is good...but I don't care a whole lot about the name. Kotlin (another language I use) offers a similar feature and calls them data classes:</p>
<p><a href="https://kotlinlang.org/docs/data-classes.html" class="external">https://kotlinlang.org/docs/data-classes.html</a></p>
<p>If this is adopted, it'd also be great to see what stdlib types can be safely ported to build on this type--things like <code>Date</code>/<code>Time</code>/<code>URI</code>, etc. (It may of course be hard or impossible to port these to use the new feature while retaining backwards compatibility.)</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=964792022-02-12T21:54:03Zdsisnero (Dominic Sisneros)dsisnero@gmail.com
<ul></ul><p>+1 -<br>
Also, is there plans to have a flag in C or a different shape so that the VM's can make this fast</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=986682022-08-16T15:18:01Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/4">@nobu (Nobuyoshi Nakada)</a> proposed <code>Data</code>, which used to be a class for extension library authors, but deprecated since ruby 2.5 and removed since 3.0. We might reuse it now.</p>
<p>Summarise the proposed name candidates:</p>
<ul>
<li>Struct::Value</li>
<li>ImmutableStrudct</li>
<li>FrozenStruct</li>
<li>Unit</li>
<li>Item</li>
<li>Box</li>
<li>SimpleStruct</li>
<li>PlainStruct</li>
<li>BasicStruct</li>
<li>ValueClass (provided by values gem?)</li>
<li>Value (provided by values gem)</li>
<li>Data</li>
</ul> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=986702022-08-16T15:31:01Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>BTW, I personally wanted Struct to store the field values simply in instance variables rather than the hidden storage. For example:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">FooBar</span> <span class="o">=</span> <span class="no">Struct</span><span class="o">::</span><span class="no">Value</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:foo</span><span class="p">,</span> <span class="ss">:bar</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">FooBar</span>
<span class="k">def</span> <span class="nf">foo_plus_bar</span>
<span class="c1"># These bare "foo" and "bar" are not visually obvious</span>
<span class="c1"># whether they are a method call or local variable access</span>
<span class="n">foo</span> <span class="o">+</span> <span class="n">bar</span>
<span class="c1"># We can write it as follows, but it is a bit verbose</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">foo</span> <span class="o">+</span> <span class="nb">self</span><span class="p">.</span><span class="nf">bar</span>
<span class="c1"># If they are stored in instance variables,</span>
<span class="c1"># it is obvious that they are field access</span>
<span class="vi">@foo</span> <span class="o">+</span> <span class="vi">@bar</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>I know it is impossible to change Struct for compatibility reason, but if we introduce a new Struct-like class, I wonder if we can change this too?</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=986722022-08-16T15:54:24ZEregon (Benoit Daloze)
<ul></ul><p>mame (Yusuke Endoh) wrote in <a href="#note-35">#note-35</a>:</p>
<blockquote>
<p>BTW, I personally wanted Struct to store the field values simply in instance variables rather than the hidden storage.<br>
I know it is impossible to change Struct for compatibility reason, but if we introduce a new Struct-like class, I wonder if we can change this too?</p>
</blockquote>
<p>FWIW, TruffleRuby used to use ivars for <code>Struct</code> but changed to "hidden ivars" for compatibility.<br>
Hidden ivars probably adds a bit more flexibility in the implementation but also means e.g. <code>attr_reader</code> can't be used directly to implement <code>Struct::Value</code>.<br>
I'd think it'd be nice if we can share implementation code between Struct and Struct::Value, so it seems best to use the same representation from that POV.</p>
<p>A problem with ivars is Struct allows members which are not valid ivar names (IIRC), so ivars can't be used internally, or <code>Kernel#instance_variables</code> will not necessarily be all Struct::Value attributes.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=986972022-08-18T06:38:57Zk0kubun (Takashi Kokubun)takashikkbn@gmail.com
<ul></ul><p>My enthusiastic +1 for <code>Data</code>.</p>
<p>I've used Kotlin and its <a href="https://kotlinlang.org/docs/data-classes.html" class="external">Data classes</a> like <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/2743">@myronmarston (Myron Marston)</a>, and I feel calling it a Data class is somewhat accepted by the community. On the other hand, calling it Struct::Value feels like a workaround to avoid a conflict with existing names. I'm not sure if <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/710">@zverok (Victor Shepelev)</a> likes <code>Data</code> over his own proposal, but note that <code>data</code> appears in his local variable name as well.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987002022-08-18T06:59:04Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>k0kubun (Takashi Kokubun) wrote in <a href="#note-37">#note-37</a>:</p>
<blockquote>
<p>My enthusiastic +1 for <code>Data</code>.</p>
<p>I've used Kotlin and its <a href="https://kotlinlang.org/docs/data-classes.html" class="external">Data Classes</a> like <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/2743">@myronmarston (Myron Marston)</a>, and I feel calling it a <code>Data</code> class is somewhat accepted by the community. On the other hand, calling it <code>Struct::Value</code> feels like a workaround to avoid a conflict with existing names. I'm not sure if <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/710">@zverok (Victor Shepelev)</a> likes <code>Data</code> over his own proposal, but note that <code>data</code> appears in his local variable name as well.</p>
</blockquote>
<p>+1 as well. It's similar to the idea of <a href="https://docs.scala-lang.org/tour/case-classes.html" class="external">Case Class</a> in Scala as well, and I think the name <code>Data</code> is reasonable. Happy to see that <code>Struct</code> is looking to deprecate <code>keyword_init</code> in favor of accepting both styles as well, both are welcome changes.</p>
<p>These will be especially useful with pattern matching features</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987202022-08-18T14:14:48Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><blockquote>
<p>I'm not sure if <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/710">@zverok (Victor Shepelev)</a> likes Data over his own proposal, but note that data appears in his local variable name as well.</p>
</blockquote>
<p>It is OK, I think, save for some clumsiness when you try to speak in plurals (wrong "datas" or right-yet-not-obvious "datum").</p>
<p>I was never too sure about the name anyway.</p>
<p>If the rest is OK, I'll rebase my PR and update naming on the weekend.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987222022-08-18T17:05:05Zmyronmarston (Myron Marston)myron.marston@gmail.com
<ul></ul><p>If “data” is the naming direction folks like, I think the class name should be DataClass. This aligns with kotlin (where data is a keyword before the class keyword) and reads better, IMO: DataClass.new gives you a new class whose purpose is to hold data. Data.new sounds like it gives you a new data which sounds weird.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987272022-08-18T17:59:45Zk0kubun (Takashi Kokubun)takashikkbn@gmail.com
<ul></ul><p>I can live with <code>DataClass</code> too, but I still can't forget the beautiful shortness of <code>Data</code>. <code>DataClass.new</code> feels like you wanted to be so right that the name ended up being a bit verbose. To address that point, I thought of <code>Data.class</code>, which looks cool, but I guess such an interface doesn't exist in Ruby yet and <code>DataClass.new</code> is more "correct" in the OOP world.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987402022-08-19T08:23:16Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>At the dev meeting, <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> rejected all name candidates except <code>Struct::Value</code> and <code>Data</code>.</p>
<ul>
<li>He wants to avoid the names already used by gems: ImmutableStruct, ValueClass, Value</li>
<li>Short common nouns would be conflicting: Unit, Item, Box</li>
<li>The main purpose of the new class is immutability, not "frozen", not "plain", not simplicity: FrozenStruct, SimpleStruct, PlainStruct</li>
<li>He doesn't plan to make the old <code>Struct</code> inherit from the new one, so <code>BasicStruct</code> like <code>BasicObject</code> is not suitable</li>
</ul>
<p>Incidentally, my proposal in <a href="#note-35">#note-35</a> was rejected because an instance variable is weak against a typo. (A misspelled reader method raises NameError, but a misspelled instance variable returns nil implicitly.)</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987412022-08-19T08:30:32Zmame (Yusuke Endoh)mame@ruby-lang.org
<ul></ul><p>This is my personal opinion. I think <code>Data</code> is a good choice since there are few compatibility issues at this time despite the short and simple name. If we were to use a slightly different word for this, such as <code>DataClass</code>, I don't see much point in choosing this word.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987422022-08-19T10:13:11Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><blockquote>
<p>At the dev meeting, <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> rejected all name candidates except <code>Struct::Value</code> and <code>Data</code>.</p>
</blockquote>
<p>So, as far as I can understand, we only should choose one of two now, right?<br>
I like <code>Struct::Value</code> slightly more, but not to the point of spending one more year discussing it :)</p>
<p>Let's stick with <code>Data</code> then, and I prepare the final PR.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987432022-08-19T10:19:51Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><p>I am not 100% satisfied with any of the candidates, but Struct::Value and Data are better than others.<br>
Struct::Value can cause conflict when someone is using <code>Struct.new("Value", :foo, :bar)</code> (this old-style code creates Struct::Value class).<br>
Data is a little ambiguous, but probably we can get used.</p>
<p>Matz.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987482022-08-19T16:24:32Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p>Umm wait.</p>
<p><code>Data</code> is actually a <a href="https://www.learnenglish.de/mistakes/data.html" class="external">plural form</a>. While using it as singular is acceptable in modern English, in this case we don't have a plural for it.</p>
<p>I believe it will be a problem while writing docs, tutorials and discussing things. "Let's define a data here. Now, let's define some more ???"</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987502022-08-19T19:09:27Zk0kubun (Takashi Kokubun)takashikkbn@gmail.com
<ul></ul><p>It consists of multiple members, so calling it data itself doesn't seem like a problem to me. For documentation, you could say a data class or data classes.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=987622022-08-20T10:31:14ZEregon (Benoit Daloze)
<ul></ul><blockquote>
<p>The main purpose of the new class is immutability, not "frozen", not "plain", not simplicity: FrozenStruct, SimpleStruct, PlainStruct</p>
</blockquote>
<p>Immutable AFAIK means "deeply frozen", while frozen means "shallow frozen" (= <code>Kernel#freeze/frozen?</code>).<br>
This new struct-like class will not ensure every value is immutable, so it won't guarantee the Struct::Value instance is immutable.<br>
So in terms of documentation and semantics the new class will create frozen but not immutable instances.</p>
<p>Regarding the name, I like Data as well.<br>
If we want to avoid the confusion of <code>Data.new</code> returning a class and not an instance of Data, we could have a another name for "create a Data subclass with these fields", maybe <code>Data.for(:a, :b)</code> or <code>Data.new_class(:a, :b)</code>/<code>Data.new_subclass(:a, :b)</code> or so.<br>
I have seen many people being confused with <code>Struct.new</code> returning a subclass, so I think it is something worth considering for the new struct-like class.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989082022-08-25T10:40:35Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><p>We are going to implement Data class in the following spec:</p>
<ul>
<li>
<code>D = Data.def(:foo, :bar)</code> to define a data class (subclass of Data)</li>
<li>
<code>Data.new</code> raises exception (unlike Struct)</li>
<li>
<code>d = D.new(1,2)</code> to create Data instance</li>
<li>Or <code>d = D.new(foo:1, foo:2)</code>
</li>
<li>
<code>D.new(1)</code> or <code>D.new(1,2,3)</code> raises ArgumentError</li>
<li>
<code>D.new(foo:1)</code> or <code>D.new(foo:1,bar:2,baz:3)</code> raises ArgumentError</li>
<li>Instead of <code>D.new(...)</code> you may want to use <code>D[...]</code>
</li>
</ul>
<p>We need further discussion regarding the following:</p>
<ul>
<li>default value to initialize Data</li>
<li>how to call <code>initialize</code> method for <code>D</code> class</li>
<li>whether we will introduce <code>Struct.def</code>
</li>
</ul>
<p>Matz.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989092022-08-25T11:25:35Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> Thanks for the decisions!</p>
<p>A few questions:</p>
<ol>
<li>I am a bit concerned about using <code>def</code>, which is strongly associated with defining methods. I wouldn't want it to be a blocker (it would be really cool to have <code>Data</code> by the 3.2), but can we consider our options here? From the top of my head, I can think of <code>define</code> (used in methods context too, but less strong association with <code>def method</code>), <code>setup</code>, <code>create</code> or <code>generate</code>.</li>
<li>"default value to initialize Data" I am not sure what do you mean by that, can you please elaborate?</li>
<li>"how to call <code>initialize</code> method for <code>D</code> class". What options do we have here? Is there a necessity for deviation from how other classes work?..</li>
</ol> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989182022-08-25T13:34:11Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><ol>
<li>I slightly regretted to make <code>Struct.new</code> to create a subclass, not an instance. So this time I didn't choose <code>new</code>. <code>create</code> or <code>generate</code> would have a similar issue with <code>new</code>. <code>define</code> might be a candidate. But I still prefer shorter one, but you can try to persuade me.</li>
<li>Sometimes it's handy to fill data members (e.g., foo) by the default value. But we still have no idea to specify those default values. And if default values are available, there could be an issue regarding modifying the value (e.g., an empty array is given for a default value, what if it's modified).</li>
<li>When we allow mixing positional and keyword initializers, implementing <code>initialize</code> may be a bit complex. But we may unify 2 kinds of initializers to keyword initializer in <code>D#new</code>. But it's implementation detail. Need to be discussed later.</li>
</ol>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">D</span><span class="o">=</span><span class="no">Data</span><span class="p">.</span><span class="nf">def</span><span class="p">(</span><span class="ss">:foo</span><span class="p">,</span> <span class="ss">:bar</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">D</span>
<span class="c1"># if arguments to `new` is passed directly</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span><span class="o">**</span><span class="n">kwd</span><span class="p">)</span>
<span class="c1"># ... checking positional and keyword initializers</span>
<span class="k">end</span>
<span class="c1"># instead</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">**</span><span class="n">kwd</span><span class="p">)</span> <span class="c1"># only takes keyword arguments</span>
<span class="c1"># ... initialization become much simpler</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>Oh, I forgot. During the discussion, someone came up with an idea of Anonymous Data, data instance without creating a subclass (kinda like (Named)Tuples in other languages). This is another topic.</p>
<p>Matz.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989222022-08-25T20:02:52Zk0kubun (Takashi Kokubun)takashikkbn@gmail.com
<ul><li><strong>Subject</strong> changed from <i>Struct::Value: simple immutable value object</i> to <i>Data: simple immutable value object</i></li><li><strong>Status</strong> changed from <i>Feedback</i> to <i>Assigned</i></li><li><strong>Assignee</strong> set to <i>zverok (Victor Shepelev)</i></li></ul> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989252022-08-25T22:48:08Zk0kubun (Takashi Kokubun)takashikkbn@gmail.com
<ul></ul><p><code>Data.new</code> aside, <code>Data.def</code> and <code>Data.define</code> are my current top-2 preferences. I'd be happy with either of these at this point.</p>
<p>But thinking about this further, I might like <code>Data.define</code> slightly more. It's a trade-off with shortness, but <code>Data.define</code> sounds more natural and it's still short enough, thanks to <code>Data</code> being chosen instead of <code>Struct::Value</code>. Even if it were to be ported to <code>Struct</code>, <code>Struct.define</code> doesn't seem too long either.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989322022-08-26T10:22:31ZEregon (Benoit Daloze)
<ul></ul><p>I like <code>Data.define</code> as well, we are "defining a new subclass of Data".<br>
<code>def</code> makes me thing to "define a method" since that's what the keyword of the same name does, but <code>Data.def</code> does not define a method but a class.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989492022-08-26T16:53:01Zaustin (Austin Ziegler)halostatue@gmail.com
<ul></ul><p>Eregon (Benoit Daloze) wrote in <a href="#note-54">#note-54</a>:</p>
<blockquote>
<p>I like <code>Data.define</code> as well, we are "defining a new subclass of Data".<br>
<code>def</code> makes me thing to "define a method" since that's what the keyword of the same name does, but <code>Data.def</code> does not define a method but a class.</p>
</blockquote>
<p>Elixir uses <code>defmodule</code> for module declarations. Why not <code>Data.defclass</code> or <code>Data.deftype</code> or even <code>Data.defshape</code>?</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989522022-08-26T19:08:52ZEregon (Benoit Daloze)
<ul></ul><p>austin (Austin Ziegler) wrote in <a href="#note-55">#note-55</a>:</p>
<blockquote>
<p>Why not <code>Data.defclass</code> or <code>Data.deftype</code> or even <code>Data.defshape</code>?</p>
</blockquote>
<p>Because those do not use a proper Ruby naming (no _ to separate words, aside: looks like Python matplotlib methods), they are longer and read less nicely.<br>
We already have some agreement on <code>Data.define</code>, let's not discuss another year on other names please.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=989782022-08-27T16:34:31Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p>Disregard that, looked in the wrong place.</p>
<p><del>Hmm, folks, working on implementation on the weekend, I am a bit confused. I believe I <em>saw</em> that in 3.2 we decided to make <code>Struct</code> more flexible, but accepting positional OR keyword args regardless of <code>keyword_init: true</code>, but currently I can't find anything like that either in <code>master</code> or in the tracker.<br>
Am I misremembering?.. (It is OK by me to implement params handling for <code>Data</code> initialization separately, I just somehow believed the same is already done for <code>Struct</code>)</del></p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=990532022-09-01T15:58:09ZRubyBugs (A Nonymous)msiegel@riverdaletechinc.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/772">@Eregon (Benoit Daloze)</a> This is great news!</p>
<p>At Panorama Education, we use maintain a fork of the tcrayford/values gem here for this purpose here: <a href="https://github.com/ms-ati/Values/tree/panoramaed-2.0.x" class="external">https://github.com/ms-ati/Values/tree/panoramaed-2.0.x</a></p>
<p>We hope that a Ruby-native solution might address some of the same needs we have in the gem:</p>
<p><strong>1) Copy with changes method</strong></p>
<p>The above gem calls this method <code>#with</code>. Called on an instance, it returns a new instance with only the provided parameters changed.</p>
<p>This API affordance is now widely adopted across languages for its usefulness, because copying with discrete changes is the proper pattern that replaces mutation for immutable value objects, for example:</p>
<p>C# Records: “immutable record structs — Non-destructive mutation” — is called with<br>
<a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record#nondestructive-mutation" class="external">https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record#nondestructive-mutation</a></p>
<p>Scala Case Classes — is called copy<br>
<a href="https://docs.scala-lang.org/tour/case-classes.html" class="external">https://docs.scala-lang.org/tour/case-classes.html</a></p>
<p>Java 14+ Records — Brian Goetz at Oracle is working on adding a with copy constructor inspired by C# above as we speak:<br>
<a href="https://mail.openjdk.org/pipermail/amber-spec-experts/2022-June/003461.html" class="external">https://mail.openjdk.org/pipermail/amber-spec-experts/2022-June/003461.html</a></p>
<p>Rust “Struct Update Syntax” via .. syntax in constructor<br>
<a href="https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax" class="external">https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax</a></p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=990542022-09-01T16:00:33ZRubyBugs (A Nonymous)msiegel@riverdaletechinc.com
<ul></ul><p><strong>2. Highly optimized hash and eql?</strong></p>
<p>Because we use value objects in loops, profiling led us to the commits in our fork of tcrayford/values, which optimize these methods, so that using Value objects in Sets and as Hash keys will be as performant as possible.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=990552022-09-01T16:04:47ZRubyBugs (A Nonymous)msiegel@riverdaletechinc.com
<ul></ul><p><strong>3. Keyword arg constructors</strong></p>
<p>Because a non-mutating copy-with-changes such as <code>#with</code> will need to take keyword arguments to indicate which values to change, it’s useful to provide a constructor form which also accepts keyword arguments</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=990562022-09-01T16:09:12ZRubyBugs (A Nonymous)msiegel@riverdaletechinc.com
<ul></ul><p><strong>4. Conversion from and to Hash</strong></p>
<p>For example: a keyword arg constructor accepting <code>(**hsh)</code>, and a <code>#to_h</code> method</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=990682022-09-03T08:41:04Zk0kubun (Takashi Kokubun)takashikkbn@gmail.com
<ul></ul><p>RubyBugs (A Nonymous) wrote in <a href="#note-58">#note-58</a>:</p>
<blockquote>
<p>We hope that a Ruby-native solution might address some of the same needs we have in the gem:</p>
</blockquote>
<p>Can you please file a separate ticket to discuss <code>Data</code> extensions that don't exist in Struct? There could be so many <code>Data</code> extension ideas, such as default values mentioned in <a href="#note-49">#note-49</a>, but discussing all that in a single ticket would make it harder to follow all of such discussions.</p>
<p>zverok (Victor Shepelev) wrote in <a href="#note-39">#note-39</a>:</p>
<blockquote>
<p>If the rest is OK, I'll rebase my PR and update naming on the weekend.</p>
</blockquote>
<p>Have you filed a PR by the way? It seems like you only attached a patch and <a href="https://github.com/ruby/ruby/pulls?q=author%3Azverok+is%3Aopen" class="external">didn't file one</a>. While <code>Data.def</code> could still be changed to <code>Data.define</code> (<a href="#note-51">#note-51</a>) depending on how the discussion goes, the code change for it wouldn't be so hard. I think it's nice to confirm its look and feel by actually playing with it early, which would be useful for discussing <code>Data.def</code> vs <code>Data.define</code> as well.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=990872022-09-08T18:20:34ZRubyBugs (A Nonymous)msiegel@riverdaletechinc.com
<ul></ul><p>k0kubun (Takashi Kokubun) wrote in <a href="#note-62">#note-62</a>:</p>
<blockquote>
<p>Can you please file a separate ticket to discuss <code>Data</code> extensions that don't exist in Struct? There could be so many <code>Data</code> extension ideas, such as default values mentioned in <a href="#note-49">#note-49</a>, but discussing all that in a single ticket would make it harder to follow all of such discussions.</p>
</blockquote>
<p>Thanks <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/10073">@k0kubun (Takashi Kokubun)</a>! I've filed follow-up ticket here for the "Copy with changes" method <code>#with</code>:<br>
<a href="https://bugs.ruby-lang.org/issues/19000" class="external">https://bugs.ruby-lang.org/issues/19000</a></p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=990942022-09-08T19:50:52ZRubyBugs (A Nonymous)msiegel@riverdaletechinc.com
<ul></ul><p>I've filed a 2nd follow-up ticket <a href="https://bugs.ruby-lang.org/issues/19001" class="external">here</a> for the Symmetric <code>#to_h</code> method whose values can be fed to a keyword-args constructor</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991042022-09-09T21:26:37Zshugo (Shugo Maeda)
<ul></ul><p>If we choose <code>define</code> instead of <code>new</code>, why not use Class.define to return a new immutable Struct-like class?</p>
<ul>
<li>The name Data doesn't imply immutability, so Class.define is OK too.</li>
<li>It's clearer that Class.define returns a class.</li>
</ul> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991062022-09-09T23:06:58Zk0kubun (Takashi Kokubun)takashikkbn@gmail.com
<ul></ul><p>shugo (Shugo Maeda) wrote in <a href="#note-65">#note-65</a>:</p>
<blockquote>
<p>why not use Class.define to return a new immutable Struct-like class?</p>
</blockquote>
<p>Given that Matz is also thinking about introducing <code>Struct.define</code>, if we do the immutable one with <code>Class.define</code>, we'd need to also pass <code>immutable: true</code> or <code>immutable: false</code> for either of these, which is longer and harder to use. Otherwise you'd need to have <code>Struct.define</code> for mutable one and <code>Class.define</code> for immutable one, which seems inconsistent.</p>
<blockquote>
<p>It's clearer that Class.define returns a class.</p>
</blockquote>
<p>I feel Data class is a fairly different concept from normal Class because it has special fields used for comparison and deconstruction. You shouldn't have any mutable thing under <code>Data.define</code> while you're free to do such things in <code>Class.new</code>. To make it easier to notice such difference, it feels cleaner to me to separate <code>Class</code>, <code>Struct</code>, and <code>Data</code> this way.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991102022-09-10T11:08:40ZEregon (Benoit Daloze)
<ul></ul><p>Agreed with <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/10073">@k0kubun (Takashi Kokubun)</a>. Also <code>Class.define</code> wouldn't make it clear it defines a data class and creates Data (subclass) instances.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991122022-09-10T12:35:05Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p>Pull request: <a href="https://github.com/ruby/ruby/pull/6353" class="external">https://github.com/ruby/ruby/pull/6353</a></p>
<p>Copying from its description:</p>
<p>Example docs rendering: <a href="https://zverok.space/ruby-rdoc/Data.html" class="external">Data</a></p>
<p>Design and implementation decisions made:</p>
<p><strong>1. The "define data object method is called <code>Data.define</code></strong>. As per <a href="https://bugs.ruby-lang.org/issues/16122#note-51" class="external">Matz</a>:</p>
<blockquote>
<p><code>define</code> might be a candidate. But I still prefer shorter one (e.g. <code>def</code>), but you can try to persuade me.</p>
</blockquote>
<p>There were a few quite reasonable arguments towards <code>define</code> in that ticket. To add to them, my PoV:</p>
<ul>
<li>I believe that nowadays (adding new APIs to the mature language), it is better to use full English words to remove confusion and increase readability;</li>
<li>
<code>def</code> is strongly associated in Ruby with "defining a method" and became a separate word in Rubyist's dictionary, not a "generic shortcut for 'define'"</li>
<li>I believe that the "definition of the new type" (unlike the definition of a new method) is a situation where clarity is more important than saving 3 chars; and somewhat rarer.</li>
</ul>
<p><strong>2. <code>define</code> accepts keyword and positional args; they are converted to keyword args there, and checked in <code>initialize</code></strong></p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Measure</span> <span class="o">=</span> <span class="no">Data</span><span class="p">.</span><span class="nf">define</span><span class="p">(</span><span class="ss">:amount</span><span class="p">,</span> <span class="ss">:unit</span><span class="p">)</span>
<span class="no">Measure</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'km'</span><span class="p">)</span> <span class="c1"># => OK</span>
<span class="no">Measure</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">amount: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">unit: </span><span class="s1">'km'</span><span class="p">)</span> <span class="c1"># => OK</span>
<span class="no">Measure</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="c1"># ArgumentError</span>
<span class="no">Measure</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">amount: </span><span class="mi">1</span><span class="p">)</span> <span class="c1"># ArgumentError</span>
<span class="no">Measure</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'km'</span><span class="p">,</span> <span class="s1">'h'</span><span class="p">)</span> <span class="c1"># ArgumentError</span>
<span class="no">Measure</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">amount: </span><span class="mi">1</span><span class="p">,</span> <span class="ss">unit: </span><span class="s1">'km'</span><span class="p">,</span> <span class="ss">comment: </span><span class="s1">'slow'</span><span class="p">)</span> <span class="c1">#=> ArgumentError</span>
</code></pre>
<p>The fact that <code>initialize</code> accepts only keyword args and checks them makes it easy to define custom <code>initialize</code> with defaults, for example (it is all explicitly documented, see link above):</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Measure</span> <span class="o">=</span> <span class="no">Data</span><span class="p">.</span><span class="nf">define</span><span class="p">(</span><span class="ss">:amount</span><span class="p">,</span> <span class="ss">:unit</span><span class="p">)</span> <span class="k">do</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">amount</span><span class="p">:,</span> <span class="ss">unit: </span><span class="s1">'-none-'</span><span class="p">)</span> <span class="o">=</span> <span class="k">super</span>
<span class="k">end</span>
<span class="no">Measure</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="c1">#=> #<data Measure amount=1, unit="-none-"></span>
</code></pre>
<p>(This might be enough for not to invent a separate API for default values, but this discussion can be postponed.)</p>
<p><strong>3. I didn't introduce any additional APIs yet (e.g. something like <code>with</code> which was discussed).</strong> So, the full API of the <code>Data</code> as rendered by RDoc is:<br>
<img src="https://user-images.githubusercontent.com/129656/189483309-59ecd424-62ba-4014-a4b6-e64a8a07e021.png" alt="image"></p>
<p>I believe it is enough for the introduction, and then the feedback might be accepted for how to make it more convenient.</p>
<p><strong>4. I wrote custom docs for <code>Data</code> class</strong> instead of copy-pasting/editing docs for <code>Struct</code>. I have a strong belief that the approach to docs used is more appropriate:</p>
<ol>
<li>For separate methods, instead of detailed documenting of every behavior quirk by spelling it in many phrases, I provided the explanation of <em>what</em> it does logically and examples of <em>how</em> it can/should be used.</li>
<li>For the entire class, I don't see a point in the recently introduced formal structure of "What's here" with many nested sections, at least for small classes. It sacrifices the lightweight-yet-enough explanations that can be consumed almost instantly, towards the feeling of "there is a book-worth of information to read," making the documentation user's experience more laborious.</li>
</ol>
<p>Probably it is not a good place to discuss those approaches in general, but at least for the Data class, I believe the chosen approach is superior. If the core team believes it is not true, I think my changes can be merged and then formal docs provided by team members who consider them valuable.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991142022-09-10T13:46:11ZEregon (Benoit Daloze)
<ul></ul><p>Looks good to me.</p>
<p>Regarding overriding <code>initialize</code> and calling <code>super</code>, that would not work if we define an optimized <code>initialize</code> instance method on the subclass by Data.define (it would result in NoMethodError or the slower generic initialize).<br>
That can be worked around on the VM side by defining the optimized <code>initialize</code> in a module, but that's extra overhead/footprint.<br>
It is what is done for Struct though in <a href="https://github.com/oracle/truffleruby/blob/master/src/main/ruby/truffleruby/core/struct.rb" class="external">https://github.com/oracle/truffleruby/blob/master/src/main/ruby/truffleruby/core/struct.rb</a></p>
<p>In general using keyword arguments for <code>initialize</code> causes a non-trivial overhead (lots of generic Hash operations),<br>
unless <code>initialize</code> is optimized and generated per Data subclass, where it can then use literal keyword arguments which are much better optimized.</p>
<p>So I think it would be best to specialize <code>initialize</code> per subclass.<br>
Concretely we would define <code>initialize</code> on Data subclasses, but not on Data itself. That keeps us the opportunity to specialize <code>initialize</code> per subclass.<br>
This also means, to override <code>initialize</code> with defaults, one would need to use <code>alias</code> to call the original <code>initialize</code> (much better for memory footprint, no extra subclass), or subclass the Data subclass to use <code>super</code>.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991152022-09-10T13:55:40Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/772">@Eregon (Benoit Daloze)</a> Yeah, those are valuable observations!</p>
<p>The specialized <code>initialize</code> also looks more reasonable to me, actually, but I followed what the <code>Struct</code> does so far.</p>
<p>I am not sure whether we have a C-level API for passing keyword args one-by-one, not by packing to Hash and unpacking back?.. My C-foo might be not strong enough for defining specialized initializer.</p>
<p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/10073">@k0kubun (Takashi Kokubun)</a> <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> is it something we want to handle from the beginning? (I assume it might be, as changing it later would break the compatibility for redefined <code>initialize</code>)</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991162022-09-10T14:04:27ZEregon (Benoit Daloze)
<ul></ul><p>zverok (Victor Shepelev) wrote in <a href="#note-70">#note-70</a>:</p>
<blockquote>
<p>I am not sure whether we have a C-level API for passing keyword args one-by-one, not by packing to Hash and unpacking back?.. My C-foo might be not strong enough for defining specialized initializer.</p>
</blockquote>
<p>I think it doesn't need to be done from the start, but to leave the possibility to do it we should define <code>initialize</code> on the subclass and not on Data.<br>
So I suggest to just define it on the subclass and initially it's fine to simply use Hash operations and optimize it later.<br>
For instance it might be easier to make this work through evaling some Ruby code, assuming the member names are valid local variable names.'</p>
<p>Regarding creating a new Data subclass instance, I wonder if we should support both positional and kwargs,<br>
or if we should only support keyword arguments for simplicity and performance (since anyway we need kwargs for <code>initialize</code> as you said).<br>
We can always add creation with positional arguments after if desired.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991172022-09-10T14:12:53Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><blockquote>
<p>Regarding creating a new Data subclass instance, I wonder if we should support both positional and kwargs, or if we should only support keyword arguments</p>
</blockquote>
<p>Am I understanding correctly that you propose to only leave <code>Measure.new(amount: 1, unit: 'km')</code> syntax, and ditch <code>Measure.new(1, 'km')</code> one?..</p>
<p>If so, I am positive that we <strong>should</strong> support both, and I believe that's <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> 's position too. I believe that need to write <code>Measure[amount: 10, unit: 'km']</code> instead of <code>Measure[10, 'km']</code> for trivial data classes would be a significant barrier towards adoption of the new feature.</p>
<p>Note that with features that pattern-matching provides, even 1-attribute Data makes sense for strong/expressive typing, e.g. <code>Result[1]</code> vs. <code>Error["brrr"]</code>, and adding <em>one more name</em> to write here would be incredibly irritating.</p>
<p>So my stance (again, as far as I understand, it is <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> 's, too) for new language features is optimization can be postponed if it is non-trivial, but finding a good and expressive API can not.</p>
<p>Anyway, it is implemented already :)</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991182022-09-10T16:41:15ZEregon (Benoit Daloze)
<ul></ul><p>Indeed, that's what I meant. Alright, I guess we need to support positional arguments too then.<br>
Because that's implemented in the subclass <code>.new</code> it should be possible to optimize it pretty well.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=991242022-09-11T02:54:00Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul></ul><p>zverok (Victor Shepelev) wrote in <a href="#note-68">#note-68</a>:</p>
<blockquote>
<p>Pull request: <a href="https://github.com/ruby/ruby/pull/6353" class="external">https://github.com/ruby/ruby/pull/6353</a></p>
</blockquote>
<p>Very nice.</p>
<p>I don't think <code>keyword_init</code> and "naming as the first argument"<br>
features are needed for new <code>Data</code>. So, I guess that splitting the<br>
<code>rb_struct_s_def</code> function rather than extracting it to<br>
<code>define_struct</code> with a bool flag.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=992132022-09-20T18:16:50Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/4">@nobu (Nobuyoshi Nakada)</a> Thanks!</p>
<p>I've applied all suggestions for the code review, except for <a href="https://github.com/ruby/ruby/pull/6353#discussion_r967742019" class="external">this one</a> (I've answered why it is done that way), and <code>define_struct</code> one. My reasoning is this:</p>
<ul>
<li>I am not sure that "naming as a first argument" is a widely used feature, but it seems nice, so I left it for <code>Data</code> too (and tests confirming its existence); I imagine that in some systems, doing <code>Data.define('MyType', :members)</code> might be preferred way;</li>
<li>If we leave that aside, the differences due to a bool flag are very small (some 5-6 lines of 70-lines method), so it seems that keeping it all together is the most straightforward.</li>
</ul>
<p>But please tell me if you disagree, and I'll change the implementation.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=992142022-09-20T21:08:23Zufuk (Ufuk Kayserilioglu)
<ul></ul><p>zverok (Victor Shepelev) wrote in <a href="#note-75">#note-75</a>:</p>
<blockquote>
<ul>
<li>I am not sure that "naming as a first argument" is a widely used feature, but it seems nice, so I left it for <code>Data</code> too (and tests confirming its existence); I imagine that in some systems, doing <code>Data.define('MyType', :members)</code> might be preferred way;</li>
</ul>
</blockquote>
<p>In my opinion, this is a good time to break free from this old API and start with a better design. The fact that the <code>class_name</code> argument defines a constant under <code>Struct</code> is a little too magical, needlessly pollutes the namespace, and leads to name clashes. I would prefer it if <code>Data</code> didn't inherit the same thing from <code>Struct</code> and had a more purpose designed API from the start.</p>
<p>I can also see from <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> 's previous message in this thread that he considers that syntax as "old-style". Moreover, the existence of the <code>class_name</code> parameter was one of his reasons against the name <code>Struct::Value</code>. It would have been horrible to not be able to use it if it was the best name for the concept, just because it could clash with someone else's magical struct class. Luckily <code>Data</code> was a better name for it. I'd rather that we don't paint ourselves into similar corners in the future.</p>
<p>matz (Yukihiro Matsumoto) wrote in <a href="#note-45">#note-45</a>:</p>
<blockquote>
<p>Struct::Value can cause conflict when someone is using <code>Struct.new("Value", :foo, :bar)</code> (this old-style code creates Struct::Value class).</p>
</blockquote> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=992362022-09-22T03:30:02Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul></ul><p>As <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/39299">@ufuk (Ufuk Kayserilioglu)</a> wrote 🙏, I don’t think the behavior worth to be kept.<br>
In the case you want a name, you can assign it to a constant.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=992502022-09-22T18:50:10Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/39299">@ufuk (Ufuk Kayserilioglu)</a> <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/4">@nobu (Nobuyoshi Nakada)</a> Makes sense, right.<br>
I adjusted the PR and removed the unification into the <code>define_struct</code> method. They are pretty different now (<a href="https://github.com/ruby/ruby/pull/6353/files#diff-af52c7b2f2401c72137b502f634cbceb9d313a66897d630930c62f4f8bfb7faaR1708-R1724" class="external">this part</a> probably can be extracted to a common one, but I am not really sure it is necessary)</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=992512022-09-22T19:07:46Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/99251/diff?detail_id=63157">diff</a>)</li></ul> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=992632022-09-22T22:45:15Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul></ul><p>zverok (Victor Shepelev) wrote in <a href="#note-78">#note-78</a>:</p>
<blockquote>
<p>They are pretty different now (<a href="https://github.com/ruby/ruby/pull/6353/files#diff-af52c7b2f2401c72137b502f634cbceb9d313a66897d630930c62f4f8bfb7faaR1708-R1724" class="external">this part</a> probably can be extracted to a common one, but I am not really sure it is necessary)</p>
</blockquote>
<p>I wonder about the “weird name” members…</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=992642022-09-22T22:57:43Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><blockquote>
<p>I wonder about the “weird name” members…</p>
</blockquote>
<p>Oh right. Left a note to self and missed it myself 🤦<br>
I adjusted the tests (though the note was left even before the <code>test_edge_cases</code> method was added, so most of it was tested already).</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=992712022-09-23T00:44:50Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/99271/diff?detail_id=63158">diff</a>)</li></ul><p>Could you summarize the up-to-date proposed specification of Data class, please?<br>
For the record, I accept <code>define</code> instead of <code>def</code>.</p>
<p>Matz.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=993192022-09-25T11:43:10Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/99319/diff?detail_id=63173">diff</a>)</li></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> I've updated the ticket text with the description of the implemented API and links.</p>
<p>Thank you!</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=993232022-09-26T00:39:23Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>I'd like to know how complicated it would be to support an interface like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Header</span> <span class="o">=</span> <span class="no">Data</span><span class="p">.</span><span class="nf">define</span><span class="p">(</span><span class="ss">:type</span><span class="p">,</span> <span class="ss">:length</span><span class="p">)</span>
<span class="c1"># ...</span>
<span class="n">buffer</span> <span class="o">=</span> <span class="no">IO</span><span class="o">::</span><span class="no">Buffer</span><span class="o">...</span>
<span class="n">header</span> <span class="o">=</span> <span class="no">Header</span><span class="p">.</span><span class="nf">new</span>
<span class="n">buffer</span><span class="p">.</span><span class="nf">unpack_into</span><span class="p">(</span><span class="n">header</span><span class="p">,</span> <span class="ss">:U16</span><span class="p">,</span> <span class="ss">:U32</span><span class="p">)</span>
<span class="c1"># internally, call rb_iv_set(header, 0, value); rb_iv_set(header, 1, value)</span>
</code></pre>
<p>What I'm asking for, is for some kinds of objects, can we consider the attributes to be indexed for efficiently writing into them in order?</p>
<p>If so, can we expose that interface, e.g. <code>rb_iv_set_indexed(VALUE object, int index, VALUE value)</code> or something.</p>
<p>For objects that don't support it, raising an exception would be fine. I imagine, both Struct and Data can support it.</p>
<p>I can make separate issue, but I felt like this was a good place to start the discussion.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=993392022-09-26T09:58:22Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/710">@zverok (Victor Shepelev)</a> The summary looks OK. I accepted.</p>
<p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/3344">@ioquatix (Samuel Williams)</a> Your proposal should be handled separately. Could you submit a new one?</p>
<p>Matz.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=993412022-09-26T12:15:26Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/710">@zverok (Victor Shepelev)</a> Could you add the (simple) NEWS entry too?</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=993712022-09-27T18:32:04Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/13">@matz (Yukihiro Matsumoto)</a> Thank you!<br>
<a class="user active user-mention" href="https://bugs.ruby-lang.org/users/4">@nobu (Nobuyoshi Nakada)</a> Done.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=994112022-09-30T09:36:37Znobu (Nobuyoshi Nakada)nobu@ruby-lang.org
<ul><li><strong>Status</strong> changed from <i>Assigned</i> to <i>Closed</i></li></ul> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=1004632022-12-03T16:24:12Zston1x (Nicolai Stoianov)stoianovnk@gmail.com
<ul></ul><p>Thanks a lot for implementing this feature! Can't wait to start applying it in specific use-cases.<br>
However, I am also wondering if it is possible to define such <code>Data</code>-derived classes in a "traditional way", meaning something like this, with inheritance or inclusion:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Ticket</span> <span class="o"><</span> <span class="no">Data</span>
<span class="c1"># "attrs" is just for example here, might be something different.</span>
<span class="n">attrs</span> <span class="ss">:event_id</span><span class="p">,</span> <span class="ss">:user_id</span><span class="p">,</span> <span class="ss">:start_at</span>
<span class="c1"># And other methods defined below</span>
<span class="k">def</span> <span class="nf">validate</span>
<span class="nb">puts</span> <span class="s2">"Validated!"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># And then just using it the same way as described in the PR:</span>
<span class="n">ticket</span> <span class="o">=</span> <span class="no">Ticket</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">event_id: </span><span class="mi">78</span><span class="p">,</span>
<span class="ss">user_id: </span><span class="mi">584</span><span class="p">,</span>
<span class="ss">start_at: </span><span class="s1">'2022-12-03 15:00:00'</span>
<span class="p">)</span>
</code></pre>
<p>I guess this might come in handy for IDEs and is simply common across codebases in Ruby.<br>
Please correct me if I'm wrong or if you've also considered similar assumptions but decided to not implement it on purpose.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=1004962022-12-05T07:22:37Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><p>I propose a little bit different approach:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Ticket</span> <span class="o">=</span> <span class="no">Data</span><span class="p">.</span><span class="nf">define</span><span class="p">(</span><span class="ss">:event_id</span><span class="p">,</span> <span class="ss">:user_id</span><span class="p">,</span><span class="ss">:start_at</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># And other methods defined below</span>
<span class="k">def</span> <span class="nf">validate</span>
<span class="nb">puts</span> <span class="s2">"Validated!"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>This is much consistent and work now without any enhancement.</p>
<p>Matz.</p> Ruby master - Feature #16122: Data: simple immutable value objecthttps://bugs.ruby-lang.org/issues/16122?journal_id=1005032022-12-05T18:43:47Zzverok (Victor Shepelev)zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/52584">@ston1x (Nicolai Stoianov)</a> I understand where you are coming from (make the declaration more homogenous for the sake of IDE's convenience), and I don't think that the goal is meaningless, but in this case, it will lead to severe complication of semantics/implementation: what's the state of the object between <code>Ticket < Data</code> and <code>attr</code>? What if <code>attr</code> never declared? What if it used several times? What if it used again in the descendant of <code>Ticket</code>?</p>
<p>This kind of complication doesn't seem worth the gain, for me. It is up to IDE to handle <code>Data</code>, it seems (as far as I know, say, YARD documentation system has a handling for <code>Struct</code> descendants, and it isn't impossible for other tools, too). Especially considering that a lot of <code>Data</code> usage would probably be just short one-line declarations and ideally, IDE that wants to autocomplete available attributes should understand them anyway, so...</p>