Issue #16122 has been updated by zverok (Victor Shepelev).


@matz 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).

So, it went as following:

1\. First, I really wanted just `Struct.new(..., immutable: false)` (and even experimented for some time with a private monkey-patch, doing just that)

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 `to_a`. Simple example:

  ```ruby
  Result = Struct.new(:success, :content)

  # Now, imagine that other code assumes `data` could be either Result, or [Result, Result, Result]
  # So, ...

  data = Result.new(true, 'it is awesome')

  Array(data) # => expected [Result(true, 'it is awesome')], got [true, 'it is awesome']

  # or...
  def foo(arg1, arg2 = nil)
    p arg1, arg2
  end

  foo(*data) # => expected [Result(true, 'it is awesome'), nil], got [true, 'it is awesome']
  ```
3\. And generally, some random value object "duck typing" itself as a collection seems not really appropriate.

4\. The same, I believe, is related to supporting `[:foo]` and `['foo']` accessors: convenient for "general content object" that `Struct` is, but for "just value" it could seem an unnecessary expansion of the interface.

5\. Finally, empty-member `Value` is allowed, while empty-member `Struct` somehow does not (I don't know if it is by design or just a bug, as I am mentioning above, `Struct.new('Name')` IS allowed, but `Struct.new` is NOT).

So, considering all the points above, it could be either _multiple_ settings: `immutable: true, enumerable: false, hash_accessors: false` (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?..). 

So I eventually decided to propose going another way.

----------------------------------------
Feature #16122: Struct::Value: simple immutable value object
https://bugs.ruby-lang.org/issues/16122#change-81265

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Currently, **Struct** behaves like a *value object*:
* it is created from simple attributes;
* its equality and representation based only on those attributes.

But also, it behaves like a **mutable object** (allows to set attributes), and **as a kind-of collection** (includes `Enumerable`, has `to_a` and `[]` accessors).

And while `Struct` is kinda useful as is, I found that in a lot of cases what I really *mean* creating a Struct is just creating a pure, immutable value object, that is *just it*. There are a lot of gems that go this or that far to provide "value object" concept, but in fact, the concept is so simple that typically nobody will install the gem to just have it -- that's why I believe it should be in language core.

So, here is the proposal **and its C implementation:**

* Class `Struct::Value`, which shares most of the implementation with a `Struct` (but is neither subclass nor superclass of it);
* Unlike struct, it is: 
  * Not Enumerable;
  * Immutable;
  * Doesn't think of itself as "almost hash" (doesn't have `to_a`, `values` and `[]` methods);
  * Can have empty members list (fun fact: `Struct.new('Foo')` creating member-less `Struct::Foo`, is allowed, but `Struct.new()` is not) to allow usage patterns like:

```ruby
class MyService
  Success = Struct::Value.new(:results)
  NotFound = Struct::Value.new
end
```
`NotFound` here, unlike, say, `Object.new.freeze` (another pattern for creating "empty typed value object"), has nice inspect `#<value NotFound>`, and created consistently with the `Success`, making the code more readable.

---Files--------------------------------
struct_value.patch (18.6 KB)


-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>