O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.
THINKING
FUNCTIONALLY
 IN
 RUBY.
      @tomstuart
1: Functional
   programming
   is a pretty
   neat idea.
2: Enumerable
   contains
   some useful
   methods.
I hope
these things
are connected.
1.
(Functional programming is a
      pretty neat idea)
What is
  functional
programming?
A programming
style.
Two broad
families of
languages:
• Scheme
  • Common Lisp
  • Dylan
  • Clojure

1. Lisp-like
  • Dynamically typed
  • Homoiconic (code is data)
  • Lists...
• Standard ML
  • Haskell
  • OCaml
  • Scala (sort of)

2. ML-like
  • Statically typed (+ reconstruction)
  • Code is no...
Functions
as values
What is
a function?
1               2
7                                       6
    2
                    3       3
    6                   1 ...
Functions
as values
“First-class functions”
      “closures”, “lambdas”,
     “anonymous functions”



Implies: higher-order
      functions
No side
effects
Values are immutable

 All functions do is
 return their result
        No state
         No I/O
(In reality,
 different languages
   accomplish this
to varying degrees.)
Implies: recursion

Implies: persistent
  data structues
So what?
Unfortunately...
Declarative                  T!
                           W HA
 programming
is counterintuitive when
 youʼre accustomed t...
Copying is expensive
But!
Referential
            transparency
“an expression can be replaced with its value”

       •   Good for efficiency
       ...
Highly expressive

Strongly compositional

  Deeply satisfying
Future-proof
• Mooreʼs Law is running out of steam
  • similar transistor density, more cores
• The futureʼs concurrent
• ...
OK seriously:
 so what?
Ruby can do
 some of this.
 Not really a functional language, but we
can pretend and get some of the benefits.
Use function values
       blocks, procs


lambda { |x| x + 1 }
Consider treating
  your values
 as immutable
   State is rarely worth it.
  It will kill you in the end.
http://clojure.org/state
Use the
functional-flavoured
    parts of the
  standard library
Think functionally.
     Be declarative.
     What, not how.
2.
(Enumerable contains
 some useful methods)
Enumerable#zip
[1, 2, 3, 4].
  zip([5, 6, 7, 8])
1   2   3   4


5   6   7   8
1   5   2   6   3   7   4   8
[                            ]
[1 , 5],[2 , 6],[3 , 7],[4 , 8]
[1, 2, 3, 4].
  zip([5, 6, 7, 8],
      [9, 10, 11, 12])
