Brandon Weaver
Posted on • Updated on
Victor Shepelev (Zverok) has just landed an extremely useful feature in Ruby, Data.define
. You can find the merge here:
https://github.com/ruby/ruby/pull/6353
...and the Ruby discussion here:
https://bugs.ruby-lang.org/issues/16122
So what is it and what does it do? Well that's what we're going to take a look into.
What it Does
Succinctly put Data.define
creates an immutable Struct
-like type which can be initialized with either positional or keyword arguments:
Point = Data.define(:x, :y)origin = Point.new(0, 0)north_of_origin = Point.new(x: 0, y: 10)
Note: This does not inherit from
Struct
...and because the API supports pattern matching you can very much do things like this still:
case originin Point[x: 0 => x, y] # rightward assignment, pattern matching Point.new(x:, y: y + 5) # shorthand hash syntaxelse originend
It can also take a block for creating additional methods if you'd like, much like Struct
:
Point = Data.define(:x, :y) do def +(other) = new(self.x + other.x, self.y + other.y)end
Then Why Not Struct?
You can absolutely use Struct
still. Given that why should you use Data
instead?
Because it's stricter, and the values it produces are immutable. With the advent of more functional patterns in Ruby this can be a very useful thing.
Consider data passing in Ractors which requires immutable state, this becomes incredibly useful for value objects and message passing for what may inevitably be our next generation of Puma and other web servers.
Why not dry-rb?
Interestingly the dry-rb
folks are very pragmatic about things like this. In fact some of their core folks are already talking about how to wrap this new feature into their immutable structs to build on top of it, rather than in parallel:
https://twitter.com/solnic29a/status/1577683251086376963
They did the same with pattern matching, and it's great to see how we build on top of what the language formally adopts.
What Do You Think of It?
Personally? I like it. I plan to use it a lot in the REPL whenever I need a quick data type I can match against without pulling out a full Struct
for it as often times I don't intend to mutate it and the keyword_init
flag can be a bit pesky to remember.
It reminds me a lot of case classes from Scala:
case class Book(isbn: String)val frankenstein = Book("978-0486282114")
...and of data classes from Kotlin:
data class User(val name: String = "", val age: Int = 0)
So this is not a novel concept, but a useful one for Ruby to adopt.
Closing Thoughts
While the new language features have certainly slowed, in accordance with Matz's focus on tooling, we're still seeing some interesting movement in the language core. Of course there's a lot of fascinating tooling too, and I'm thrilled to see those coming to fruition, and perhaps soon I'll write about them as well.
In the mean time thanks to Zverok for seeing this through, and everyone who participated!
Top comments (9)
Subscribe
Billy
Billy
Freelancer, Ruby prograrmmer, Linux Geek.
-
Location
See AlsoRuby 3.2.0 Preview 2 ReleasedWhat's new in Ruby 3.2Ruby on Rails 3.2 Release Notes — Ruby on Rails GuidesRuby on Rails 3.2 Release Notes — Ruby on Rails GuidesShangHai
-
Work
Senior Ruby programmer. at Airhost
-
Joined
• Dec 15 '22
- Copy link
This Data.define
stolen from Crystal. The key different with Struct is:
Struct is just a conveniently way to create a class, it is reference, create on heap.
Data.define is a value, create on stack.
Janko Marohnić
Janko Marohnić
Ruby-off-Rails evangelist, open source contributor
-
Email
janko@hey.com
-
Location
Brno, Czechia
-
Education
Mathematics and Computer Science
-
Work
Senior Ruby Engineer
-
Joined
• Oct 6 '22 • Edited on Oct 6 • Edited
- Copy link
I love it, I was excited to see this alternative implementation of Struct. However, you noted in this article that the Data
class inherits from Struct
, whereas I understood from the PR that it inherits from Object
. How did you find that out? The docs also seem to indicate that Data
is an alternative implementation:
See also
Struct
, which is a similar concept, but has more container-alike API, allowing to change contents of the object and enumerate it.
Brandon Weaver
Brandon Weaver
Senior Staff Eng at One Medical. Autistic / ADHD, He / Him. I'm the Lemur guy.
-
Location
San Francisco, CA
-
Work
Senior Staff Eng at One Medical
-
Joined
• Oct 13 '22
- Copy link
Article amended to reflect that. In the past it was going to loosely inherit it so I must have missed that fork
Tjad Clark
Tjad Clark
-
Joined
• Oct 15 '22
- Copy link
In the source code, it is written in the struct.c file - there is some relation
Tjad Clark
Tjad Clark
-
Joined
• Oct 10 '22
- Copy link
Based on this post, I was playing around with Data, thinking that it would be a good fit for Ractor
. The idea that Data is immutable is sound, however the reality is that it may be misleading, as to fully gain advantage of Ractor
communication, the object needs to be fully immutable (or deeply immutable). So by default Data
instances will still be copied to each instance of Ractor
, unless Ractor.make_shareable
was called to deep freeze the data instance.
Bart de Water
Bart de Water
-
Joined
• Oct 20 '22
- Copy link
It would be useful to have a with
method to clone an instance with new data (like Sorbet's T::Struct): bugs.ruby-lang.org/issues/19000
Martin Chabot
Martin Chabot
-
Joined
• Jan 6 '23
- Copy link
The only thing that bugs me is that is that there's no way to set default values.
niku
niku
A programmer live in Hokkaido, Japan.
-
Location
Hokkaido, Japan
-
Joined
• Feb 27 '23 • Edited on Mar 11 • Edited
- Copy link
You can override initialize method to set default values.
Point = Data.define(:x, :y) do def initialize(x: 0, y: 1) super endendp1 = Point.newp p1 # => <data Point x=0, y=1>p2 = Point.new(x: 3, y: 4)p p2 # => <data Point x=3, y=4>
docs.ruby-lang.org/en/3.2/Data.htm...
Note that Measure#initialize always receives keyword arguments, and that mandatory arguments are checked in initialize, not in new. This can be important for redefining initialize in order to convert arguments or provide defaults:
hope it helps.
For further actions, you may consider blocking this person and/or reporting abuse