Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lazy, Lazy, Lazy all the things !

Lazy, Lazy, Lazy all the things !

Lazy programming has been a fundamental feature of a lot of programming languages, especially functional programming languages like Haskell. Being inspired by these languages, Ruby offers us a few nifty ways of doing lazy programming. The ability to avoid needless calculations, perform operations on potentially infinite sequences and defining control flow as abstracts can exponentially reduce the running time of your programs.

Shaunak Pagnis

January 28, 2017
Tweet

More Decks by Shaunak Pagnis

Other Decks in Programming

Transcript

  1. LAZINESS IS THE QUALITY THAT MAKES YOU GO TO GREAT

    EFFORT TO REDUCE OVERALL ENERGY EXPENDITURE. Larry Wall BECAUSE,
  2. YOU HAVE TO BE LAZY TO BE A RUBY PROGRAMMER

    Yukihiro ‘Matz’ Matsumoto AND,
  3. ABSTRACTING THE CONTROL FLOW PART 1 - THE PLEDGE INITIALISE

    AN OBJECT ▸All we need to do is initialise an object ▸But we need to check if its nil or not ▸Seriously, how hard can that be?
  4. ABSTRACTING THE CONTROL FLOW PART 1 - THE TURN INITIALISE

    AN OBJECT if @foo.nil? @foo = :bar end
  5. ABSTRACTING THE CONTROL FLOW PART 2 - THE PLEDGE FETCHING

    AN AUTHENTICATION TOKEN ▸Communicate with an API ▸Fetch an Authentication Token ▸Use it
  6. ABSTRACTING THE CONTROL FLOW PART 2 - THE TURN SEND

    A REQUEST, AND SAVE THE TOKEN require 'restclient' class Authenticator attr_reader :auth_token def auth_token url res = RestClient.get(url) @auth_token = parse_response_for_token(res) end def parse_response_for_token response # parsing logic here, returns a hash end end
  7. ABSTRACTING THE CONTROL FLOW PART 2 - THE PRESTIGE SEND

    A REQUEST, AND SAVE THE TOKEN, LAZILY require 'restclient' class Authenticator attr_reader :auth_token def auth_token url @auth_token ||= (res = RestClient.get(url) && parse_response_for_token(res)) end def parse_response response # parsing logic here, returns a hash end end
  8. LONG SEQUENCES PART 1 - THE PLEDGE CALCULATE SUM OF

    FIRST THOUSAND EVEN NUMBERS ▸Iterate 2000 times ▸Check if the number is even ▸Add to a sum if it is.
  9. LONG SEQUENCES PART 1 - THE TURN LOOP IT, SUM

    IT, RETURN IT def sum_of_thousand_even_numbers sum = 0 2001.times do |number| sum += number if number % 2 == 0 end sum end sum_of_even_numbers(1000) => 1001000
  10. LONG SEQUENCES PART 1 - THE TURN RANGE IT, SELECT

    IT, REDUCE IT def sum_of_even_numbers limit (1..limit*2) .select(&:even?) .reduce(:+) end sum_of_even_numbers(1000) => 1001000
  11. LONG SEQUENCES PART 1 - THE TURN RANGE IT, SELECT

    IT, TAKE IT, REDUCE IT def sum_of_even_numbers limit (1..Float::INFINITY) .select(&:even?) .take(limit) .reduce(:+) end
  12. LONG SEQUENCES PART 1 - THE TURN RANGE IT, SELECT

    IT, TAKE IT, REDUCE IT def sum_of_even_numbers limit (1..Float::INFINITY) .select(&:even?) .take(limit) .reduce(:+) end DANG! INFINITE LOOP !
  13. LONG SEQUENCES PART 1 - THE PRESTIGE RANGE IT, SELECT

    IT, TAKE IT, REDUCE IT, LAZILY def sum_of_even_numbers limit (1..Float::INFINITY) .lazy .select(&:even?) .take(limit) .reduce(:+) end sum_of_even_numbers(1000) => 1001000
  14. LAZY SEQUENCES PART 1 - THE PLEDGE STEP WISE PERSON

    LOOKUP ▸We want to fetch person’s data based on his email ▸Using Third party services like Clearbit ▸And each lookup for data is expensive computationally and monetarily.
  15. LAZY SEQUENCES - THE TURN STEP WISE LOOKUP def find_match

    lookups = [ expensive_phone_lookup(@person.email), expensive_location_lookup(@person.email), expensive_property_lookup(@person.email), ] lookups.each do |lookup| result = @service.match_person(stage) break result if result.success? end end
  16. LAZY SEQUENCES - THE PRESTIGE STEP WISE LOOKUP def stages(email)

    @stages ||= Enumerator.new do |enumerator| # Step 1: Only Phone phone = expensive_phone_lookup(email) enumerator.yield(:email_only, :phone => phone) # Step 2: Only Phone Number location = expensive_location_lookup(email) enumerator.yield(:location_only, :location => location) # Step 3: Only Property property = expensive_property_lookup(email) enumerator.yield(:property, :property => property) # Step 4: All Info enumerator.yield(:all_info, all_info) end end
  17. LAZY SEQUENCES - THE PRESTIGE STEP WISE LOOKUP def find_match

    while stage = stage(@person.email).next result = @service.match_person(stage) break result if result.success? end rescue StopIteration => _ # error handling here end
  18. RAILS IS A HUGE PROJECT ! 250,000+ LINES OF CODE

    60,000+ COMMITS 3,200+ CONTRIBUTOR S
  19. SO IN 2010, TO IMPROVE BOOT TIME, RAILS CHANGED ITS

    INITIALISATION MECHANISM TO LAZY LOADING
  20. ACTIVE SUPPORT LAZY LOAD HOOKS THE TASK OF THIS LIBRARY

    IS TO ▸Accept blocks of code and store them in a hash. ▸Have a calling mechanism for the stored blocks. ▸Then call those stored blocks when required.
  21. ACTIVE SUPPORT LAZY LOAD HOOKS THE IMPLICATIONS OF THIS LIBRARY

    ARE ▸By calling the .run_load_hooks method at the end, Rails can call code that depends on ActiveRecord, lazily. ▸This helps Rails load code only when it’s required, which reduces its booting time significantly.
  22. YOU CAN USE THIS OBJECT FOR QUERYING, SCOPING, ET AL;

    ALL IN THE CONTEXT OF CONTROLLERS AND VIEWS.
  23. THE CURRENT USER - THE PLEDGE ‣ All you need

    to do is store the current_user in a thread-local variable and use it in your model ‣ Thread.current[:current_user] = @current_user USING CURRENT USER ACROSS THE APP
  24. THE CURRENT USER - THE PLEDGE USING CURRENT USER ACROSS

    THE APP ‣ If you’re using threaded app servers like Thin or Puma, then it’s wise to use the request_store or request_store_rails gem. ‣ RequestStore.store[:current_user] = @current_user
  25. SAY YOU HAVE A BLOG APPLICATION AND YOU WANT TO

    SHOW THE CURRENT USER’S COMMENTS ON A BLOG
  26. THE CURRENT USER - THE PRESTIGE USING CURRENT USER ACROSS

    THE APP class Blog include Mongoid::Document field :title, type: String field :content, type: String belongs_to :author, class_name: "User" has_many :comments attr_accessor :current_viewer after_initialize Proc.new { |blog| blog.current_viewer = RequestStore.store[:current_user] } def my_comments self.comments.where(user_id: self.current_viewer.id) end end
  27. THE CURRENT USER - THE PRESTIGE USING CURRENT USER ACROSS

    THE APP class Blog include Mongoid::Document field :title, type: String field :content, type: String belongs_to :author, class_name: "User" has_many :comments attr_accessor :current_viewer after_initialize Proc.new { |blog| blog.current_viewer = RequestStore.store[:current_user] } def my_comments self.comments.where(user_id: self.current_viewer.id) end end
  28. THE CURRENT USER - THE TURN USING CURRENT USER ACROSS

    THE APP module CurrentUser class RailTie < Rails::RailTie ActiveSupport.on_load :action_controller do before_action do |c| RequestStore.store[:current_user] ||= c.current_user if c.current_user.present? end end end end
  29. THE CURRENT USER - THE TURN USING CURRENT USER ACROSS

    THE APP module CurrentUser class RailTie < Rails::RailTie ActiveSupport.on_load :action_controller do before_action do |c| RequestStore.store[:current_user] ||= c.current_user if c.current_user.present? end end end end
  30. class Blog include Mongoid::Document field :title, type: String field :content,

    type: String belongs_to :author, class_name: "User" attr_accessor :current_viewer after_initialize Proc.new { |blog| blog.current_viewer = RequestStore.store[:current_user] } ActiveSupport.run_load_hooks(:blog, self) end
  31. class Blog include Mongoid::Document field :title, type: String field :content,

    type: String belongs_to :author, class_name: "User" attr_accessor :current_viewer after_initialize Proc.new { |blog| blog.current_viewer = RequestStore.store[:current_user] } ActiveSupport.run_load_hooks(:blog, self) end
  32. module Commentable def self.included(base) base.has_many :comments, as: :commentable base.send(:include, InstanceMethods)

    end module InstanceMethods def my_comments self.comments.where(user_id: self.current_viewer.id) end # . . . end end
  33. class Feed # feed model details here ActiveSupport.run_load_hooks(:feed, self) end

    class Photo # feed model details here ActiveSupport.run_load_hooks(:photo, self) end