Enumerable#select
 (a.k.a. #find_all)
{ |x| x.odd? }
1
?
1
!
?
1
1   2
!
?
1   ?
1   2
!
?
1   "
    ?
    2
[1, 2, 3, 4].
 select { |x| x.odd? }
1   2   3   4
?   ?   ?   ?
1   2   3   4
!
?
1   "
    ?
    2   !
        ?
        3   "
            ?
            4
1       3
!
?
1       !
        ?
        3
    1       3
[ 1, 3 ]
Enumerable#partition
[1, 2, 3, 4].
  partition { |x| x.odd? }
1   2   3   4
?   ?   ?   ?
1   2   3   4
!
?
1   "
    ?
    2   !
        ?
        3   "
            ?
            4
1   3   2   4
!
?   !
    ?   "
        ?   "
            ?
1       3       2       4
!
?       !
        ?       "
                ?       "
                        ?
    1       3 ...
[[ 1 , 3 ] , [ 2 , 4 ]]
Enumerable#map
(a.k.a. #collect)
{ |x| x * 3 }
2
×3
6
2
×3
2
6
[1, 2, 3, 4].
   map { |x| x * 3 }
1    2    3    4
×3
3    ×3
     6    ×3
          9    ×3
               12
1    2    3    4
×3
1    ×3
     2    ×3
          3    ×3
               4
3    6    9    12
[ , , , ]
 3   6   9 12
Enumerable#inject
  (a.k.a. #reduce)
{ |x, y| x + y }
3       5
        8
    +
3   5
3+5     8
[1, 2, 3, 4].
  inject(0) { |x,y| x+y }
0
        1
        1
    +
                2
                3
            +
                        3
                  ...
0 1
0+1
      1   2
      1+2
              3   3
              3+3
                      6   4
                      6 + ...
module Enumerable
  def inject(initial)
    result = initial

   for element in self
     result = yield(result, element)
...
a.k.a. “left fold”
(foldl, fold_left)
0 1 2 3 4
10
(((0 + 1) + 2) + 3) + 4
...versus “right fold”
(foldr, fold_right)
1 2 3 4 0
10
1 + (2 + (3 + (4 + 0)))
The initial argument is
      optional...
[1, 2, 3, 4].
  inject { |x,y| x+y }
[2, 3, 4].4].
[1, 2, 3,
  inject(1)|x,y| x+yx+y }
  inject { { |x,y| }
...but only if the
output is the same
type as the input...
>> ['El', 'rug'].
   inject(0) { |l,s| l + s.length }
=> 5

>> ['El', 'rug'].
   inject { |l,s| l + s.length }
TypeError: ...
...and itʼs meaningful
 to get nil when the
  collection is empty
>> [].inject { |x,y| x+y }
=> nil

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

>> [].inject(1) { |x,y| x*y }
=> 1
P O SE !
C O M
[1, 2, 3, 4].
  map { |x| x * 3 }.
  inject(0) { |x| x+y }
1       2       3       4
    ×3
    3       ×3
            6       ×3
                    9       ×3
                    ...
1       2        3       4
 ×3
 1        ×3
          2        ×3
                   3      ×3
                          4...
I am so
 excited.
What now?
Review your
Ruby code.
Func it up.
result = ''
for name in names
  unless result.empty?
    result << ', '
  end
  result << name
end
result
!
result = ''
for name in names
  unless result.empty?
    result << ', '
  end
  result << name
end
result



names.join(...
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)
    en...
!
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)
    ...
[1, 2, 3, 1, 2, 3, 1, 2, 3].sort =
 [1, 1, 1, 2, 2, 2, 3, 3, 3]




def count_mines_near(x, y) # = (2, 8)
  ((x-1..x+1).en...
[1, 2, 3, 1, 2, 3, 1, 2, 3].sort =
 [1, 1, 1, 2, 2, 2, 3, 3, 3].zip(
 [7, 8, 9, 7, 8, 9, 7, 8, 9]) =
 [[1, 7], [1, 8], [1,...
[1, 2, 3, 1, 2, 3, 1, 2, 3].sort =
 [1, 1, 1, 2, 2, 2, 3, 3, 3].zip(
 [7, 8, 9, 7, 8, 9, 7, 8, 9]) =
 [[1, 7], [1, 8], [1,...
[      [1, 7],    [1, 8],   …,    [1, 1007],
       [2, 7],    [2, 8],   …,    [2, 1007],
            …,              …,  ...
[      [1, 7],    [1, 8],   …,    [1, 1007],
       [2, 7],    [2, 8],   …,    [2, 1007],
            …,              …,  ...
def numbers_near(n, radius)
  ((n - radius)..(n + radius)).entries
end

def squares_near(x, y, radius)
  diameter = (radiu...
Learn a
functional language
• OCaml
• Scheme (Google “SICP”)
• Clojure
• Scala
• Haskell? Erlang?
Thanks!
@tomstuart / tom@experthuman.com
http://www.flickr.com/photos/somemixedstuff/2403249501/




  http://en.wikipedia.org/wiki/File:Modified-pc-case.png
Thinking Functionally In Ruby
Thinking Functionally In Ruby
Thinking Functionally In Ruby
Próximos SlideShares
Carregando em…5
×

Thinking Functionally In Ruby

3.128 visualizações

Publicada em

Publicada em: Tecnologia
  • Seja o primeiro a comentar

Thinking Functionally In Ruby

  1. 1. THINKING FUNCTIONALLY IN RUBY. @tomstuart
  2. 2. 1: Functional programming is a pretty neat idea.
  3. 3. 2: Enumerable contains some useful methods.
  4. 4. I hope these things are connected.
  5. 5. 1. (Functional programming is a pretty neat idea)
  6. 6. What is functional programming?
  7. 7. A programming style.
  8. 8. Two broad families of languages:
  9. 9. • Scheme • Common Lisp • Dylan • Clojure 1. Lisp-like • Dynamically typed • Homoiconic (code is data) • Lists are fundamental
  10. 10. • Standard ML • Haskell • OCaml • Scala (sort of) 2. ML-like • Statically typed (+ reconstruction) • Code is not data • ADTs and pattern matching
  11. 11. Functions as values
  12. 12. What is a function?
  13. 13. 1 2 7 6 2 3 3 6 1 7 5 4 8 4
  14. 14. Functions as values
  15. 15. “First-class functions” “closures”, “lambdas”, “anonymous functions” Implies: higher-order functions
  16. 16. No side effects
  17. 17. Values are immutable All functions do is return their result No state No I/O
  18. 18. (In reality, different languages accomplish this to varying degrees.)
  19. 19. Implies: recursion Implies: persistent data structues
  20. 20. So what?
  21. 21. Unfortunately...
  22. 22. Declarative T! W HA programming is counterintuitive when youʼre accustomed to imperative HOW ! programming
  23. 23. Copying is expensive
  24. 24. But!
  25. 25. 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
  26. 26. Highly expressive Strongly compositional Deeply satisfying
  27. 27. 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
  28. 28. OK seriously: so what?
  29. 29. Ruby can do some of this. Not really a functional language, but we can pretend and get some of the benefits.
  30. 30. Use function values blocks, procs lambda { |x| x + 1 }
  31. 31. Consider treating your values as immutable State is rarely worth it. It will kill you in the end.
  32. 32. http://clojure.org/state
  33. 33. Use the functional-flavoured parts of the standard library
  34. 34. Think functionally. Be declarative. What, not how.
  35. 35. 2. (Enumerable contains some useful methods)
  36. 36. Enumerable#zip
  37. 37. [1, 2, 3, 4]. zip([5, 6, 7, 8])
  38. 38. 1 2 3 4 5 6 7 8
  39. 39. 1 5 2 6 3 7 4 8
  40. 40. [ ] [1 , 5],[2 , 6],[3 , 7],[4 , 8]
  41. 41. [1, 2, 3, 4]. zip([5, 6, 7, 8], [9, 10, 11, 12])
  42. 42. Enumerable#select (a.k.a. #find_all)
  43. 43. { |x| x.odd? }
  44. 44. 1 ?
  45. 45. 1 ! ? 1
  46. 46. 1 2 ! ? 1 ?
  47. 47. 1 2 ! ? 1 " ? 2
  48. 48. [1, 2, 3, 4]. select { |x| x.odd? }
  49. 49. 1 2 3 4 ? ? ? ?
  50. 50. 1 2 3 4 ! ? 1 " ? 2 ! ? 3 " ? 4
  51. 51. 1 3 ! ? 1 ! ? 3 1 3
  52. 52. [ 1, 3 ]
  53. 53. Enumerable#partition
  54. 54. [1, 2, 3, 4]. partition { |x| x.odd? }
  55. 55. 1 2 3 4 ? ? ? ?
  56. 56. 1 2 3 4 ! ? 1 " ? 2 ! ? 3 " ? 4
  57. 57. 1 3 2 4 ! ? ! ? " ? " ?
  58. 58. 1 3 2 4 ! ? ! ? " ? " ? 1 3 2 4
  59. 59. [[ 1 , 3 ] , [ 2 , 4 ]]
  60. 60. Enumerable#map (a.k.a. #collect)
  61. 61. { |x| x * 3 }
  62. 62. 2 ×3 6
  63. 63. 2 ×3 2 6
  64. 64. [1, 2, 3, 4]. map { |x| x * 3 }
  65. 65. 1 2 3 4 ×3 3 ×3 6 ×3 9 ×3 12
  66. 66. 1 2 3 4 ×3 1 ×3 2 ×3 3 ×3 4 3 6 9 12
  67. 67. [ , , , ] 3 6 9 12
  68. 68. Enumerable#inject (a.k.a. #reduce)
  69. 69. { |x, y| x + y }
  70. 70. 3 5 8 +
  71. 71. 3 5 3+5 8
  72. 72. [1, 2, 3, 4]. inject(0) { |x,y| x+y }
  73. 73. 0 1 1 + 2 3 + 3 6 + 4 + 10
  74. 74. 0 1 0+1 1 2 1+2 3 3 3+3 6 4 6 + 4 10
  75. 75. module Enumerable def inject(initial) result = initial for element in self result = yield(result, element) end result end end
  76. 76. a.k.a. “left fold” (foldl, fold_left)
  77. 77. 0 1 2 3 4
  78. 78. 10 (((0 + 1) + 2) + 3) + 4
  79. 79. ...versus “right fold” (foldr, fold_right)
  80. 80. 1 2 3 4 0
  81. 81. 10 1 + (2 + (3 + (4 + 0)))
  82. 82. The initial argument is optional...
  83. 83. [1, 2, 3, 4]. inject { |x,y| x+y }
  84. 84. [2, 3, 4].4]. [1, 2, 3, inject(1)|x,y| x+yx+y } inject { { |x,y| }
  85. 85. ...but only if the output is the same type as the input...
  86. 86. >> ['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
  87. 87. ...and itʼs meaningful to get nil when the collection is empty
  88. 88. >> [].inject { |x,y| x+y } => nil >> [].inject(0) { |x,y| x+y } => 0 >> [].inject(1) { |x,y| x*y } => 1
  89. 89. P O SE ! C O M
  90. 90. [1, 2, 3, 4]. map { |x| x * 3 }. inject(0) { |x| x+y }
  91. 91. 1 2 3 4 ×3 3 ×3 6 ×3 9 ×3 12 0 3 + 9 + + 18 + 30
  92. 92. 1 2 3 4 ×3 1 ×3 2 ×3 3 ×3 4 0 3 0+3 3 6 3+6 9 9 9 + 9 18 12 18+12 30
  93. 93. I am so excited. What now?
  94. 94. Review your Ruby code. Func it up.
  95. 95. result = '' for name in names unless result.empty? result << ', ' end result << name end result
  96. 96. ! result = '' for name in names unless result.empty? result << ', ' end result << name end result names.join(', ')
  97. 97. 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 count end
  98. 98. ! 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 count end 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) }. length end
  99. 99. [1, 2, 3, 1, 2, 3, 1, 2, 3].sort = [1, 1, 1, 2, 2, 2, 3, 3, 3] def count_mines_near(x, y) # = (2, 8) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select { |x, y| mine_at?(x, y) }. length end
  100. 100. [1, 2, 3, 1, 2, 3, 1, 2, 3].sort = [1, 1, 1, 2, 2, 2, 3, 3, 3].zip( [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]] def count_mines_near(x, y) # = (2, 8) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select { |x, y| mine_at?(x, y) }. length end
  101. 101. [1, 2, 3, 1, 2, 3, 1, 2, 3].sort = [1, 1, 1, 2, 2, 2, 3, 3, 3].zip( [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]].select {…} = [[1, 8], [3, 7]].length = 2 def count_mines_near(x, y) # = (2, 8) ((x-1..x+1).entries * 3).sort. zip((y-1..y+1).entries * 3). select { |x, y| mine_at?(x, y) }. length end
  102. 102. [ [1, 7], [1, 8], …, [1, 1007], [2, 7], [2, 8], …, [2, 1007], …, …, …, [1000, 7], [1000, 8], …, [1000, 1007], [1001, 7], [1000, 8], …, [1001, 1007]] 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) }. length end
  103. 103. [ [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
  104. 104. def numbers_near(n, radius) ((n - radius)..(n + radius)).entries end 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) }. length end
  105. 105. Learn a functional language • OCaml • Scheme (Google “SICP”) • Clojure • Scala • Haskell? Erlang?
  106. 106. Thanks! @tomstuart / tom@experthuman.com
  107. 107. http://www.flickr.com/photos/somemixedstuff/2403249501/ http://en.wikipedia.org/wiki/File:Modified-pc-case.png

×