Slide 1

Slide 1 text

The Fun in Functional Devon Estes @devoncestes github.com/devonestes

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Warning! ⚠

Slide 5

Slide 5 text

Warning! ⚠ I’m going to try and be funny

Slide 6

Slide 6 text

“First, make the change easy (warning: this may be hard), then make the easy change.” - Kent Beck

Slide 7

Slide 7 text

“First, become Aaron Patterson or Justin Searls (warning: this may be hard), then write the presentation.” - Me

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

&

Slide 10

Slide 10 text

)

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Ruby, we have a problem…

Slide 19

Slide 19 text

Moore’s Law

Slide 20

Slide 20 text

Speed Complexity

Slide 21

Slide 21 text

Moore’s Law is Dead ☠

Slide 22

Slide 22 text

Moore’s Law is Dead ☠ (to Ruby)

Slide 23

Slide 23 text

Moore’s Law is Dead ☠ (to Ruby) (and by Ruby, I mean MRI)

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Functional Programming to the Rescue!

Slide 26

Slide 26 text

Functional Programming to the Rescue! Immutable data structures

Slide 27

Slide 27 text

Functional Programming to the Rescue! Immutable data structures Lazy or delayed evaluation

Slide 28

Slide 28 text

Functional Programming to the Rescue! Immutable data structures Lazy or delayed evaluation Pure functions

Slide 29

Slide 29 text

Functional Programming to the Rescue! Immutable data structures Lazy or delayed evaluation Pure functions Higher order functions

Slide 30

Slide 30 text

Functional Programming to the Rescue! Immutable data structures Lazy or delayed evaluation Pure functions Higher order functions Curried functions

Slide 31

Slide 31 text

Functional Programming to the Rescue! Immutable data structures Lazy or delayed evaluation Pure functions Higher order functions Curried functions

Slide 32

Slide 32 text

Ruby to the Rescue!

Slide 33

Slide 33 text

Ruby to the Rescue! Immutable data structures

Slide 34

Slide 34 text

Ruby to the Rescue! Immutable data structures Lazy or delayed evaluation

Slide 35

Slide 35 text

Ruby to the Rescue! Immutable data structures Lazy or delayed evaluation Pure functions

Slide 36

Slide 36 text

Ruby to the Rescue! Immutable data structures Lazy or delayed evaluation Pure functions Higher order functions

Slide 37

Slide 37 text

Ruby to the Rescue! Immutable data structures Lazy or delayed evaluation Pure functions Higher order functions Curried functions

Slide 38

Slide 38 text

Ruby - The Language of the Future

Slide 39

Slide 39 text

I ❤ # => ruby client.rb data_for_team 'New York Jets' # # => ruby client.rb data_for_game 'New York Jets' 'Miami Dolphins' '11/06/2016' # client = NFLData::Client.new(api_key: ENV['NFL_API_KEY']) case ARGV[0] when 'data_for_team' puts client.data_for_team(team_name: ARGV[1]) when 'data_for_game' puts client.data_for_game(team1: ARGV[1], team2: ARGV[2], date: ARGV[3]) else 'Not a recognized command' end

Slide 40

Slide 40 text

module NFLData class Client def initialize(api_key:) @api_key = api_key end def data_for_team(team_name:) url = "http://nfl.com/api/v1/teams/#{team_name}?api_key=#{api_key}" raw = fetch_raw_data(url) add_turnover_data(raw) add_win_percentage(raw) add_how_team_is_doing(raw) end def data_for_game(team1:, team2:, date:) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" raw = fetch_raw_data(url) add_rushing_comparison(raw) add_passing_comparison(raw) end private # ...

Slide 41

Slide 41 text

private attr_reader :api_key def fetch_raw_data(url) uri = URI.parse(url) json = Net::HTTP.get(uri) JSON.parse(json) end def add_turnover_data(raw) raw[:turnover_differential] = raw[:num_defensive_turnovers].to_i - raw[:num_offensive_turnovers].to_i end def add_win_percentage(raw) total_games = raw[:num_wins] + raw[:num_losses] raw[:win_percentage] = raw[:num_wins].to_f / total_games.to_f end def add_how_team_is_doing(raw) def add_rushing_comparison(raw) def add_passing_comparison(raw) end end

