Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Ruby(および1つのGem)の4つの単純なメモ化パターン

メモ化は、アクセサメソッドを高速化するために使用できる手法です。時間のかかる作業、つまり1回だけ実行する必要がある作業を実行するメソッドの結果をキャッシュします。 Railsでは、メモ化が頻繁に使用されるため、メソッドをメモするモジュールも含まれています。

後で、これは物議を醸すように削除され、最初に説明する非常に一般的なメモ化パターンを使用することになりました。 しかし、ご覧のとおり、この基本的なパターンが正しく機能しない場所がいくつかあります。 そのため、より高度なメモ化パターンについても見ていき、その過程でRubyについていくつかの優れた点を学びます!

超基本的なメモ化

このメモ化パターンは、Rubyで常に表示されます:

app / models / order.rb
class User < ActiveRecord::Base
  def twitter_followers
    # assuming twitter_user.followers makes a network call
    @twitter_followers ||= twitter_user.followers
  end
end

||= 多かれ少なかれ@twitter_followers = @twitter_followers || twitter_user.followers 。つまり、最初にtwitter_followersに電話をかけたときにのみ、ネットワークに電話をかけることになります。 、および将来の呼び出しは、インスタンス変数@twitter_followersの値を返すだけです。 。

複数行のメモ化

スローコードは、ひどいことをしなければ1行に収まらないことがあります。 基本パターンを拡張して複数行のコードを処理する方法はいくつかありますが、これが私のお気に入りです:

app / models / order.rb
class User < ActiveRecord::Base
  def main_address
    @main_address ||= begin
      maybe_main_address = home_address if prefers_home_address?
      maybe_main_address = work_address unless maybe_main_address
      maybe_main_address = addresses.first unless maybe_main_address
    end
  end
end

begin...end {...}のように、単一のものとして扱うことができるコードのブロックをRubyで作成します Cスタイルの言語で。そのため、||= ここでは以前と同じように機能します。

nilはどうですか?

しかし、これらのメモ化パターンには、厄介な隠れた問題があります。最初の例では、ユーザーがTwitterアカウントを持っておらず、TwitterフォロワーAPIがnilを返した場合はどうなりますか。 ? 2番目の例では、ユーザーにアドレスがなく、ブロックがnilを返した場合はどうなりますか。 ?

メソッドを呼び出すたびに、インスタンス変数はnilになります 、そして高価なフェッチを再度実行します。

したがって、||= おそらく正しい道ではありません。代わりに、nilを区別する必要があります およびundefined

app / models / order.rb
class User < ActiveRecord::Base
  def twitter_followers
    return @twitter_followers if defined? @twitter_followers
    @twitter_followers = twitter_user.followers
  end
end
app / models / order.rb
class User < ActiveRecord::Base
  def main_address
    return @main_address if defined? @main_address
    @main_address = begin
      main_address = home_address if prefers_home_address?
      main_address ||= work_address
      main_address ||= addresses.first # some semi-sensible default
    end
  end
end

残念ながら、これは少し醜いですが、nilで動作します 、false 、およびその他すべて。 (nilを処理するため この場合、この問題を回避するためにNullオブジェクトと空の配列を使用することもできます。 nilを避けるもう1つの理由 !)

そしてパラメータはどうですか?

単純なアクセサーに適したメモ化パターンがいくつかあります。 しかし、このようなパラメータを受け取るメソッドをメモ化する場合はどうでしょうか?

app / models / city.rb
class City < ActiveRecord::Base
  def self.top_cities(order_by)
    where(top_city: true).order(order_by).to_a
  end
end

RubyのHash 完全に機能するイニシャライザーがあります この状況のた​​めに。 Hash.newを呼び出すことができます ブロック付き:

Hash.new {|h, key| h[key] = some_calculated_value }

その後、設定されていないハッシュ内のキーにアクセスしようとするたびに、ブロックが実行されます。そして、アクセスしようとしたキーとともにハッシュ自体をブロックに渡します。

したがって、このメソッドをメモ化する場合は、次のようにすることができます。

app / models / city.rb
class City < ActiveRecord::Base
  def self.top_cities(order_by)
    @top_cities ||= Hash.new do |h, key|
      h[key] = where(top_city: true).order(key).to_a
    end
    @top_cities[order_by]
  end
end

そしてorder_byに何を渡しても 、正しい結果がメモ化されます。 ブロックはキーが存在しない場合にのみ呼び出されるため、ブロックの結果がnilまたはfalseになることを心配する必要はありません。

驚くべきことに、Hash 実際には配列であるキーで問題なく機能します:

h = {}
h[["a", "b"]] = "c"
h[["a", "b"]] # => "c"

したがって、このパターンは、任意の数のパラメーターを持つメソッドで使用できます!

なぜこのような問題をすべて経験するのですか?

もちろん、これらのメモ化パターンを多くのメソッドに追加し始めると、コードはすぐに判読できなくなります。あなたの方法はすべて儀式であり、実体はありません。

したがって、多くのメモ化が必要なアプリで作業している場合は、使いやすいAPIを使用してメモ化を処理するgemを使用することをお勧めします。 Memoistは良いもののようで、Railsが持っていたものとかなり似ています。 (または、新しく見つけたメモ化の知識があれば、自分でメモ化を作成することもできます)。

しかし、このようなパターンを調査し、それらがどのように組み合わされ、どこで機能し、どこに鋭いエッジがあるかを確認することは常に興味深いことです。また、探索しながら、あまり知られていないRubyの機能についていくつかの巧妙なことを学ぶことができます。


  1. RuboCopを使用したRubyコードのリンティングと自動フォーマット

    リンティングは、プログラムおよびスタイルのエラーについてソースコードを自動チェックすることです。このチェックは、リンターと呼ばれる静的コード分析ツールによって実行されます。ただし、コードフォーマッタは、事前に構成された一連のルールに厳密に準拠するようにソースコードをフォーマットするためのツールです。リンターは通常違反を報告しますが、問題を修正するのは通常プログラマー次第ですが、コードフォーマッターはそのルールをソースコードに直接適用する傾向があるため、フォーマットの間違いを自動的に修正します。 プロジェクトでより一貫性のあるコードスタイルを作成するタスクでは、通常、個別のリンティングツールと

  2. LoggerとLogrageを使用してRubyにログインする

    Rubyでのログの操作 ロギングは、アプリケーションが通常対処する主要なタスクの1つです。ログは、たとえば、必要なときに使用されます アプリ内で何が起こっているかを確認します それらを監視する、または 特定のデータの指標を収集します。 新しいプログラミング言語を学ぶとき、情報を記録するための最初の明白な選択は、ネイティブメカニズムです。通常、それは簡単で、文書化されており、コミュニティ全体に広く行き渡っています。 ログデータは、使用している会社、ビジネス、アプリケーションの種類によって大きく異なります。したがって、あなたとあなたのチームが選択したロギングソリューションがその全体的な使