Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Functional Objects in Ruby: new horizons – Valentine Ostakh

63 views

Published on

Ruby Meditation #22
May 19, 2018
Dnipro

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Functional Objects in Ruby: new horizons – Valentine Ostakh

  1. 1. Functional Objects in Ruby new horizons Valentyn Ostakh
  2. 2. What is Functional Object?
  3. 3. at first: Object with callable interface
  4. 4. Callable interface #call
  5. 5. proc_fn = proc { |x| 1 + x } lambda_fn = lambda { |x| 1 + x } method_fn = 1.method(:+) proc_fn.call(2) # => 3 lambda_fn.call(2) # => 3 method_fn.call(2) # => 3 Callable interface
  6. 6. Object + callable interface class CallablePORO def call # TODO end def self.call # TODO end end CallablePORO.call CallablePORO.new.call
  7. 7. Callable interface in action Rack provides a minimal interface between webservers that support Ruby and Ruby frameworks. To use Rack, provide an object that responds to the call method.
  8. 8. secondly: Object which behave like functions from FP
  9. 9. What is FP?
  10. 10. What is FP? • programming paradigm • stateless • immutability • evaluation of mathematical functions • functions everywhere
  11. 11. FP concepts • Purity • Immutability • Higher-order functions • Closures • Function Composition • Point-Free Notation • Currying • Common Functional Functions • Referential Transparency
  12. 12. Purity
  13. 13. Purity Pure Functions are very simple functions. They only operate on their input parameters. Pure Functions will always produce the same output given the same inputs. Most useful Pure Functions must take at least one parameter. All useful Pure Functions must return something. Pure functions have no side effects.
  14. 14. Purity z = 10 def sum(x, y) x + y end def just10 10 end Pure functions
  15. 15. Purity z = 10 def sum(x, y) x + y + z end def convert(file_name) input = File.read(file_name) output = JSON2YML.convert(input) File.write('output.yml', output) end Impure functions
  16. 16. Purity You don’t just write Pure Functions. Since programs have to interface to the real world, some parts of every program must be impure. The goal is to minimize the amount of impure code and segregate it from the rest of our program.
  17. 17. Immutability
  18. 18. Immutability str = 'bite my shiny metal ass' str.object_id # => 70317488591440 upper_str = str.upcase upper_str # => "BITE MY SHINY METAL ASS" upper_str.object_id # => 70317484572200 The simplest examples are the primitive objects: Symbol, String, Integer, TrueClass(true), FalseClass(false), NilClass(nil) …
  19. 19. Immutability Value Objects • Small object that represents a simple entity whose equality is not based on identity • Value objects have multiple attributes • Attributes should be immutable throughout its life cycle • Equality is determined by its attributes (and its type)
  20. 20. Immutability There are no variables in Functional Programming. Stored values are still called variables because of history but they are constants, i.e. once x takes on a value, it’s that value for life. x = 5 x = x + 1 In math, x can never be equal to x + 1
  21. 21. Higher-Order Functions
  22. 22. Higher-Order Functions In Functional Programming, a function is a first-class citizen of the language. In other words, a function is just another value. Since functions are just values, we can pass them as parameters. Higher-order Functions either take functions as parameters, return functions or both.
  23. 23. Higher-Order Functions # returns value values = [1, 2, 3, 4, 5] values.map do |value| value * value end # => [1, 4, 9, 16, 25] # returns function def adder(a, b) lambda { a + b } end adder_fn = adder(1, 2) # => #<Proc: (lambda)> adder_fn.call # => 3
  24. 24. Closures
  25. 25. Closures Techniques for implementing lexically scoped name binding in languages with first-class functions When a function is created, all of the variables in its scope at the time of creation are accessible to it for the lifetime of the function. A function exists as long as there still a reference to it. A closure is a function’s scope that’s kept alive by a reference to that function.
  26. 26. Closures outer = 1 def m inner = 99 yield inner puts "inner var = #{inner}" end m { |inner| outer += inner } # => 99 puts "outer var = #{outer}" # => 100
  27. 27. Functional Composition
  28. 28. Functional Composition Code reuse sounds great but is difficult to achieve. Make the code too specific and you can’t reuse it. Make it too general and it can be too difficult to use in the first place. So what we need is a balance between the two, a way to make smaller, reusable pieces that we can use as building blocks to construct more complex functionality.
  29. 29. Functional Composition a = lambda { |x| x + 4 } b = lambda { |y| y / 2 } a.compose(b).call(4) #=> 6 b.compose(a).call(4) #=> 4
  30. 30. Functional Composition add10 = -> value { value + 10 } mult5 = -> value { value * 5 } compose = -> (fn1, fn2) { -> value { fn1.(fn2.(value)) } } add10mult5 = compose.(mult5, add10) mult5add10 = compose.(add10, mult5) add10mult5.(5) # => 75 mult5add10.(5) # => 35
  31. 31. Functional Composition In math,  f ∘ g is functional composition and is read “f composed with g” or, more commonly, “f after g”. So (f ∘ g)(x) is equivalent to calling  f after calling g with x or simply,  f(g(x)).
  32. 32. Point-Free Notation
  33. 33. Point-Free Notation A style of writing functions without having to specify the parameters. First, we don’t have to specify redundant parameters. And since we don’t have to specify them, we don’t have to think up names for all of them. Second, it’s easier to read and reason about since it’s less verbose. This example is simple, but imagine a function that took more parameters. Tacit programming is a programming paradigm in which a function definition does not include information regarding its arguments, using combinators and function composition instead of variables
  34. 34. Point-Free Notation tacit_string = :to_s.to_proc def increment self + 1 end tacit_increment = :increment.to_proc tacit_string.call(:Bender) # => "Bender" tacit_increment.call(101) # => 102
  35. 35. Currying
  36. 36. Currying Function decomposition. A Curried Function is a function that only takes a single parameter at a time. A Curried Function is a sequence of functions.
  37. 37. Currying add = -> x, y { x + y } native_curry = add.curry curry_add = -> x { -> y { x + y } } add.(5, 7) # => 12 native_curry.(5) # => #<Proc: (lambda)> curry_add.(5) # => #<Proc: (lambda)> native_curry.(5).(7) # => 12 curry_add.(5).(7) # => 12
  38. 38. Common Functional Functions
  39. 39. Common Functional Functions This function is usually called fold in Functional Languages. Higher-Order Functions for processing data structures for building up new values. Map, Reduce, Select, Find … Functional Programming uses recursion to do looping.
  40. 40. Common Functional Functions values = [{k: :foo, v: 1}, {k: :bar, v: 2}, {k: :foo, v: 2}] values.select { |value| value[:k] == :foo } # => [{k: :foo, v: 1}, {k: :foo, v: 2}] values.find { |value| value[:k] == :foo } # => [{k: :foo, v: 1}] values.reduce(0) { |m, value| m += value[:v] } # => 5
  41. 41. Referential Transparency
  42. 42. Referential Transparency Referential Transparency is a fancy term to describe that a pure function can safely be replaced by its expression.
  43. 43. Referential Transparency x = 3 x + 7 # => 10 3 + 7 # => 10
  44. 44. Not off all FP concepts are applicable in Ruby не все концепции из ФП применимы к ООП Правильнее будет сказать так, фп языки построены на данных концепциях, в этом их особенность, В ОО языках мы можем лишь имитировать данные концепции
  45. 45. New horizons
  46. 46. Purity + Object
  47. 47. Functional Object
  48. 48. Functional object abilities • #call(input) • no side-effects • return some output
  49. 49. Callable object class SurfaceGravity attr_reader :weight def initialize(weight) @weight = weight end def call weight * 9.8 end end weight_125 = SurfaceGravity.new(125) weight_696 = SurfaceGravity.new(696) weight_125.call # => 1225.0 weight_696.call # => 6820.8
  50. 50. Callable object class SurfaceGravity attr_reader :weight def initialize(weight) @weight = weight end def call weight * 9.8 end end weight_125 = SurfaceGravity.new(125) weight_696 = SurfaceGravity.new(696) weight_125.call # => 1225.0 weight_696.call # => 6820.8
  51. 51. Achievements • #call(input) • no side-effects • return some output
  52. 52. Functional object class SurfaceGravity def call(weight) weight * 9.8 end end surface_gravity = SurfaceGravity.new surface_gravity.call(125) # => 1225.0 surface_gravity.call(696) # => 6820.8
  53. 53. Functional object class SurfaceGravity def call(weight) weight * 9.8 end end surface_gravity = SurfaceGravity.new surface_gravity.call(125) # => 1225.0 surface_gravity.call(696) # => 6820.8
  54. 54. Achievements • #call(input) • no side-effects • return some output
  55. 55. Callable Object class LaunchShuttle attr_reader :shuttle def initialize(shuttle) @shuttle = shuttle end def call CheckFuelSystem.new(shuttle).call ConfirmLaunchReady.new(shuttle).call end end
  56. 56. Callable Object class LaunchShuttle attr_reader :shuttle def initialize(shuttle) @shuttle = shuttle end def call CheckFuelSystem.new(shuttle).call ConfirmLaunchReady.new(shuttle).call end end
  57. 57. Achievements • #call(input) • no side-effects • return some output
  58. 58. Functional Object class LaunchShuttle attr_reader :check_engines, :confirm_launch_ready def initialize(check_engines:, confirm_launch_ready:) @check_engines = check_engines @confirm_launch_ready = confirm_launch_ready end def call(shuttle) check_engines.call(shuttle) confirm_launch_ready.call(shuttle) end end
  59. 59. Functional Object class LaunchShuttle attr_reader :check_engines, :confirm_launch_ready def initialize(check_engines:, confirm_launch_ready:) @check_engines = check_engines @confirm_launch_ready = confirm_launch_ready end def call(shuttle) check_engines.call(shuttle) confirm_launch_ready.call(shuttle) end end
  60. 60. Achievements • #call(input) • no side-effects • return some output
  61. 61. Functional Object class LaunchShuttle attr_reader :check_engines, :confirm_launch_ready def initialize(check_engines:, confirm_launch_ready:) @check_engines = check_engines @confirm_launch_ready = confirm_launch_ready end def call(shuttle) check_engines.call(shuttle) confirm_launch_ready.call(shuttle) end end
  62. 62. Functional Object launch_shuttle = LaunchShuttle.new( check_engines: CheckEngines.new, confirm_launch_ready: ConfirmLaunchReady.new ) launch_shuttle.call(shuttle)
  63. 63. Discover Dependency Injection
  64. 64. Single Responsibility Open/closed Liskov substitution Interface segregation Dependency inversion
  65. 65. Inversion of Control container
  66. 66. Flow of control
  67. 67. Functional Object class LaunchShuttle attr_reader :check_engines, :confirm_launch_ready def initialize(check_engines:, confirm_launch_ready:) @check_engines = check_engines @confirm_launch_ready = confirm_launch_ready end def call(shuttle) check_engines.call(shuttle) confirm_launch_ready.call(shuttle) end end
  68. 68. Achievements • #call(input) • side-effects under control • return some output
  69. 69. Single Responsibility Open/closed Liskov substitution Interface segregation Dependency inversion
  70. 70. Single Responsibility A class should have only one reason to change
  71. 71. Open / closed Entities should be open for extension, but closed for modification
  72. 72. Liskov substitution Subtypes must be substitutable for their base types
  73. 73. Interface segregation Many client-specific interfaces are better than one general-purpose interface
  74. 74. Dependency inversion One should depend upon abstractions, not concretions
  75. 75. Functional objects benefits • simplicity • reusability • composition • following SOLID • architecture style
  76. 76. Inspired by • Functional Architecture for the Practical Rubyist - RedDotRubyConf 2017: https://www.youtube.com/watch? time_continue=962&v=7qnsRejCyEQ • Next Generation Ruby Web Appswith dry-rb, ROM, and Roda - RedDotRubyConf 2016: https://www.youtube.com/watch? v=6ecNAjVWqaI • Full Stack Fest 2015: Blending Functional and OO Programming in Ruby, by Piotr Solnica: https://www.youtube.com/watch? v=rMxurF4oqsc • RubyConf 2017: 4 Programming Paradigms in 45 Minutes by Aja Hammerly: https://www.youtube.com/watch?v=3TBq__oKUzk
  77. 77. Thank you!
  78. 78. Questions?

×