Slide 42

Slide 42 text

module NFLData class Client def initialize(api_key:) @api_key = api_key end def data_for_team(team_name:) url = "http://nfl.com/api/v1/teams/#{team_name}?api_key=#{api_key}" raw = fetch_raw_data(url) add_turnover_data(raw) add_win_percentage(raw) add_how_team_is_doing(raw) end def data_for_game(team1:, team2:, date:) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" raw = fetch_raw_data(url) add_rushing_comparison(raw) add_passing_comparison(raw) end private # ...

Slide 43

Slide 43 text

module NFLData class Client def initialize(api_key:) @api_key = api_key end def data_for_team(team_name:) url = "http://nfl.com/api/v1/teams/#{team_name}?api_key=#{api_key}" raw = fetch_raw_data(url) add_turnover_data(raw) add_win_percentage(raw) add_how_team_is_doing(raw) end def data_for_game(team1:, team2:, date:) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" raw = fetch_raw_data(url) add_rushing_comparison(raw) add_passing_comparison(raw) end private # ...

Slide 44

Slide 44 text

team_client = NFLData::Team.new(api_key: ENV['NFL_API_KEY']) game_client = NFLData::Game.new(api_key: ENV['NFL_API_KEY']) case ARGV[0] when 'data_for_team' puts team_client.get_data(team_name: ARGV[1]) when 'data_for_game' puts game_client.get_data(team1: ARGV[1], team2: ARGV[2], date: ARGV[3]) else 'Not a recognized command' end

Slide 45

Slide 45 text

module NFLData class Base def initialize(api_key:) @api_key = api_key end private attr_reader :api_key def fetch_raw_data(url) uri = URI.parse(url) json = Net::HTTP.get(uri) JSON.parse(json) end end end

Slide 46

Slide 46 text

module NFLData class Game < NFLData::Base def get_data(team1:, team2:, date:) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" @raw = fetch_raw_data(url) add_rushing_comparison add_passing_comparison add_defensive_comparison end private attr_reader :raw def add_rushing_comparison def add_passing_comparison def add_defensive_comparison end end

Slide 47

Slide 47 text

module NFLData class Team < NFLData::Base def get_data(team_name:) url = "http://nfl.com/api/v1/teams/#{team_name}?api_key=#{api_key}" @raw = fetch_raw_data(url) add_turnover_data add_win_percentage add_how_team_is_doing end private attr_reader :raw def add_turnover_data def add_win_percentage def add_how_team_is_doing end end

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

45678

Slide 50

Slide 50 text

module NFLData class Game < NFLData::Base def get_data(team1:, team2:, date:) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" @raw = fetch_raw_data(url) add_rushing_comparison add_passing_comparison add_defensive_comparison end private attr_reader :raw def add_rushing_comparison def add_passing_comparison def add_defensive_comparison end end

Slide 51

Slide 51 text

module NFLData class Game < NFLData::Base def get_data(team1:, team2:, date:) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" @raw = fetch_raw_data(url) add_rushing_comparison add_passing_comparison add_defensive_comparison end private attr_reader :raw def add_rushing_comparison def add_passing_comparison def add_defensive_comparison end end

Slide 52

Slide 52 text

module NFLData class Base def initialize(api_key:, fetcher:) @api_key = api_key @fetcher = fetcher end private attr_reader :api_key, :fetcher end end

Slide 53

Slide 53 text

module NFLData class Game < NFLData::Base def call(team1:, team2:, date:) url = "http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" @raw = fetcher.call(url) add_rushing_comparison add_passing_comparison add_defensive_comparison end private attr_reader :raw def add_rushing_comparison def add_passing_comparison def add_defensive_comparison end end

Slide 54

Slide 54 text

