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.

0b1540133151bf8f02b726268d962517?s=128

Shaunak Pagnis

January 28, 2017
Tweet

Transcript

  1. LAZY, LAZY, LAZY, ALL THE THINGS!

  2. WHAT IS LAZY PROGRAMMING?

  3. IT DOES NOT MEAN LETTING AN IDE WRITE CODE FOR

    US
  4. IT DOES NOT MEAN ASKING SIRI TO DO DATABASE MIGRATIONS

  5. IT IS NOT WRITING CODE ON A GLOOMY MONDAY WITH

    A CUP OF COFFEE
  6. WHAT IS IT THEN ?

  7. IT CAN BE A SIMPLE EXPRESSION

  8. OR, A DESIGN PATTERN

  9. PERHAPS, EVEN A LANGUAGE SEMANTIC

  10. WHY AM I TELLING YOU THIS ?

  11. None
  12. LAZINESS IS THE QUALITY THAT MAKES YOU GO TO GREAT

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

    Yukihiro ‘Matz’ Matsumoto AND,
  14. LET’S BE LAZY

  15. 1. LAZY EVALUATION 2. LAZY LOADING 3. LAZY INHERITANCE

  16. LAZY EVALUATION THE EXPRESSION

  17. DELAYING EVALUATION OF AN EXPRESSION UNTIL IT'S REQUIRED

  18. GIVING US THE ABILITY TO ABSTRACT THE CONTROL FLOW

  19. EXAMPLE

  20. 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?
  21. ABSTRACTING THE CONTROL FLOW PART 1 - THE TURN INITIALISE

    AN OBJECT if @foo.nil? @foo = :bar end
  22. ABSTRACTING THE CONTROL FLOW PART 1 - THE PRESTIGE INITIALISE

    AN OBJECT, LAZILY @foo ||= :bar
  23. THAT WAS SIMPLE ENOUGH

  24. NOW, A MORE USEFUL EXAMPLE

  25. ABSTRACTING THE CONTROL FLOW PART 2 - THE PLEDGE FETCHING

    AN AUTHENTICATION TOKEN ▸Communicate with an API ▸Fetch an Authentication Token ▸Use it
  26. 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
  27. 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
  28. THIS LOOKS SUSPICIOUSLY LIKE CACHING

  29. CACHING = LAZINESS

  30. MIND = BLOWN!

  31. None
  32. None
  33. ANOTHER EXAMPLE

  34. 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.
  35. 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
  36. THAT’S NOT THE RUBY WAY, IS IT ?

  37. 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
  38. 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
  39. 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 !
  40. 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
  41. THAT WAS PEDAGOGICAL

  42. LET’S GET REAL(WORLD)

  43. 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.
  44. 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
  45. 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
  46. 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
  47. LAZY LOADING A DESIGN PATTERN

  48. A DESIGN PATTERN WHERE LOADING OF OBJECTS IS DELAYED UNTIL

    REQUIRED
  49. RUBY HAS PROCS, BUILT TO STORE AND RUN CODE WHEN

    REQUIRED
  50. RUBY HAS MODULES, BUILT TO WRITE AND INJECT CODE WHEN

    REQUIRED
  51. COMBINE THESE TWO AND YOU CAN BUILD PROGRAMS THAT ALLOW

    LAZY LOADING
  52. WE’LL BE LOOKING AT AN EXCELLENT LIBRARY WRITTEN IN RUBY

    ON RAILS
  53. BUT FIRST ! A LITTLE BACKGROUND INFO

  54. RAILS IS A HUGE PROJECT ! 250,000+ LINES OF CODE

    60,000+ COMMITS 3,200+ CONTRIBUTOR S
  55. AND HAS A COMPLEX LOADING AND INITIALISING MECHANISM

  56. BUT, WITH HIGH COMPLEXITY COMES HIGH BOOT UP TIME

  57. SO IN 2010, TO IMPROVE BOOT TIME, RAILS CHANGED ITS

    INITIALISATION MECHANISM TO LAZY LOADING
  58. https://github.com/rails/rails/commit/39d6f9e112f2320d8c2006ee3bcc160cfa761d0a

  59. AND THAT’S HOW ACTIVESUPPORT::LAZYLOAD HOOKS WAS BORN

  60. 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.
  61. ACCEPT BLOCKS AND STORE THEM IN A HASH https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb

  62. CALL THE HOOKS ONLY WHEN REQUIRED https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb

  63. EXECUTE THE STORED BLOCKS https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb

  64. https://github.com/rails/rails/blob/master/activerecord/lib/active_record.rb ActiveRecord

  65. https://github.com/rails/rails/blob/master/activerecord/lib/active_record.rb ActiveRecord

  66. https://github.com/rails/rails/blob/master/activerecord/lib/active_record/base.rb ActiveRecord::Base

  67. https://github.com/rails/rails/blob/master/activerecord/lib/active_record/base.rb ActiveRecord::Base

  68. 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.
  69. EXAMPLE

  70. THE CORNERSTONE OF A NUTRITIOUS RAILS APP

  71. None
  72. USER AUTHENTICATION

  73. CURRENT_USER, REMEMBER?

  74. YOU CAN USE THIS OBJECT FOR QUERYING, SCOPING, ET AL;

    ALL IN THE CONTEXT OF CONTROLLERS AND VIEWS.
  75. None
  76. PASSING THE OBJECT TO EVERY MODEL METHOD DOESN’T MAKE SENSE,

    RIGHT ?
  77. 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
  78. 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
  79. SAY YOU HAVE A BLOG APPLICATION AND YOU WANT TO

    SHOW THE CURRENT USER’S COMMENTS ON A BLOG
  80. 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
  81. 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
  82. 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
  83. 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
  84. ANOTHER EXAMPLE

  85. WE HAVE THE SAME BLOG APPLICATION, BUT WITH A FEW

    CHANGES
  86. 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
  87. 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
  88. WE’VE SEPARATED THE COMMENTS FEATURE AS A MODULE

  89. 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
  90. ActiveSupport.on_load(:blog) do self.send(:include, Commentable) end

  91. NOW WE’VE DECIDED TO ADD FEEDS AND PHOTOS TO THE

    APP
  92. 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
  93. THEY SHOULD SUPPORT COMMENTS AS WELL

  94. [:blog, :feed, :photo].each do |post| ActiveSupport.on_load(post) do self.send(:include, Commentable) end

    end
  95. THERE ARE PLENTY OF OTHER LAZY PARADIGMS

  96. THERE ARE PLENTY OF OTHER LAZY LANGUAGES

  97. ENOUGH LAZINESS TODAY

  98. CONCLUSION

  99. ||= IS A HANDY SHORT CIRCUIT OPERATOR

  100. ENUMERATOR::LAZY OFFERS INFINITE AND IMMUTABLE SEQUENCES

  101. ACTIVESUPPORT::LAZYLOADHOOKS IS A POWERFUL LIBRARY FOR COMPOSING BEHAVIOUR

  102. THANK YOU !

  103. AMURA MARKETING TECHNOLOGIES SHAUNAK PAGNIS

  104. @_kanuahs shaunakpp shaunak-amura SHAUNAK PAGNIS