Skip to content

Instantly share code, notes, and snippets.

@ixti
Last active August 22, 2019 19:56
Show Gist options
  • Save ixti/b035c9b1988be13de34dda8f677df167 to your computer and use it in GitHub Desktop.
Save ixti/b035c9b1988be13de34dda8f677df167 to your computer and use it in GitHub Desktop.
def print_result(desc)
result = yield
puts "#{desc} class=#{result.class} inspect=#{result.inspect}"
rescue => e
warn "#{desc} fail: #{e}"
end
def test(obj)
puts "\n== #{obj.inspect} == "
print_result("[*obj] =>") { [*obj] }
print_result("Array(obj) =>") { Array(obj) }
print_result("Array.wrap(obj) =>") { Array.wrap(obj) }
end
# trivial cases work the same:
[nil, true, false, 161, [1, 3, 1, 2], { :foo => :bar }].each(&method(:test))
# things become different with objects that implement implicit (to_ary) and/or
# explicit (to_a) coercions:
test(1..3)
# now, let's go crazy:
class Dummy
def initialize(to_a:, to_ary:)
@to_a = to_a
@to_ary = to_ary
define_singleton_method(:to_a) { @to_a } if @to_a
define_singleton_method(:to_ary) { @to_ary } if @to_ary
end
class << self
alias [] new
end
end
test(Dummy[:to_a => [1, 6, 1], :to_ary => [1, 3, 1, 2]])
test(Dummy[:to_a => "AFA", :to_ary => [1, 3, 1, 2]])
test(Dummy[:to_a => [1, 6, 1], :to_ary => "ACAB"])
test(Dummy[:to_a => [1, 6, 1], :to_ary => nil])
test(Dummy[:to_a => nil, :to_ary => [1, 6, 1]])
test(Dummy[:to_a => "AFA", :to_ary => nil])
test(Dummy[:to_a => nil, :to_ary => "AFA"])
test(Dummy[:to_a => nil, :to_ary => nil])
@ixti
Copy link
Author

ixti commented Aug 22, 2019

Overview

  • If object does not implement neither implicit coercion (#to_ary) nor explicit coercion (#to_a):

    • Both Array.wrap and Array will return a new Array with given object as its element:

      Array.wrap(true) # => [true]
      Array(true)      # => [true]
  • If object implements #to_a and not #to_ary:

    • Array.wrap will wrap it into a new array:

      Array.wrap(Dummy.new(:to_a => [1, 3, 1, 2])) # => [Dummy.new(:to_a => [1, 3, 1, 2])]
    • Array will coerce it with #to_a

      Array(Dummy.new(:to_a => [1, 3, 1, 2])) # => [1, 3, 1, 2]

      but if #to_a returns not an Array, an exception will be raised:

      Array(Dummy.new(:to_a => "ACAB")) # !! TypeError
  • If object implements #to_ary:

    • Array will use it:

      Array(Dummy.new(:to_ary => [1, 6, 1])) # => [1, 6, 1]

      but if #to_ary returns not an Array, an exception will be raised:

      Array(Dummy.new(:to_ary => "AFA")) # !! TypeError
    • Array.wrap will use it, even if result will be not an Array:

      Array.wrap(Dummy.new(:to_ary => [1, 6, 1])) # => [1, 6, 1]
      Array.wrap(Dummy.new(:to_ary => "AFA"))     # => "AFA"
  • If both #to_ary and #to_a are implemented:

    • Array will use #to_ary and will not fallback to #to_a in any matter

@ixti
Copy link
Author

ixti commented Aug 22, 2019

Update 1: How [*obj] behaves:

  • it always ignores #to_ary implementation
  • returns Array with given object as its only element if object does not responds to #to_a
  • calls #to_a if object responds to that
    • raises TypeError if returned value isn't an Array

@sensorsasha
Copy link

Seems like Array.wrap is the most obvious implementation.

@ixti
Copy link
Author

ixti commented Aug 22, 2019

To make it easier to think of those methods, here's brief outline of behaviours:

Array(object)

  • Return object as is if it's an Array
  • Otherwise coerce object implicitly (if object responds to #to_ary)
    • Raise TypeError if coercion returned in non-Array
  • Otherwise coerce object explicitly (if object responds to #to_a)
    • Raise TypeError if coercion returned non-Array
  • Otherwise wrap object into an Array (as in: [object])

Array.wrap(object)

  • Return object as is if it's an Array
  • Otherwise coerce object implicitly (if object responds to #to_ary)
    • Return result of coercion as is if it's truthy (anything but nil or false)
  • Otherwise wrap object into an Array (as in: [object])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment