Skip to content

Instantly share code, notes, and snippets.

@hannestyden
Created August 10, 2014 09:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hannestyden/3065a455f3d663313cce to your computer and use it in GitHub Desktop.
Save hannestyden/3065a455f3d663313cce to your computer and use it in GitHub Desktop.
# # Adventures in `flat_map` land
#
# Investigating the conceptual difference between Ruby's `flat_map` and Scala's `flatMap`.
#
# Ruby's `flat_map`
a = [[1], [2], [[3]], [[4], 5], [[[6]]]]
a.flat_map { |e| e.first } # => [1, 2, 3, 4, [6]]
# ... which is the same as ...
a.map { |e| e.first }.flatten(1) # => [1, 2, 3, 4, [6]]
# Kinda meh, since it's just the combo of existing methods ...
#
# ... but what about Scala's `flatMap`?
# As explained in http://www.brunton-spall.co.uk/post/2011/12/02/map-map-and-flatmap-in-scala/
# the power lies in the elements' respective result of being "flattened"
module ArrayFlatMap
def flatMap(&block) # urghCase, to avoid collision
map(&block).
map(&:to_a). # `to_a` is chosen as the "preparation" for the element to be flattened
flatten(1)
end
end
class Array; include ArrayFlatMap; end
# Each "flattenable" object defines how they "prepare" for being flattened.
class Nothing
def to_a; []; end
end
class Something
def initialize(value); @value = value; end
def to_a; [@value]; end
end
b = (1..5).to_a
b.flatMap { |i| i > 2 ? Something.new(i) : Nothing.new }
# => [3, 4, 5]
# ... which is of course the same as ...
b.select { |i| i > 2 }
# => [3, 4, 5]
# ... or even ...
b.map { |i| i > 2 ? i : nil }.compact
# => [3, 4, 5]
# ... but the two latter examples can't let the elements decide what flattening entails.
# In a ducktyped environment, a more pragmatic way would be to check if each
# element is "arrayable", otherwise the element is kept as is.
class ResponseOrSelf
def initialize(object); @object = object; end
def method_missing(method, *arguments, &block)
return @object unless @object.respond_to?(method)
@object.send(method, *arguments, &block)
end
end
class DucksInOrder
def initialize(array); @array = array; end
def flat_map(&block)
@array.map(&block).map { |e| ResponseOrSelf.new(e).to_a }.flatten(1)
end
end
c = DucksInOrder.new((1..5).to_a)
c.flat_map { |i| i > 2 ? Something.new(i) : Nothing.new }
# => [3, 4, 5]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment