Is there an abstraction for the declare-update-return pattern?

50 views Asked by At

When writing iterative code with mutation in ruby, I often find myself following this pattern:

def build_x some_data
  x = [] # or x = {}
  some_data.each do |data|
    x.some_in_place_update! (... data ...)
  end
  x
end

(x often does not have the same shape as some_data, so a simple map will not do.)

Is there a more idiomatic or better way to write code that follows this pattern?


[edit] A real example:

def to_hierarchy stuff
  h = {}
  stuff.each do |thing|
    path = thing.uri.split("/").drop(4)
    sub_h = h
    path.each do |segment|
      sub_h[segment] ||= {}
      sub_h = sub_h[segment]
    end
    sub_h.merge!(
      data: thing.data,
    )
  end
  h
end

This begins with a flat list of things, which have related but distinct uris. It transforms this flat list into a hierarchy, grouping related things that share the same segments of a uri. This follows the pattern I described: initialize h, loop over some data and mutate h along the way, and then spit out h at the end.

[edit2] Another related example

def count_data obj
  i = if obj[:data] then 1 else 0
  obj.each do |k, v|
    i += count_statements v unless :data == k
  end
  i
end
2

There are 2 answers

0
mu is too short On BEST ANSWER

Your to_hierarchy example could be done with each_with_object:

def to_hierarchy stuff
  stuff.each_with_object({}) do |thing, h|
    #...
  end
end

each_with_object passes the extra object to the block and returns that object when the iteration is done.

If you're more of a traditionalist, you could use inject:

def to_hierarchy stuff
  stuff.inject({}) do |h, thing|
    #...
    h
  end
end

Note the block argument order change and that the block has to return h so that inject can feed it back into the next block invocation.

Your general example could be written as:

def build_x some_data
  some_data.each_with_object([]) do |data, x|
    x.some_in_place_update! (... data ...)
  end
end

or:

def build_x some_data
  some_data.inject({}) do |x, data|
    x.some_in_place_update! (... data ...)
    x
  end
end
0
Borodin On

Ah! You want each_with_object. Like this

def to_hierarchy stuff
  stuff.each_with_object({}) do |thing, h|
    path = thing.uri.split("/").drop(4)
    sub_h = h
    path.each do |segment|
      sub_h[segment] ||= {}
      sub_h = sub_h[segment]
    end
    sub_h.merge!(
        data: thing.data,
    )
  end
end