irb(main):001:0> l = ->(str) { puts str.upcase } irb(main):002:0> l.call('hi') HI nil irb(main):003:0> str = 'hi' irb(main):004:0> p = Proc.new { puts str.upcase } irb(main):005:0> p.call HI nil irb(main):006:0> def up(str) irb(main):007:1> puts str.upcase irb(main):008:1> end :up irb(main):009:0> m = method(:up) Object#upp(str) irb(main):010:0> m.call('hi') HI nil irb(main):011:0> def call_block(&block) irb(main):012:1> block.call('hi') irb(main):013:1> end :call_block irb(main):014:0> call_block { |str| puts str.upcase } HI nil

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

fetcher = -> (url) do uri = URI.parse(url) json = Net::HTTP.get(uri) JSON.parse(json) end team_client = NFLData::Team.new(api_key: ENV['NFL_API_KEY'], fetcher: fetcher) game_client = NFLData::Game.new(api_key: ENV['NFL_API_KEY'], fetcher: fetcher) case ARGV[0] when 'data_for_team' puts team_client.get_data(team_name: ARGV[1]) when 'data_for_game' puts game_client.get_data(team1: ARGV[1], team2: ARGV[2], date: ARGV[3]) else 'Not a recognized command' end

Slide 57

Slide 57 text

client = NFLData::Client.new(api_key: ENV['NFL_API_KEY']) case ARGV[0] when 'data_for_team' puts client.data_for_team(team_name: ARGV[1]) when 'data_for_game' puts client.data_for_game(team1: ARGV[1], team2: ARGV[2], date: ARGV[3]) else 'Not a recognized command' end

Slide 58

Slide 58 text

module NFLData class Client def initialize(api_key:) @api_key = api_key end def data_for_team(team_name:) url = "http://nfl.com/api/v1/teams/#{team_name}?api_key=#{api_key}" raw = fetch_raw_data(url) add_turnover_data(raw) add_win_percentage(raw) add_how_team_is_doing(raw) end def data_for_game(team1:, team2:, date:) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" raw = fetch_raw_data(url) add_rushing_comparison(raw) add_passing_comparison(raw) end private # ...

Slide 59

Slide 59 text

The Method Object

Slide 60

Slide 60 text

module NFLData class Client def initialize(api_key:, fetcher: default_fetcher) @api_key = api_key @fetcher = fetcher end def data_for_team(team_name:) url = "http://nfl.com/api/v1/teams/#{team_name}?api_key=#{api_key}" NFLData::Team.new(fetcher: fetcher).call(url: url) end def data_for_game(team1:, team2:, date:) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" NFLData::Game.new(fetcher: fetcher).call(url: url) end private attr_reader :api_key, :fetcher def default_fetcher end end

Slide 61

Slide 61 text

module NFLData class Game def initialize(fetcher:) @fetcher = fetcher end def call(url:) @data = fetcher.call(url) add_rushing_comparison add_passing_comparison add_defensive_comparison data end private attr_reader :fetcher, :data def add_rushing_comparison def add_passing_comparison def add_defensive_comparison end end

Slide 62

Slide 62 text

module NFLData class Team def initialize(fetcher:) @fetcher = fetcher end def call(url:) @data = fetcher.call(url) add_turnover_data add_win_percentage add_how_team_is_doing data end private attr_reader :fetcher, :data def add_turnover_data def add_win_percentage def add_how_team_is_doing end end

Slide 63

Slide 63 text

Pure Function

Slide 64

Slide 64 text

Pure Function 1) Same output for a given input ⚖

Slide 65

Slide 65 text

Pure Function 1) Same output for a given input ⚖ 2) No side effects

Slide 66

Slide 66 text

Pure Function Benefits

Slide 67

Slide 67 text

Pure Function Benefits Super easy to test

Slide 68

Slide 68 text

Pure Function Benefits Super easy to test Super easy to make thread safe

Slide 69

Slide 69 text

Pure Function Benefits Super easy to test Super easy to make thread safe Highly composable

Slide 70

Slide 70 text

Is it Pure?

Slide 71

Slide 71 text

IO#read irb(main):001:0> IO.read('call.rb') "irb(main):001:0> l = ->(str) #..."

Slide 72

Slide 72 text

