Thinking Functionally In Ruby

110
THINKING FUNCTIONALLY IN RUBY. @tomstuart

description

 

Transcript of Thinking Functionally In Ruby

Page 1: Thinking Functionally In Ruby

THINKINGFUNCTIONALLY

IN

RUBY.@tomstuart

Page 2: Thinking Functionally In Ruby

Functional programming is a pretty neat idea.

1:

Page 3: Thinking Functionally In Ruby

Enumerable contains some useful methods.

2:

Page 4: Thinking Functionally In Ruby

I hopethese things are connected.

Page 5: Thinking Functionally In Ruby

(Functional programming is a pretty neat idea)

1.

Page 6: Thinking Functionally In Ruby

What is functional

programming?

Page 7: Thinking Functionally In Ruby

A programming style.

Page 8: Thinking Functionally In Ruby

Two broad families of languages:

Page 9: Thinking Functionally In Ruby

1. Lisp-like

• Scheme• Common Lisp• Dylan• Clojure

• Dynamically typed• Homoiconic (code is data)• Lists are fundamental

Page 10: Thinking Functionally In Ruby

2. ML-like

• Standard ML• Haskell• OCaml• Scala (sort of)

• Statically typed (+ reconstruction)• Code is not data• ADTs and pattern matching

Page 11: Thinking Functionally In Ruby

Functionsas values

Page 12: Thinking Functionally In Ruby

What isa function?

Page 13: Thinking Functionally In Ruby
Page 14: Thinking Functionally In Ruby
Page 15: Thinking Functionally In Ruby
Page 16: Thinking Functionally In Ruby

172

36

45 4

26

8

371

Page 17: Thinking Functionally In Ruby

Functionsas values

Page 18: Thinking Functionally In Ruby

“First-class functions”

Implies: higher-order functions

“closures”, “lambdas”,“anonymous functions”

Page 19: Thinking Functionally In Ruby

No side effects

Page 20: Thinking Functionally In Ruby

Values are immutable

All functions do is return their result

No stateNo I/O

Page 21: Thinking Functionally In Ruby

(In reality,different languages

accomplish thisto varying degrees.)

Page 22: Thinking Functionally In Ruby

Implies: recursion

Implies: persistent data structues

Page 23: Thinking Functionally In Ruby

So what?

Page 24: Thinking Functionally In Ruby

Unfortunately...

Page 25: Thinking Functionally In Ruby

Declarative programming

is counterintuitive whenyouʼre accustomed to

imperative programming

WHAT!

HOW!

Page 26: Thinking Functionally In Ruby

Copying is expensive

Page 27: Thinking Functionally In Ruby

But!

Page 28: Thinking Functionally In Ruby

Referential transparency

“an expression can be replaced with its value”

• Good for efficiency• caching/memoisation/inlining• ultimately more efficient

• Good for programmer understanding• state is incredibly hard to deal with• ultimately more intuitive

• Good for code reuse and testing

Page 29: Thinking Functionally In Ruby

Highly expressive

Deeply satisfying

Strongly compositional

Page 30: Thinking Functionally In Ruby

Future-proof• Mooreʼs Law is running out of steam• similar transistor density, more cores

• The futureʼs concurrent• Concurrent access to mutable state is hard...• but not if you donʼt have mutable state!

• Parallelising an algorithm is hard...• but itʼs easier if your code isnʼt overspecified

Page 31: Thinking Functionally In Ruby

OK seriously: so what?

Page 32: Thinking Functionally In Ruby

Ruby can do some of this.Not really a functional language, but we

can pretend and get some of the benefits.

Page 33: Thinking Functionally In Ruby

Use function values

lambda { |x| x + 1 }

blocks, procs

Page 34: Thinking Functionally In Ruby

Consider treating your values

as immutableState is rarely worth it.

It will kill you in the end.

Page 35: Thinking Functionally In Ruby

http://clojure.org/state

Page 36: Thinking Functionally In Ruby

Use thefunctional-flavoured

parts of thestandard library

Page 37: Thinking Functionally In Ruby

Be declarative.

Think functionally.

What, not how.

Page 38: Thinking Functionally In Ruby

2.(Enumerable contains some useful methods)

Page 39: Thinking Functionally In Ruby

Enumerable#zip

Page 40: Thinking Functionally In Ruby

[1, 2, 3, 4]. zip([5, 6, 7, 8])[1, 2, 3, 4]. zip([5, 6, 7, 8])

Page 41: Thinking Functionally In Ruby

21 3 4

65 7 8

Page 42: Thinking Functionally In Ruby

21 3 465 7 8

Page 43: Thinking Functionally In Ruby

[ ]21 3 465 7 8[ ], , , ,[ ] [ ] [ ], , ,

