11 ms·
Ruby adds a core class called Data to represent simple immutable value objects
- Alifatisk 4y agoI believe this is a part of 3.2 and not 3.1? Couldn’t find it in the docs anyway.
- e12e 4y agoYes, the post is wrong (to reference 3.1): https://docs.ruby-lang.org/en/master/NEWS_md.html https://docs.ruby-lang.org/en/master/NEWS_md.html https://docs.ruby-lang.org/en/master/Data.html https://docs.ruby-lang.org/en/master/Data.html
- phs 4y agoIt seems to have been around unofficially for some time before that even. https://stackoverflow.com/q/7306004/580412 https://stackoverflow.com/q/7306004/580412
- e12e 4y ago> @nobu (Nobuyoshi Nakada) proposed Data, 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. https://bugs.ruby-lang.org/issues/16122 https://bugs.ruby-lang.org/issues/16122 Ed: https://ruby-doc.org/core-2.5.0/Data.html https://ruby-doc.org/core-2.5.0/Data.html > Data > This is a deprecated class, base class for C extensions using Data_Make_Struct or Data_Wrap_Struct. Vs https://ruby-doc.org/core-2.4.0/Data.html https://ruby-doc.org/core-2.4.0/Data.html > Data > This is a recommended base class for C extensions using Data_Make_Struct or Data_Wrap_Struct, see doc/extension.rdoc for details.
- yunohn 4y ago> irb(main):005:0> john.name = "John Doe" > (irb):5:in `<main>': undefined method `name=' for <...> (NoMethodError) > Did you mean? name This error message is a bit roundabout. I assume the implication is that `name` is the getter and `name=` is the setter. And so, immutable objects don't have the setter method? But definitely could be worded much better than NoMethodError. Disclaimer: I'm more of a Python/JS/TS person, and haven't used Ruby much.
- chrisseaton 4y ago> But definitely could be worded much better than NoMethodError. But that is the root cause - there is no such method. The thing about 'Did you mean? name' is just a guess to help you.
- ohbtvz 4y ago> The thing about 'Did you mean? name' is just a guess to help you. And instead it throws anyone who hasn't extensive experience with Ruby's quirks into a spiral of misery.
- chc 4y agoThis is a pretty basic facet of Ruby. You do not need extensive experience to be familiar with Ruby's approach to getters and setters. This will be surprising to people whose expectations come from other languages, but I doubt it would surprise any Ruby developer.
- djur 4y agoAgreed. The second chapter of Programming Ruby, "Classes, Objects, and Variables", shows you how to manually implement 'attributes' before introducing you to the attr_* methods. This is fundamental stuff.
- bhaak 4y agoHow so? That's a day 1 experience for Ruby. Typos and calls for the methods on the wrong objects will always get you a NoMethodError. Anybody past their first Ruby tutorial will be familiar with this exception.
- deleted 4y ago[deleted]
- Karunamon 4y agoThis is the kind of problem you encounter once, scratch your head, look it up on stack overflow, understand why it is, and then commit this little quirk to memory and it never really troubles you again. (Or more realistically, your IDE underlines your error in red and you never even see the error message)
- nine_k 4y agoI wonder if immutable strings are considered.
- corytheboyd 4y agoI love Ruby, but I honestly don't get the point of Struct and now Data. You can do the same thing as both Struct and Data with Class. An immutable data container is a Class without setter functions. Whenever you start with a Struct that now needs methods to operate on private data, you just have a Class. Whatever, I still love you Ruby. You can always just not use these things and life goes on.
- chc 4y agoData is shorter to write and gives you automatic structural equality. You really could do the same thing with a class, but if you want something exactly like Data, it's nice to have it built in.
- berkes 4y agoI'm a big fan of value objects. In Ruby, many of my projects have a simple baseclass, ValueObject, that is only a little simpler than Data. I love it that I can now use it from the language instead. It saves me just one class. At most five methods. But it saves me having to explain and defend this for colleagues who insist that an email is Just A String, or a price being a float (i know) has worked Just Fine. I can now point them at stdclass documentation. And it being part of std weighs far more than the opinion of that contractor that keeps on nagging about design patterns and crap.
- bfung 4y agoThey’re both shortcuts to creating class-like things. Take @dataclass in python for example, that’s similar to Struct for Ruby. ‘Data’ documentation from the GitHub PR (https://zverok.space/ruby-rdoc/Data.html https://zverok.space/ruby-rdoc/Data.html): Data provides no member writers, or enumerators: it is meant to be a storage for immutable atomic values. But note that if some of data members is of a mutable class, Data does no additional immutability enforcement See also Struct, which is a similar concept, but has more container-alike API, allowing to change contents of the object and enumerate it.
- masklinn 4y ago
- otikik 4y agoThis makes me sad and confused. Isn't "Data" the worst possible name one could use for anything in programming? I am not exaggerating; it is literally the example I have always used. If Data is an immutable struct, call it ImmutableStruct. If that's too long, there's ImmuStruct or IStruct. Any of those names is better than Data. They are all more concise.
- corytheboyd 4y agoHell, Struct already accepts keyword argument options (keyword_init), they could have just added a new one (immutable: true) IMO ImmutableStruct is exactly as verbose as BasicObject, something that has been in Ruby for a long time now. ImmutableStruct is a massively better name for this.
- knodi123 4y ago> Struct already accepts keyword argument options (keyword_init), they could have just added a new one (immutable: true) The article explains the reason they didn't do that: Ruby maintainers decided that Struct could not be made to suit the requirements of a value object. The core Struct class is a “somewhat alike” value-object; it is compared by value and is, mutable collection-alike (defines to_a and is Enumerable) dictionary-alike (has [] and .values methods)
- inopinatus 4y agoSee https://bugs.ruby-lang.org/issues/16122 https://bugs.ruby-lang.org/issues/16122 for lengthy deliberation of the justification (in particular, of the relative utility vs merely enhancing Struct), and discussion of the naming options, but let's crucially note that Data itself is not an immutable struct, it is a factory for classes when the instances of those classes are intended to be immutable structs. Hence Product = Data.define(:name, :price) creates a Product class, most likely during initialisation; then in application code we could subsequently write cheese = Product.new("Wensleydale 200g", 499) and here it's cheese that is immutable; the point being, we're still naming our domain objects purposefully. In that light, I think it's appropriate for a language to use generic terms for fundamental elements. For example, almost everything is descended from Object and we're fine with it.
- _dain_ 4y ago>Discussions started in Ruby forums to build a data model on the principles of the Value Object, introduced by Martin Fowler. are they trying to say that martin fowler invented immutable data structures???
- borski 4y agoNo, they’re trying to say that Martin Fowler introduced the Value Object.
- cratermoon 4y agoValue Object is older than that. It was called Whole Value in Ward Cunningham's CHECKS Pattern Language of Information Integrity, published in 1994
- eddsh1994 4y agoValue objects are equal if they have the same values (although are still different objects) - there's a little more to it than simply immutable data structures.
- colonwqbang 4y agoWell, what is it then? Is it something more than * An immutable composite value that * Has an equality operation not based on pointer identity Those have been known for a long time, at least 30 years. It is the default type of value of Haskell, a language that is older than Ruby.
- eddsh1994 4y agoYes, they can still be separate objects too https://wiki.haskell.org/Physical_equality https://wiki.haskell.org/Physical_equality
- capableweb 4y ago> Well, what is it then? Is it something more than Straight from the horse's mouth: https://martinfowler.com/bliki/ValueObject.html https://martinfowler.com/bliki/ValueObject.html
- laurent123456 4y ago> (irb):5:in `<main>': undefined method `name=' for #<data Person name="John", dob=#<Date: 1990-01-01 ((2447893j,0s,0n),+0s,2299161j)>> (NoMethodError) > Did you mean? name Is that the best they could do to express that `person.name` is readonly? The "did you mean" part especially is really where they could add info that would make sense to a human, but clearly that didn't happen.
- TulliusCicero 4y agoProgramming error messages are almost always so terrible.
- eropple 4y agoSo I get your point, but to this reasonably-Ruby-familiar human, this is not surprising. `name=` is the name of the setter method that you'd need to have here if you were to set the field; it doesn't exist, so Ruby's telling you so. It's a pretty common idiom. It could be improved--not easily; a setter method does not necessarily have to map to a data type, and Ruby itself doesn't know what you mean here because the Data type is in the stdlib but doesn't appear to be a compiler special case--but this is a bit nitpicking, and I would perhaps gently push back on changing the error message because it's representing what you really do need to know in Ruby once you're past the level one "driving Rails" usage of Ruby. Getting an error message other than this one would obfuscate what's happening and leave me as a long-time Ruby user pretty confused.
- tenderlove 4y agoMaybe we could do something like reparse the line and check the arity? We know the reader doesn't take a parameter, but the user clearly provided one, so maybe we could eliminate suggestions that have the wrong arity.
- eropple 4y agoThat's a neat idea! I might limit it to 0-arity vs. N-arity suggestions, to not create odd behavior for other situations (assuming Data is, as I expect, just Ruby and doesn't have any compiler special-sauce to it). I guess it would then also provide a better message for `attr_reader` vs `attr_accessor` usage, too, right?
- ryanseys 4y ago> However there is one caveat. If some of the data members are of a mutable class, Data does no additional immutability enforcement. Seems like an area of concern / gotchas. Either restrict Data to not allow mutable nested objects, or provide immutable versions of stdlib object types as well and enforce that those types are used. Yet another Rubocop-that-should-just-be-built-into-the-language coming in 3...2...1...
- lolinder 4y agoThis is pretty common when adding immutability to a language that wasn't designed around it. Whether it's JavaScript's `const` or Java's `final`, languages almost always add shallow immutability rather than deep. My sense is that it's quite difficult to add deep immutability in later. Rust is the main exception I'm aware of, and it had deep immutability from the get-go.
- capableweb 4y ago> Whether it's JavaScript's `const` const is JS is referring to the reference and has nothing to do with the value, it's not even shallow immutability, there is none at all. const arr = [] arr.push(1) is valid JavaScript. Bit nitpicky maybe, but wouldn't want people to get the impression const gives you any sort of immutability.
- masklinn 4y ago> wouldn't want people to get the impression const gives you any sort of immutability. const makes the binding immutable but not the object. Object.freeze makes the object immutable but not it’s children.
- cormacrelf 4y agoAccidentally mutating globals was a big problem with the language. Const didn't solve it completely: you can still implicitly create globals by forgetting to declare the binding locally. But at least if you have a global you can make it an immutable binding and get warned about mutating it some of the time (ie assignments). As far as I'm concerned that is all it is useful for. For a local binding you don't really get much benefit since it's not deeply immutable, and it isn't an improvement to the ambiguity of local/global scopes, so you're frankly better off not using it as it will trick newcomers. The same argument doesn't apply to Data's shallow immutability. It will give you errors when mutating at least some of the fields. If your code can catch you mutating a number, then you can notice the bug and be reminded to make deep copies etc. It's an improvement, just like Object.freeze.
- tjstankus 4y agoAs I type this, every top-level comment here is a criticism or a nitpick. A cool new feature is being added to Ruby. People have done difficult and meaningful work that benefits others. And this is the response here. I'm psyched for this feature. As someone who has worked primarily with Ruby and Elixir, I really appreciate the design and safety affordances of immutable data. I'm glad there's a bit more immutability in Ruby and that, in general, the language keeps progressing. Thank you Ruby language devs.
- fknorangesite 4y ago> As I type this, every top-level comment here is a criticism or a nitpick. This is maybe going to sound snarky, but honestly I really am being serious: at this point, what else do you expect from this website? HN has a well-earned reputation of being a bunch of kneejerk-critics and naysayers.
- tjstankus 4y agoI hear you and I agree. The HN vibe is nitpicky, for sure. I was a little surprised the comments here were 100% negative (or not positive, to be fair). I usually wouldn't bother reading all the comments, but I care about Ruby, so I was a little more irked by the negativity than usual. :)
- maxbond 4y agoPersonally, I do expect this behavior, but I also call it out, because I don't accept this behavior. For better or worse HN is my community. Its given me so much over the years, even as it's infuriated me much of the time. As I recognize patterns of dysfunction I highlight them, so that the community can understand & overcome them. And I do think I see incremental progress. Not to say I caused that progress, I have no idea what if any role I played from my modest soap box (I suspect very little), but I think there's every reason to hope for the better.
- nixpulvis 4y agoNot all critique is knee-jerk just because it comes quickly.
- prescriptivist 4y agoIf you want to use something like this right now (with a threadsafe bonus) concurrent-ruby has some useful specialized structs that offer similar behavior: * ImmutableStruct: Immutable struct where values are set at construction and cannot be changed later. * MutableStruct: Synchronized, mutable struct where values can be safely changed at any time. * SettableStruct: Synchronized, write-once struct where values can be set at most once, either at construction or any time thereafter. https://github.com/ruby-concurrency/concurrent-ruby https://github.com/ruby-concurrency/concurrent-ruby
- sacnoradhq 4y agoYep. There was no reason to add trivial functionality that already exists. Just something else to go wrong, support, and add bloat.
- kayodelycaon 4y agoSo… I was trying to figure out why freezing a Struct wasn’t a suitable solution here and the article pretty much answered it: Data is not enumerable. That alone is enough reason to create a separate class. Struct has completely wrong ergonomics for certain things. I’m sure there are a bunch of other reasons but I didn’t feel the need to dig further.
- TheDesolate0 4y ago
- sacnoradhq 4y agoRuby dev since early 00's. WTF. There's #frozen. It signals something is immutable. Why add another type this late into the game, add complexity, do it a completely different way, and break everything that came before with this unnecessary shit?
- debaserab2 4y agoer... whats #frozen?
- psychoslave 4y agoI guess it refers to {ENV,Module,Object,String}#freeze and #frozen. - https://ruby-doc.org/core-3.1.1/Object.html#method-i-freeze https://ruby-doc.org/core-3.1.1/Object.html#method-i-freeze - https://www.rubyguides.com/2016/01/ruby-mutability/ https://www.rubyguides.com/2016/01/ruby-mutability/
- JohnBooty 4y agobreak everything that came before How is this breaking anything, let alone everything? I'm not sure I see the value of Data either, but I also don't see how it breaks anything. One of us is severely misunderstanding it.
- throwaway2037 4y agoDid anyone else do a "double take" (what the...?) when they read this part: <<[the Value Object] has the following properties: Immutable, ..., Extensible>> ??? I'm confused. How can an immutable object be extensible? In Java parlance, would that mean something like: public /*not final!*/ class GinormousData { // all public final immutable members public final String name; public final int name; ... } Then, you are supposed to extend it? Weird. I never saw this style once. Is it a good idea? The scary part, since the class is not final, you need to constantly worry if someone did something crazy like inherit and add mutable fields. Then legacy code that operates on references of GinormousData is clueless. If I need to compose value objects, I would do this: public final class GinormousData { // all public final immutable members public final String name; public final int name; ... } public final class EvenBiggerData { public final GinormousData gdata; // other public final immutable members } Regardless of my language concerns above, this is great new feature for Ruby. When I write Python, I go wild with namedtuples and dataclasses(frozen=True). Love them immutable data objects -- so easy to reason about when reading code.
- lolinder 4y agoI don't know why you're being downvoted so hard—I've never seen a definition of values objects that includes extensibility. I'm not even really sure what the point would be with the way they're typically used.
- pfdietz 4y agoThe notion of value objects reminds me of certain types in Common Lisp, dating back to the definition of the language in the 1980s. The numeric and character types in Common Lisp are immutable value types. The standard says they may be copied at any time, so the object equality operator (eq) may or may not return true on them. There is a separate equality operator, eql, that performs value equality on value types (so two numbers whose value is the same integer, even if they are bignums with a representation stored on the heap, will return true when compared with eql.) Eql has the same meaning as eq on types that are not immutable value types. While this is not standardly possible in CL, it would be nice if users could extend operators like eql with new methods, thereby defining new immutable value types. A nice thing about these value types is they play well with distributed computation or with saving of values to offline storage.