RubyTapas #199 - #204
199 Regexp Union
★★
- "leaning toothpick syndrome"
- LTS つまようじ大好き症候群
- Perl perlretut - Perl の正規表現のチュートリアル - perldoc.jp
patterns = [ ".each do |", ".each {|", ] Regexp.union(patterns) # => /\.each\ do\ \||\.each\ \{\|/ Regexp.union(".each do |", ".each {|", /for .* in .*/) # => /\.each\ do\ \||\.each\ \{\||(?-mix:for .* in .*)/
Regexp.union(/Avdi/i, /RubyTapas/) # => /(?i-mx:Avdi)|(?-mix:RubyTapas)/
- Regex options Rubyist Magazine - 標準添付ライブラリ紹介 【第 14 回】 正規表現 (3)
(?-mix:
m, i, xを無効にする(?i-mx:
i有効、m, xは無効
200 Quantity
★
量(数値)の扱いなどは、単位の変換、型の変換、実装者・チームの違いで精度が失われる。重大なシステムでは悲劇につながる。
class Altimeter ... def change_by(change_amount) raise TypeError unless Feet === change_amount old_value = @value_in_feet @value_in_feet += change_amount puts "Altitude changed from #{old_value} to #{@value_in_feet}" end
in change_by: TypeError
, in change_by: undefined method +
class Feet attr_accessor :magnitude ... def +(other) Feet.new(@magnitude + other.magnitude) end end alt = Altimeter.new(Feet.new(10_000)) alt.change_by(Feet.new(-600)) # >> Altitude changed from 10000.0 feet to 9400.0 feet
201 Immutable Object
★
Numbers are immutable
良くない
START_ALTITUDE = Feet.new(10_000) alt = Altimeter.new(START_ALTITUDE) alt.change_by(Feet.new(500)) START_ALTITUDE # => #<Feet:0x000000017e0e90 @magnitude=10500.0> # >> Altitude changed from 10500.0 feet to 10500.0 feet
メソッドの実装を間違えた場合を防ぐ (>=
を間違えて=
)
class Feet attr_reader :magnitude def initialize(magnitude) @magnitude = magnitude.to_f freeze end ... # 間違い def positive? @magnitude = 0.0 end end f = Feet.new(30_000) f.positive? # => # ~> -:18:in `positive?': can't modify frozen Feet (RuntimeError) # ~> from -:23:in `<main>'
freezeを使う
202 Identity And Equality
★
class Feet ... def ==(other) other.is_a?(Feet) && magnitude == other.magnitude end end
case-equality
one.equal?(other_one) # => false one === other_one # => true case one when Feet.new(1) then puts "one" when Feet.new(2) then puts "two" end # >> one
There is one other kind of equality which we haven't touched on yet: hash equality
203 Hash Table
★
hash values
123.hash # => -4326452433101730515 "hello".hash # => 1575747404428851756 "goodbye".hash # => 2103260368034954340 :foo.hash # => -924210406470135610
Ruby's actual Hash implementation is quite sophisticated
even with very large hashes, the Hash only ever needs to perform full comparisons against a few keys before finding the one it's looking for
204 Hash Equality
★
期待しない動作
h = { one => "one", two => "two" } h[Feet.new(1)] # => nil h.key?(Feet.new(1)) # => false require "set" s = Set.new([one, two]) s # => #<Set: {#<Feet:0x00000002435da8 @magnitude=1.0>, #<Feet:0x00000002435d80 @magnitude=2.0>}> s.include?(Feet.new(1)) # => false
Ruby doesn't use the double-equals equivalence operator to do this. It uses a different method which is specifically intended for hash equality, spelled eql?
def hash [magnitude, Feet].hash end alias_method :eql?, :==
- Value Object class
- define an equivalency operator that uses the object's state, rather than its identity
- define a custom #hash method which uses the object's state to generate a hash value
- alias the hash equality operator to the equivalency operator