Equality, Comparison, Hashing and Duck Typing
21.1 Equality Methods
Same object (identity) Strict equality used by Hash keys (no type coercion: 1.eql?(1.0) is false) Yes, with hash Hash code for Hash/Set membership Yes, with eql?
Case equality (used by case/when, grep) Ordering (with Comparable)
class Version
include Comparable
attr_reader :major, :minor, :patch
def initialize(str) = @major, @minor, @patch = str.split(".").map(&:to_i)
def <=>(other)
return nil unless other.is_a?(Version)
[major, minor, patch] <=> [other.major, other.minor, other.patch]
end
def eql?(other) = other.is_a?(Version) && (self <=> other).zero?
def hash = [major, minor, patch].hash
def to_s = [major, minor, patch].join(".")
end
versions = %w[1.10.0 1.2.3 1.2.10].map { Version.new(it) }
versions.sort.map(&:to_s) # ["1.2.3", "1.2.10", "1.10.0"]
versions.max.to_s
{ Version.new("1.0.0") => "stable" }[Version.new("1.0.0")] # "stable"21.2 Conversion Protocols
Implicit (strict, "is a") Explicit (lenient, "can become") to_proc (used by &)
Implement coerce for numeric-like classes so 2 * my_obj works.
21.3 Duck Typing
"If it walks like a duck and quacks like a duck, it's a duck." Program to behavior, not to types.
def log_all(writer, messages) # anything with #<< or #write works: File, StringIO, Array, Logger...
messages.each { |m| writer << "#{m}\n" }
end
log_all($stdout, %w[a b])
log_all(StringIO.new, %w[c])
log_all([], %w[d])
def save(obj)
raise TypeError, "needs #to_h" unless obj.respond_to?(:to_h)
store(obj.to_h)
end