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

Clean & fast code with enumerators

Clean & fast code with enumerators

Ruby’s Enumerator class is a powerful tool for writing code for dealing with streams of data and events, while being easy-to-understand and at the same time both concurrent and parallel.

This talk will go in more detail on how to do so, and also show SlowEnumeratorTools, which provides some of the glue code that makes Enumerator nicer to use, and faster to boot.

There will be some overlap with Sergio’s “Understanding Unix pipes with Ruby” talk from the November meetup.

Denis Defreyne

January 11, 2018
Tweet

More Decks by Denis Defreyne

Other Decks in Programming

Transcript

  1. { "books": [ { "id": "B98312", "title": "The Monkey's Raincoat",

    "author": "Oswaldo Berge" }, { "id": "B98318", "title": "The World, the Flesh and the Devil", "author": "Haleigh Thompson" } ] }
  2. def fetch_books(base_url) url = base_url + '/books' response = NetUVHTTP.get_response(URI.parse(url))

    case response.code when '200' body = JSON.parse(response.body) body.fetch('books') else raise "Unexpected response code: #{response.code}" end end
  3. { "books": [ { "id": "B98312", "title": "The Monkey's Raincoat",

    "author": "Oswaldo Berge" }, { "id": "B98318", "title": "The World, the Flesh and the Devil", "author": "Haleigh Thompson" } ], "cursor": "B98318" }
  4. def fetch_books(base_url) url = base_url + '/books' response = NetUVHTTP.get_response(URI.parse(url))

    case response.code when '200' body = JSON.parse(response.body) body.fetch('books') else raise "Unexpected response code: #{response.code}" end end
  5. def fetch_books(base_url) books = [] loop do url = base_url

    + '/books' response = NetUVHTTP.get_response(URI.parse(url)) case response.code when '200' body = JSON.parse(response.body) books.concat(body.fetch('books')) break else raise "Unexpected response code: #{response.code}" end end books end
  6. def fetch_books(base_url) books = [] cursor = nil loop do

    url = base_url + '/books' url += "?cursor=#{cursor}" if cursor response = NetUVHTTP.get_response(URI.parse(url)) case response.code when '200' body = JSON.parse(response.body) books.concat(body.fetch('books')) cursor = body.fetch('cursor') break if cursor.nil? else raise "Unexpected response code: #{response.code}" end end books end
  7. def fetch_books(base_url) books = [] cursor = nil loop do

    url = base_url + '/books' url += "?cursor=#{cursor}" if cursor response = NetUVHTTP.get_response(URI.parse(url)) case response.code when '200' body = JSON.parse(response.body) books.concat(body.fetch('books')) cursor = body.fetch('cursor') break if cursor.nil? else raise "Unexpected response code: #{response.code}" end end books end
  8. def fetch_books(base_url) books = [] cursor = nil loop do

    url = base_url + '/books' url += "?cursor=#{cursor}" if cursor response = NetUVHTTP.get_response(URI.parse(url)) case response.code when '200' body = JSON.parse(response.body) books.concat(body.fetch('books')) cursor = body.fetch('cursor') break if cursor.nil? else raise "Unexpected response code: #{response.code}" end end books end
  9. def fetch_books(base_url)
 cursor = nil loop do url = base_url

    + '/books' url += "?cursor=#{cursor}" if cursor response = NetUVHTTP.get_response(URI.parse(url)) case response.code when '200' body = JSON.parse(response.body) body.fetch('books').each { |b| yield(b) } cursor = body.fetch('cursor') break if cursor.nil? else raise "Unexpected response code: #{response.code}" end end
 
 end
  10. def fetch_books(base_url) cursor = nil loop do url = base_url

    + '/books' url += "?cursor=#{cursor}" if cursor response = NetUVHTTP.get_response(URI.parse(url)) case response.code when '200' body = JSON.parse(response.body) body.fetch('books').each { |b| yield(b) } cursor = body.fetch('cursor') break if cursor.nil? else raise "Unexpected response code: #{response.code}" end end end
  11. def fetch(base_url) cursor = nil loop do url = base_url

    url += "?cursor=#{cursor}" if cursor response = NetUVHTTP.get_response(URI.parse(url)) case response.code when '200' body = JSON.parse(response.body) yield(body) cursor = body.fetch('cursor') break if cursor.nil? else raise "Unexpected response code: #{response.code}" end end end
  12. responses = [] fetch('http://localhost:4567/books') do |response| responses << response end

    books = responses.map { |r| r.fetch('books') }.flatten books.each do |book| p book end
  13. (1..3).map { |i| [i, 10+i] } # h> [[1, 11],

    [2, 12], [3, 13]] (1..3).flat_map { |i| [i, 10+i] } # h> [1, 11, 2, 12, 3, 13]
  14. def fetch(base_url) return to_enum(__method__, base_url).lazy unless block_given? cursor = nil

    loop do url = base_url url += "?cursor=#{cursor}" if cursor response = NetUVHTTP.get_response(URI.parse(url)) case response.code when '200' body = JSON.parse(response.body) yield(body) cursor = body.fetch('cursor') break if cursor.nil? else raise "Unexpected response code: #{response.code}" end end end
  15. Given ‣ 200 books ‣ HTTP response contains 50 books

    ‣ it takes 1s to fetch a batch of books ‣ it takes 1s to store a batch of books

  16. Given ‣ 200 books ‣ HTTP response contains 50 books

    ‣ it takes 1s to fetch a batch of books ‣ it takes 1s to store a batch of books
 Question How long does this code take to execute?
 
 3s 4s 5s 8s 16s
  17. Given ‣ 200 books ‣ HTTP response contains 50 books

    ‣ it takes 1s to fetch a batch of books ‣ it takes 1s to store a batch of books
 Question How long does this code take to execute?
 
 3s 4s 5s 8s 16s
  18. db = Database.connect books = fetch('http://localhost:4567/books') .flat_map { |r| r.fetch('books')

    }
 books = SlowEnumeratorTools.buffer(books, 50)
 
 books.each_slice(50) { |batch| db.store(batch) }
  19. Given ‣ 200 books ‣ HTTP response contains 50 books

    ‣ it takes 1s to fetch a batch of books ‣ it takes 1s to store a batch of books

  20. Given ‣ 200 books ‣ HTTP response contains 50 books

    ‣ it takes 1s to fetch a batch of books ‣ it takes 1s to store a batch of books
 Question How long does this code take to execute?
 
 3s 4s 5s 8s 16s
  21. Given ‣ 200 books ‣ HTTP response contains 50 books

    ‣ it takes 1s to fetch a batch of books ‣ it takes 1s to store a batch of books
 Question How long does this code take to execute?
 
 3s 4s 5s 8s 16s
  22. db = Database.connect books = fetch('http://localhost:4567/books') .flat_map { |r| r.fetch('books')

    }
 books = SlowEnumeratorTools.buffer(books, 50)
 
 
 books.each_slice(50) { |batch| db.store(batch) }
  23. db = Database.connect books = fetch('http://localhost:4567/books') .flat_map { |r| r.fetch('books')

    }
 books = SlowEnumeratorTools.buffer(books, 50)
 book_batches = SlowEnumeratorTools.batch(books)
 
 book_batches.each { |batch| db.store(batch) }
  24. content_events = MyEventStream.new('content') layout_events = MyEventStream.new('layouts') events = SlowEnumeratorTools.merge( [content_events,

    layout_events]) event_batches = SlowEnumeratorTools.batch(events) event_batches.each do |es| system('./rebuild.sh') end
  25. db = MyDB.new api_a = MyAPI.new(MyHTTPClient.new('example.com')) api_b = MyAPI.new(MyHTTPClient.new('example.org')) articles

    = SlowEnumeratorTools.merge( [api_a.articles, api_b.articles]) articles = SlowEnumeratorTools.buffer(articles, 200)
  26. db = MyDB.new api_a = MyAPI.new(MyHTTPClient.new('example.com')) api_b = MyAPI.new(MyHTTPClient.new('example.org')) articles

    = SlowEnumeratorTools.merge( [api_a.articles, api_b.articles]) articles = SlowEnumeratorTools.buffer(articles, 200) article_batches = SlowEnumeratorTools.batch(articles)
  27. db = MyDB.new api_a = MyAPI.new(MyHTTPClient.new('example.com')) api_b = MyAPI.new(MyHTTPClient.new('example.org')) articles

    = SlowEnumeratorTools.merge( [api_a.articles, api_b.articles]) articles = SlowEnumeratorTools.buffer(articles, 200) article_batches = SlowEnumeratorTools.batch(articles) article_batches.each { |as| db.store_multi(as) }