Feature #12719
closed`Struct#merge` for partial updates
Added by halogenandtoast (Matthew Mongeau) about 9 years ago. Updated about 4 years ago.
Description
Other languages have operators for performing partial updates on maps. I feel like Struct could be more useful if it provided an easy way of performing partial (or full) updates.
After the change you can do the following:
Point = Struct.new(:x, :y)
p = Point.new(1, 2)
p2 = p.merge(y: 4)
p3 = p2.merge(x: 10)
p.inspect     # => #<struct Point x=1, y=2>
p2.inspect   # => #<struct Point x=1, y=4>
p3.inspect   # => #<struct Point x=10, y=4>
p.merge!("x" => 9)
p.inspect    # => #<struct Point x=9, y=2>
Files
| struct_merge.patch (2.93 KB) struct_merge.patch | halogenandtoast (Matthew Mongeau), 09/05/2016 02:55 AM | 
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #1
            [ruby-core:77126]
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #1
            [ruby-core:77126]
        
      
      - Tracker changed from Bug to Feature
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #2
            [ruby-core:77127]
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #2
            [ruby-core:77127]
        
      
      If this is well received I think a similar syntax could be used for hashes in place of merge.
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #3
            [ruby-core:77129]
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #3
            [ruby-core:77129]
        
      
      - File struct_merge.patch added
As an alternative since the | syntax might get shot down. Here's a patch adding a merge function instead:
Point = Struct.new(:x, :y)
p = Point.new(1, 2)
p2 = p.merge(y: 4)
p3 = p2.merge(x: 10)
puts p.inspect # => #<struct Point x=1, y=2>
puts p2.inspect # => #<struct Point x=1, y=4>
puts p3.inspect # => #<struct Point x=10, y=4>
        
           Updated by nobu (Nobuyoshi Nakada) about 9 years ago
          
          
        
        
          
            Actions
          
          #4
            [ruby-core:77131]
          Updated by nobu (Nobuyoshi Nakada) about 9 years ago
          
          
        
        
          
            Actions
          
          #4
            [ruby-core:77131]
        
      
      In your example, the value in the LHS is ignored when the same key is present in the RHS hash.
It doesn't feel nice as | operator.
merge sounds nice in that sense, but your patch would segfault at p.merge(0).
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #5
            [ruby-core:77132]
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #5
            [ruby-core:77132]
        
      
      Thanks, nice catch. I'll update this tomorrow to not segfault.
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #6
            [ruby-core:77136]
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #6
            [ruby-core:77136]
        
      
      - File struct_merge_no_segfault.patch added
Updated so it won't segfault
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #7
            [ruby-core:77150]
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #7
            [ruby-core:77150]
        
      
      - File merge_bang.patch added
Since merge closely resembles the similar hash function, I think it makes sense to also add merge! as a function. I'm not a fan of the mutating methods, but I would find it surprising if this interface was different. Here's an updated patch. I also fixed some of the documentation I wrote.
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #8
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #8
        
      
      - File deleted (struct_update.patch)
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #9
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #9
        
      
      - File deleted (struct_merge.patch)
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #10
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #10
        
      
      - File deleted (struct_merge_no_segfault.patch)
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #11
            [ruby-core:77152]
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #11
            [ruby-core:77152]
        
      
      - File struct_merge.patch struct_merge.patch added
- Subject changed from `Struct#|` for partial updates to `Struct#merge` for partial updates
- Description updated (diff)
Update ChangeLog
        
           Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #12
          Updated by halogenandtoast (Matthew Mongeau) about 9 years ago
          
          
        
        
          
            Actions
          
          #12
        
      
      - File deleted (merge_bang.patch)
        
           Updated by matz (Yukihiro Matsumoto) almost 9 years ago
          
          
        
        
          
            Actions
          
          #13
            [ruby-core:78337]
          Updated by matz (Yukihiro Matsumoto) almost 9 years ago
          
          
        
        
          
            Actions
          
          #13
            [ruby-core:78337]
        
      
      - Status changed from Open to Feedback
I want to see a real-world use-case for the feature.
In addition, I don't think the name merge is the best for the functionality.
Matz.
        
           Updated by Eregon (Benoit Daloze) almost 9 years ago
          
          
        
        
          
            Actions
          
          #14
            [ruby-core:78658]
          Updated by Eregon (Benoit Daloze) almost 9 years ago
          
          
        
        
          
            Actions
          
          #14
            [ruby-core:78658]
        
      
      Scala has "copy" for this purpose: some_case_class_object.copy(field: new_value)
        
           Updated by marcotc (Marco Costa) about 4 years ago
          
          
        
        
          
            Actions
          
          #15
            [ruby-core:105495]
          Updated by marcotc (Marco Costa) about 4 years ago
          
          
        
        
          
            Actions
          
          #15
            [ruby-core:105495]
        
      
      To add a real-world use-case: in the ddtrace gem we have a configuration Struct called AgentSettings that holds the global defaults for our configuration:
AgentSettings = Struct.new(
  :ssl,
  :hostname,
  :port,
  :timeout_seconds,
  :deprecated_for_removal_transport_configuration_proc,
  :deprecated_for_removal_transport_configuration_options
) do
  def initialize(
    ssl:,
    hostname:,
    port:,
    timeout_seconds:,
    deprecated_for_removal_transport_configuration_proc:,
    deprecated_for_removal_transport_configuration_options:,
  )
    super(ssl, hostname, port, timeout_seconds, deprecated_for_removal_transport_configuration_proc, \
              deprecated_for_removal_transport_configuration_options)
    freeze
  end
end
But in a few places we want to override some of the default configuration values:
transport.adapter(
  default_adapter,
  agent_settings.hostname,
  agent_settings.port,
  # We explicitly use profiling_upload_timeout_seconds instead of agent_settings.timeout because profile
  # uploads are bigger and thus we employ a separate configuration.
  timeout: profiling_upload_timeout_seconds,
  ssl: agent_settings.ssl
)
Today we resort to unpacking the Struct and effectively transferring the values to a hash (in the form of keyword arguments). We don't actually want our Struct to become a hash (or keyword arguments) when we pass it to #adapter in the example above, we would like a AgentSettings struct to be passed but with timeout overridden.