Page 44: Thinking Functionally In Ruby

[1, 2, 3, 4]. zip([5, 6, 7, 8], [9, 10, 11, 12])

Page 45: Thinking Functionally In Ruby

Enumerable#select(a.k.a. #find_all)

Page 46: Thinking Functionally In Ruby

{ |x| x.odd? }

Page 47: Thinking Functionally In Ruby

11?

Page 48: Thinking Functionally In Ruby

11?!

Page 49: Thinking Functionally In Ruby

11?!

22?

Page 50: Thinking Functionally In Ruby

11?!

22?"

Page 51: Thinking Functionally In Ruby

[1, 2, 3, 4]. select { |x| x.odd? }

Page 52: Thinking Functionally In Ruby

1 2 3 421 3 4? ? ? ?

Page 53: Thinking Functionally In Ruby

1 2 3 421 3 4? ? ? ?! "" !

Page 54: Thinking Functionally In Ruby

1 31 3? ?! !

1 3

Page 55: Thinking Functionally In Ruby

1 3[ ],

Page 56: Thinking Functionally In Ruby

Enumerable#partition

Page 57: Thinking Functionally In Ruby

[1, 2, 3, 4]. partition { |x| x.odd? }

Page 58: Thinking Functionally In Ruby

1 2 3 421 3 4? ? ? ?

Page 59: Thinking Functionally In Ruby

1 2 3 421 3 4? ? ? ?! "" !

Page 60: Thinking Functionally In Ruby

1?!

4?"

2?"

3?!

Page 61: Thinking Functionally In Ruby

1?!

4?"

2?"

3?!

1 3 2 4

Page 62: Thinking Functionally In Ruby

1 3[ ], 2 4[ ],,[ ]

Page 63: Thinking Functionally In Ruby

Enumerable#map(a.k.a. #collect)

Page 64: Thinking Functionally In Ruby

{ |x| x * 3 }

Page 65: Thinking Functionally In Ruby

226×3

Page 66: Thinking Functionally In Ruby

226×3

Page 67: Thinking Functionally In Ruby

[1, 2, 3, 4]. map { |x| x * 3 }

Page 68: Thinking Functionally In Ruby

1 2 3 421 3 463 9 12×3 ×3 ×3 ×3

Page 69: Thinking Functionally In Ruby

1 2 3 421 3 463 9 12

×3 ×3 ×3 ×3

Page 70: Thinking Functionally In Ruby

63 9 12[ ], , ,

Page 71: Thinking Functionally In Ruby

Enumerable#inject(a.k.a. #reduce)

Page 72: Thinking Functionally In Ruby

{ |x, y| x + y }

Page 73: Thinking Functionally In Ruby

33 558+

Page 74: Thinking Functionally In Ruby

33

55 8+

Page 75: Thinking Functionally In Ruby

[1, 2, 3, 4]. inject(0) { |x,y| x+y }[1, 2, 3, 4]. inject(0) { |x,y| x+y }

Page 76: Thinking Functionally In Ruby

1

2

3

4

1

2

3

4

0

1

3

6

10

+

+

+

+

Page 77: Thinking Functionally In Ruby

1

1 2

3 3

6 4

01

2

3

4

0

1

3

6 10

+

+

+

+

Page 78: Thinking Functionally In Ruby

module Enumerable def inject(initial) result = initial

for element in self result = yield(result, element) end

result endend

Page 79: Thinking Functionally In Ruby

a.k.a. “left fold”(foldl, fold_left)

Page 80: Thinking Functionally In Ruby

1 2 3 40

Page 81: Thinking Functionally In Ruby

10(((0 + 1) + 2) + 3) + 4

Page 82: Thinking Functionally In Ruby

...versus “right fold”(foldr, fold_right)

Page 83: Thinking Functionally In Ruby

1 2 3 4 0

Page 84: Thinking Functionally In Ruby

101 + (2 + (3 + (4 + 0)))

Page 85: Thinking Functionally In Ruby

The initial argument is optional...

Page 86: Thinking Functionally In Ruby

[1, 2, 3, 4]. inject { |x,y| x+y }

Page 87: Thinking Functionally In Ruby

[1, 2, 3, 4]. inject { |x,y| x+y }[2, 3, 4]. inject(1) { |x,y| x+y }

Page 88: Thinking Functionally In Ruby

...but only if the output is the same type as the input...

Page 89: Thinking Functionally In Ruby

>> ['El', 'rug']. inject(0) { |l,s| l + s.length }=> 5

>> ['El', 'rug']. inject { |l,s| l + s.length }TypeError: can't convert Fixnum into String from (irb):1:in `+' from (irb):1 from (irb):1:in `inject' from (irb):1:in `each' from (irb):1:in `inject' from (irb):1

