ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [ruby-core:94508] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
@ 2019-08-23 17:40 ` zverok.offline
  2019-08-24  6:55 ` [ruby-core:94526] " eregontp
                   ` (18 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-23 17:40 UTC (permalink / raw
  To: ruby-core

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

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

* Author: zverok (Victor Shepelev)
* Status: Open
* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94526] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
  2019-08-23 17:40 ` [ruby-core:94508] [Ruby master Feature#16122] Struct::Value: simple immutable value object zverok.offline
@ 2019-08-24  6:55 ` eregontp
  2019-08-24 10:17 ` [ruby-core:94527] " zverok.offline
                   ` (17 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: eregontp @ 2019-08-24  6:55 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by Eregon (Benoit Daloze).


This sounds interesting to me.
What would a simple implementation look like in Ruby code?
I'm not quite sure what the available API is since it's all described as Struct - some methods.

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

* Author: zverok (Victor Shepelev)
* Status: Open
* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94527] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
  2019-08-23 17:40 ` [ruby-core:94508] [Ruby master Feature#16122] Struct::Value: simple immutable value object zverok.offline
  2019-08-24  6:55 ` [ruby-core:94526] " eregontp
@ 2019-08-24 10:17 ` zverok.offline
  2019-08-24 10:37 ` [ruby-core:94528] " zverok.offline
                   ` (16 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-24 10:17 UTC (permalink / raw
  To: ruby-core

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


@Eregon, here is rendered version of class' docs: https://zverok.github.io/ruby-rdoc/Struct-Value.html

Basically, it is what is said on the tin: like Struct, just leaner.

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

* Author: zverok (Victor Shepelev)
* Status: Open
* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94528] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (2 preceding siblings ...)
  2019-08-24 10:17 ` [ruby-core:94527] " zverok.offline
@ 2019-08-24 10:37 ` zverok.offline
  2019-08-29  8:01 ` [ruby-core:94661] " matz
                   ` (15 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-24 10:37 UTC (permalink / raw
  To: ruby-core

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


> What would a simple implementation of `Struct::Value.new` look like in Ruby code?

Oh, I've probably answered the wrong question... But I am not quite sure I understand yours.

Theoretically, it is just something like this (ignoring the fact that `Struct`s implementation has optimized storage and other tricks, and any input validation):

```ruby
class Struct::Value
  def self.new(*args, keyword_init: false)
    name, *members = args.first.is_a?(String) ? args : [nil, *args]
    Class.new(self) do
      @members = members

      def self.new(*args)
        allocate.tap { |o| o.__send__(:initialize, *args) }
      end

      members.each { |m| define_method(m) { instance_variable_get("@#{m}") }}
    end.tap { |cls| const_set(name, cls) if name }
  end
# ....
```

So, (if that's what you've asking) it produces object of different class, `Struct::Value`, unrelated to `Struct`, but sharing most of the implementation.

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

* Author: zverok (Victor Shepelev)
* Status: Open
* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94661] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (3 preceding siblings ...)
  2019-08-24 10:37 ` [ruby-core:94528] " zverok.offline
@ 2019-08-29  8:01 ` matz
  2019-08-29  9:35 ` [ruby-core:94662] " zverok.offline
                   ` (14 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: matz @ 2019-08-29  8:01 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by matz (Yukihiro Matsumoto).

Status changed from Open to Feedback

The typical solution is `Struct.new(...).freeze`. This doesn't require any enhancement. The other option is `Struct.new(..., immutable: false)`. It looks simpler than the proposed `Struct::Value`.

Matz.


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

* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94662] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (4 preceding siblings ...)
  2019-08-29  8:01 ` [ruby-core:94661] " matz
@ 2019-08-29  9:35 ` zverok.offline
  2019-08-29 12:50 ` [ruby-core:94665] " daniel
                   ` (13 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-29  9:35 UTC (permalink / raw
  To: ruby-core

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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94665] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (5 preceding siblings ...)
  2019-08-29  9:35 ` [ruby-core:94662] " zverok.offline
@ 2019-08-29 12:50 ` daniel
  2019-08-29 12:55 ` [ruby-core:94666] " zverok.offline
                   ` (12 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: daniel @ 2019-08-29 12:50 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by Dan0042 (Daniel DeLorme).


If I understand correctly, the idea is to have `X=Struct::Value.new(:x,:y,:z)` which is strictly equivalent to

```ruby
class X
  def initialize(x=nil, y=nil, z=nil)
    @x,@y,@z = x,y,z
  end
  attr_reader :x, :y, :z
end
```

Or was there some nuance I didn't catch?

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

* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94666] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (6 preceding siblings ...)
  2019-08-29 12:50 ` [ruby-core:94665] " daniel
@ 2019-08-29 12:55 ` zverok.offline
  2019-08-29 14:14 ` [ruby-core:94667] " mame
                   ` (11 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-29 12:55 UTC (permalink / raw
  To: ruby-core

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


@Dan0042 you are (probably) missing `#inspect`, `#==`, `#eql?`, `#hash`, `#to_h` and a bunch of other methods that are pretty trivial, but also important for the "value object".

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

* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94667] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (7 preceding siblings ...)
  2019-08-29 12:55 ` [ruby-core:94666] " zverok.offline
@ 2019-08-29 14:14 ` mame
  2019-08-29 14:46 ` [ruby-core:94668] " zverok.offline
                   ` (10 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: mame @ 2019-08-29 14:14 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by mame (Yusuke Endoh).


I couldn't understand what is "value object", and I found: https://martinfowler.com/bliki/ValueObject.html
Please do not assume that everybody knows such an essay ;-)
No one pointed out the article during the developer's meeting, so we cannot understand what you want.

I have some comments:

* 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 `Struct::Value` 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.
* The behavior of `Struct::Value` is too different from `Struct`.  Another class name (like `ValueClass` or `NamedTuple` or what not) looks more suitable.
* 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.

BTW, I understand the motivation of the proposal.  I want "structural equality" in Ruby.  Personally, I often write:

```
class Point3D
  include StructuralEquality
  def initialize(x, y, z)
    @x, @y, @z = x, y, z
  end
end

foo1 = Point3D.new(1, 2, 3)
foo2 = Point3D.new(1, 2, 3)
p foo1 == foo2 #=> true
h = { foo1 => "ok" }
p h[foo2] #=> "ok"
```

(The definition of `StructuralEquality` is here: https://github.com/mame/ruby-type-profiler/blob/436a10787fc74db47a8b2e9db995aa6ef7c16311/lib/type-profiler/utils.rb#L8-L31 )

But, I'm unsure if it deserves a built-in feature.

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

* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94668] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (8 preceding siblings ...)
  2019-08-29 14:14 ` [ruby-core:94667] " mame
@ 2019-08-29 14:46 ` zverok.offline
  2019-08-29 20:33 ` [ruby-core:94672] " zverok.offline
                   ` (9 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-29 14:46 UTC (permalink / raw
  To: ruby-core

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


@mame I understand your concerns. I'll update the description today or tomorrow to include all the terminology and detailed rationale behind the proposal.

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

* 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/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94672] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (9 preceding siblings ...)
  2019-08-29 14:46 ` [ruby-core:94668] " zverok.offline
@ 2019-08-29 20:33 ` zverok.offline
  2019-08-30  0:09 ` [ruby-core:94674] " naruse
                   ` (8 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-29 20:33 UTC (permalink / raw
  To: ruby-core

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

Description updated

@mame, @matz, I updated the description, tried to include a proper rationale for every design decision made.

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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94674] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (10 preceding siblings ...)
  2019-08-29 20:33 ` [ruby-core:94672] " zverok.offline
@ 2019-08-30  0:09 ` naruse
  2019-08-30  7:05 ` [ruby-core:94679] " zverok.offline
                   ` (7 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: naruse @ 2019-08-30  0:09 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by naruse (Yui NARUSE).


> 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.

I'm using immutable_struct.gem.

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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94679] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (11 preceding siblings ...)
  2019-08-30  0:09 ` [ruby-core:94674] " naruse
@ 2019-08-30  7:05 ` zverok.offline
  2019-08-30 13:15 ` [ruby-core:94683] " daniel
                   ` (6 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-30  7:05 UTC (permalink / raw
  To: ruby-core

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


@naruse Of course, there _are_ 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.

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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94683] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (12 preceding siblings ...)
  2019-08-30  7:05 ` [ruby-core:94679] " zverok.offline
@ 2019-08-30 13:15 ` daniel
  2019-08-30 14:55 ` [ruby-core:94685] " zverok.offline
                   ` (5 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: daniel @ 2019-08-30 13:15 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by Dan0042 (Daniel DeLorme).


Question: you say "Doesn't think of itself as almost hash" but at the same time you say it should have `to_h`. Isn't that a contradiction?

Naming suggestion: BasicStruct (in parallel to Object and BasicObject)

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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94685] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (13 preceding siblings ...)
  2019-08-30 13:15 ` [ruby-core:94683] " daniel
@ 2019-08-30 14:55 ` zverok.offline
  2019-08-30 18:26 ` [ruby-core:94690] " daniel
                   ` (4 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-08-30 14:55 UTC (permalink / raw
  To: ruby-core

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


@Dan0042

> Question: you say "Doesn't think of itself as almost hash" but at the same time you say it should have `to_h`. Isn't that a contradiction? 

Nope. An object that has `to_h` 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 `to_s` but that doesn't make them "something like String". 

But "mimicking" some of the Hash API (with `[]` and `values` and `values_at`) makes the object responsibility less focused.

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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94690] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (14 preceding siblings ...)
  2019-08-30 14:55 ` [ruby-core:94685] " zverok.offline
@ 2019-08-30 18:26 ` daniel
  2019-09-03 15:56 ` [ruby-core:94762] " dementiev.vm
                   ` (3 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: daniel @ 2019-08-30 18:26 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by Dan0042 (Daniel DeLorme).


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 :-/

     Struct.new(:x).new(42).values_at(0)  #=> [42]
     Struct.new(:x).new(42).values_at(:x) #=> TypeError


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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94762] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (15 preceding siblings ...)
  2019-08-30 18:26 ` [ruby-core:94690] " daniel
@ 2019-09-03 15:56 ` dementiev.vm
  2019-09-07 19:56 ` [ruby-core:94832] " zverok.offline
                   ` (2 subsequent siblings)
  19 siblings, 0 replies; 20+ messages in thread
From: dementiev.vm @ 2019-09-03 15:56 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by palkan (Vladimir Dementyev).


zverok (Victor Shepelev) wrote:
> **Why not a gem?**
> 
> * 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.

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 `dry-initializer`, 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).

If there is still no such a gem then there is no enough demand for the feature itself.

So, why pushing it to the core?


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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:94832] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (16 preceding siblings ...)
  2019-09-03 15:56 ` [ruby-core:94762] " dementiev.vm
@ 2019-09-07 19:56 ` zverok.offline
  2019-10-30  2:46 ` [ruby-core:95586] " daniel
  2019-11-28  8:40 ` [ruby-core:96017] " matz
  19 siblings, 0 replies; 20+ messages in thread
From: zverok.offline @ 2019-09-07 19:56 UTC (permalink / raw
  To: ruby-core

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


@palkan I have a strong **feeling** 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.

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 _tinsy bit more convenient._"

In other words, I am proposing to introduce the concept, not implementation.

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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:95586] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (17 preceding siblings ...)
  2019-09-07 19:56 ` [ruby-core:94832] " zverok.offline
@ 2019-10-30  2:46 ` daniel
  2019-11-28  8:40 ` [ruby-core:96017] " matz
  19 siblings, 0 replies; 20+ messages in thread
From: daniel @ 2019-10-30  2:46 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by Dan0042 (Daniel DeLorme).


zverok (Victor Shepelev) wrote:
> So, considering all the points above, it could be either _multiple_ settings: `immutable: true, enumerable: false, hash_accessors: false`

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 `Array(mystruct)` bug shown above), the `Struct::Value` approach doesn't work. If you find yourself always repeating the same options, it's trivial to write your own `ValueStruct` helper function.

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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [ruby-core:96017] [Ruby master Feature#16122] Struct::Value: simple immutable value object
       [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
                   ` (18 preceding siblings ...)
  2019-10-30  2:46 ` [ruby-core:95586] " daniel
@ 2019-11-28  8:40 ` matz
  19 siblings, 0 replies; 20+ messages in thread
From: matz @ 2019-11-28  8:40 UTC (permalink / raw
  To: ruby-core

Issue #16122 has been updated by matz (Yukihiro Matsumoto).


I like the idea of helpers in https://bugs.ruby-lang.org/issues/16122#note-18.
We need to discuss further the combination of attributes (immutable, enumerable, etc.)

Matz.


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

* Author: zverok (Victor Shepelev)
* Status: Feedback
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
**Value Object** is a useful concept, introduced by Martin Fowler ([his post](https://martinfowler.com/bliki/ValueObject.html), [Wikipedia Entry](https://en.wikipedia.org/wiki/Value_object)) with the following properties (simplifying the idea):

* representing some relatively simple data;
* immutable;
* compared by type & value;
* nicely represented.

Value objects are super-useful especially for defining APIs, their input/return values. Recently, there were some movement towards using more immutability-friendly approach in Ruby programming, leading to creating several discussions/libraries with value objects. For example, [Tom Dalling's gem](https://github.com/tomdalling/value_semantics), [Good Ruby Value object convention](https://github.com/zverok/good-value-object) (disclaimer: the latter is maintained by yours truly).

I propose to introduce **native value objects** to Ruby as a core class.

**Why not a gem?**

* 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.
* Potentially, a lot of standard library (and probably even core) APIs could benefit from the concept.

**Why `Struct` is not enough**

Core `Struct` class is "somewhat alike" value-object, and frequently used instead of one: it is compared by value and consists of simple attributes. On the other hand, `Struct` is:
* mutable;
* collection-alike (defines `to_a` and is `Enumerable`);
* dictionary-alike (has `[]` and `.values` methods).

The above traits somehow erodes the semantics, making code less clear, especially when duck-typing is used.

For example, this code snippet shows why `to_a` is problematic:

```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']
```

Having `[]` and `each` defined on something that is thought as "just value" can also lead to subtle bugs, when some method checks "if the received argument is collection-alike", and value object's author doesn't thought of it as a collection.

**Concrete proposal**

* Class name: `Struct::Value`: lot of Rubyists are used to have `Struct` as a quick "something-like-value" drop-in, so alternative, more strict implementation, being part of `Struct` API, will be quite discoverable; *alternative: just `Value`*
* Class API is copying `Struct`s one (most of the time -- even reuses the implementation), with the following exceptions *(note: the immutability is **not** the only difference)*:
  * 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. And if it will evolve to have some attributes, the code change would be easy.

**Patch is provided**

[Sample rendered RDoc documentation](https://zverok.github.io/ruby-rdoc/Struct-Value.html)

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


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

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2019-11-28  8:40 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <redmine.issue-16122.20190823174036@ruby-lang.org>
2019-08-23 17:40 ` [ruby-core:94508] [Ruby master Feature#16122] Struct::Value: simple immutable value object zverok.offline
2019-08-24  6:55 ` [ruby-core:94526] " eregontp
2019-08-24 10:17 ` [ruby-core:94527] " zverok.offline
2019-08-24 10:37 ` [ruby-core:94528] " zverok.offline
2019-08-29  8:01 ` [ruby-core:94661] " matz
2019-08-29  9:35 ` [ruby-core:94662] " zverok.offline
2019-08-29 12:50 ` [ruby-core:94665] " daniel
2019-08-29 12:55 ` [ruby-core:94666] " zverok.offline
2019-08-29 14:14 ` [ruby-core:94667] " mame
2019-08-29 14:46 ` [ruby-core:94668] " zverok.offline
2019-08-29 20:33 ` [ruby-core:94672] " zverok.offline
2019-08-30  0:09 ` [ruby-core:94674] " naruse
2019-08-30  7:05 ` [ruby-core:94679] " zverok.offline
2019-08-30 13:15 ` [ruby-core:94683] " daniel
2019-08-30 14:55 ` [ruby-core:94685] " zverok.offline
2019-08-30 18:26 ` [ruby-core:94690] " daniel
2019-09-03 15:56 ` [ruby-core:94762] " dementiev.vm
2019-09-07 19:56 ` [ruby-core:94832] " zverok.offline
2019-10-30  2:46 ` [ruby-core:95586] " daniel
2019-11-28  8:40 ` [ruby-core:96017] " matz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).