IO#read irb(main):001:0> IO.read('call.rb') "irb(main):001:0> l = ->(str) #..." irb(main):002:0> IO.read('call.rb') Errno::ENOENT: No such file or directory @ rb_sysopen - call.rb from (irb):2:in 'read' from (irb):2 from /Users/devoncestes/.rbenv/versions/2.3.1/bin/irb:11:in ''

Slide 73

Slide 73 text

Integer#+ irb(main):001:0> 2 + 2 4

Slide 74

Slide 74 text

Integer#+ irb(main):001:0> 2 + 2 4 irb(main):002:0> 2 + 2 4

Slide 75

Slide 75 text

Integer#+ irb(main):001:0> 2 + 2 4 irb(main):002:0> 2 + 2 4 irb(main):003:0> 2 + 2 4

Slide 76

Slide 76 text

String#downcase irb(main):001:0> str = 'I am a String' "I am a String"

Slide 77

Slide 77 text

String#downcase irb(main):001:0> str = 'I am a String' "I am a String" irb(main):002:0> str.downcase "i am a string"

Slide 78

Slide 78 text

String#downcase irb(main):001:0> str = 'I am a String' "I am a String" irb(main):002:0> str.downcase "i am a string" irb(main):003:0> str "I am a String"

Slide 79

Slide 79 text

String#<< irb(main):001:0> str = 'Is << pure?' "Is << pure?"

Slide 80

Slide 80 text

String#<< irb(main):001:0> str = 'Is << pure?' "Is << pure?" irb(main):002:0> str << " Let's find out!" "Is << pure? Let's find out!"

Slide 81

Slide 81 text

String#<< irb(main):001:0> str = 'Is << pure?' "Is << pure?" irb(main):002:0> str << " Let's find out!" "Is << pure? Let's find out!" irb(main):003:0> str "Is << pure? Let's find out!"

Slide 82

Slide 82 text

client = NFLData::Client.new(api_key: ENV['NFL_API_KEY']) case ARGV[0] when 'team_data' puts client.team_data(team_name: ARGV[1]) when 'game_data' puts client.game_data(team1: ARGV[1], team2: ARGV[2], date: ARGV[3]) else 'Not a recognized command' end

Slide 83

Slide 83 text

module NFLData class Client def initialize(api_key:, fetcher: default_fetcher) @api_key = api_key @fetcher = fetcher end def team_data(team_name:, metadata_adder: NFLData::Metadata::Team) url = "http://nfl.com/api/v1/teams/#{team_name}?api_key=#{api_key}" data = fetcher.call(url) metadata_adder.call(data: data) end def game_data(team1:, team2:, date:, metadata_adder: NFLData::Metadata::Game) url = “http://nfl.com/api/v1/games/#{date}?teams=#{team1},#{team2} &api_key=#{api_key}" data = fetcher.call(url) metadata_adder.call(data: data) end private attr_reader :api_key, :fetcher def default_fetcher end end

Slide 84

Slide 84 text

module NFLData module Metadata class Team def self.call(**args) new(args).call end def initialize(data:) @data = data end def call add_turnover_data add_win_percentage add_how_team_is_doing data.freeze end private attr_reader :data # ... end end end

Slide 85

Slide 85 text

module NFLData module Metadata class Game def self.call(**args) new(args).call end def initialize(data:) @data = data end def call add_rushing_comparison add_passing_comparison add_defensive_comparison data.freeze end private attr_reader :data # ... end end end

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

No content

Slide 88

Slide 88 text

Takeaways

Slide 89

Slide 89 text

Takeaways Method objects are your friend

Slide 90

Slide 90 text

Takeaways Method objects are your friend Avoid the mystery guest

Slide 91

Slide 91 text

Takeaways Method objects are your friend Avoid the mystery guest Look for pure functions ✨

Slide 92

Slide 92 text

Takeaways Method objects are your friend Avoid the mystery guest Look for pure functions ✨ Delay and group side effects

Slide 93

Slide 93 text

Takeaways Method objects are your friend Avoid the mystery guest Look for pure functions ✨ Delay and group side effects Start thinking about the future

Slide 94

Slide 94 text

Grazie mille per l'ascolto! Chi ha domande?