Page 90: Thinking Functionally In Ruby

...and itʼs meaningful to get nil when the collection is empty

Page 91: Thinking Functionally In Ruby

>> [].inject { |x,y| x+y }=> nil

>> [].inject(0) { |x,y| x+y }=> 0

>> [].inject(1) { |x,y| x*y }=> 1

Page 92: Thinking Functionally In Ruby

COMPOSE!

Page 93: Thinking Functionally In Ruby

[1, 2, 3, 4]. map { |x| x * 3 }. inject(0) { |x| x+y }

Page 94: Thinking Functionally In Ruby

1 2 3 421 3 463 9 12×3 ×3 ×3 ×3

003

918

30

++

++

Page 95: Thinking Functionally In Ruby

36

912

1 2 3 421 3 4

63

912

×3 ×3 ×3 ×3

39

18

00

39

18 30

++

++

Page 96: Thinking Functionally In Ruby

I am so excited.

What now?

Page 97: Thinking Functionally In Ruby

Review your Ruby code.

Func it up.

Page 98: Thinking Functionally In Ruby

result = ''for name in names unless result.empty? result << ', ' end result << nameendresult

Page 99: Thinking Functionally In Ruby

result = ''for name in names unless result.empty? result << ', ' end result << nameendresult

names.join(', ')

!

Page 100: Thinking Functionally In Ruby

def count_mines_near(x, y) count = 0 for i in x-1..x+1 for j in y-1..y+1 count += 1 if mine_at?(i, j) end end countend

Page 101: Thinking Functionally In Ruby

def count_mines_near(x, y) count = 0 for i in x-1..x+1 for j in y-1..y+1 count += 1 if mine_at?(i, j) end end countend

def count_mines_near(x, y) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select { |x, y| mine_at?(x, y) }. lengthend

!

Page 102: Thinking Functionally In Ruby

def count_mines_near(x, y) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select { |x, y| mine_at?(x, y) }. lengthend

[1, 2, 3, 1, 2, 3, 1, 2, 3][1, 1, 1, 2, 2, 2, 3, 3, 3] .sort =

# = (2, 8)

Page 103: Thinking Functionally In Ruby

def count_mines_near(x, y) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select { |x, y| mine_at?(x, y) }. lengthend

[1, 2, 3, 1, 2, 3, 1, 2, 3][1, 1, 1, 2, 2, 2, 3, 3, 3][7, 8, 9, 7, 8, 9, 7, 8, 9][[1, 7], [1, 8], [1, 9], [2, 7], [2, 8], [2, 9], [3, 7], [3, 8], [3, 9]]

.sort = .zip( ) =

# = (2, 8)

Page 104: Thinking Functionally In Ruby

def count_mines_near(x, y) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select { |x, y| mine_at?(x, y) }. lengthend

[1, 2, 3, 1, 2, 3, 1, 2, 3][1, 1, 1, 2, 2, 2, 3, 3, 3][7, 8, 9, 7, 8, 9, 7, 8, 9][[1, 7], [1, 8], [1, 9], [2, 7], [2, 8], [2, 9], [3, 7], [3, 8], [3, 9]]

.sort = .zip( ) =

.select {…} =[[1, 8], [3, 7]] .length = 2

# = (2, 8)

Page 105: Thinking Functionally In Ruby

def count_mines_near(x, y) ((x-500..x+500).entries * 1001).sort. zip((y-500..y+500).entries * 1001). select { |x, y| mine_at?(x, y) }. lengthend

[ [1, 7], [1, 8], …, [1, 1007], [2, 7], [2, 8], …, [2, 1007], …, …, …, [1000, 7], [1000, 8], …, [1000, 1007], [1001, 7], [1000, 8], …, [1001, 1007]]

Page 106: Thinking Functionally In Ruby

[ [1, 7], [1, 8], …, [1, 1007], [2, 7], [2, 8], …, [2, 1007], …, …, …, [1000, 7], [1000, 8], …, [1000, 1007], [1001, 7], [1000, 8], …, [1001, 1007]]

173 96 121 78 237

705

Page 107: Thinking Functionally In Ruby

def numbers_near(n, radius) ((n - radius)..(n + radius)).entriesend

def squares_near(x, y, radius) diameter = (radius * 2) + 1 (numbers_near(x, radius) * diameter). sort. zip(numbers_near(y, radius) * diameter)end

def count_mines_near(x, y, radius = 1) squares_near(x, y, radius). select { |x, y| mine_at?(x, y) }. lengthend

Page 108: Thinking Functionally In Ruby

Learn afunctional language•OCaml•Scheme (Google “SICP”)•Clojure•Scala•Haskell? Erlang?