ブロックとprocsによるRubyの関数型プログラミング


オブジェクト指向プログラミング対機能プログラミング


Rubyは真のオブジェクト指向プログラミング言語です.一般的に、我々はオブジェクト指向(OOP)プログラミングが大好き!オブジェクト指向プログラミングは、私たちのコードを読みやすく、再利用可能な、モジュール化し、一般的にかなり簡単なパラダイムです.
Rubyでは、すべてがオブジェクトです.一般に、クラスとインスタンスにコードを分割するので、これはよく知られていない概念です.本当に、すべて!
それを試してみましょう!
self
# => main

self.class
# => Object

nil.class
# => NilClass

true.class
# => TrueClass
イーブンnil ? はい.nilsingleton インスタンスNilClass .
さて、これは何ですかmain 事まあ.main デフォルトはtop level context Rubyで我々に提供されます.すべてがオブジェクトであるので、我々はこれをグローバルオブジェクトと呼びます.しかし、私たちのグローバルなオブジェクトでさえObject .
実際には、クラスObject クラスのインスタンスですClass !
self.class.class
# => Class
我々はクラスのクラスをオーバーライドすることができますし、自分自身を確認!
class Class
  alias prev_new new

  def new(*args)
    print "Creating a new #{self.name} class. \n"
    prev_new(*args)
  end
end

class Text
end

t = Text.new
# => ...
# => Creating a new Text class.

方法は?


我々がこれをするとき、物事は少しトリッキーになります.Rubyメソッドは、コードのスニペットであり、オブジェクトではありません.
関数パラダイム(この場合、typescript)を考慮する他の言語では、関数を変数に割り当てて呼び出します.
function ourFunction():void {
    console.log("hello");
}

let variable = ourFunction
variable()
// "hello"
// => undefined
Rubyで同様のアプローチを試してみましょう.
def method
    puts "hello"
end

variable = method

variable()
# NoMethodError (undefined method `variable' for main:Object)
variable
# => nil
Rubyでvariable が返り値method . 我々が呼ぶようにmethod , それは暗黙のうちに呼び出されます.呼び出しmethod , このメソッドはmethod() . 現在のパラダイムでは、関数自体を別の変数に渡すことはできません.

高次関数


いくつかの点で、この制限は我々の創造力を妨げるDRY コード.
また、高次の関数を作成する能力を失います.

Higher-order function: a function that takes a function as a parameter or returns a function.


タイプスクリプトの単純な高次の機能を見てみましょう.
function multiplier(number1: number): (number2: number) => number {
    return function(number2: number): number {
        return number1 * number2
    }
}

let doubler = multiplier(2)
doubler(6)
// => 12
Rubyでは、ブロックとprocsのパワーを使用して関数プログラミングのいくつかの原則をエコーすることができます!それを与えましょう!

Proc 101


我々がnitty grittyコードに入る前に、休止して、よりよく理解しましょうprocs . エーProc コードのブロックをオブジェクトとして格納する特殊なRubyオブジェクトです.我々が我々を例示するようにProc インスタンスは、メソッドパラメーターの直後にブロックをインスタンス化することができます.
print_greetings = Proc.new() do
  puts "Welcome!"
  puts "Bonjour!"
  puts "¡Bienvenidas!"
end
print_greetings
# => #<Proc:0x00007fe0ff042a08>
いくつかの異なる方法でprocを呼び出すことができます.
  • メソッドの連鎖.call
  • 連鎖法.yield
  • ブラケット表記の使用[]
  • 起動.call 構文的糖で.()
  • print_greetings.call
    print_greetings.yield
    print_greetings[]
    print_greetings.()
    
    # Welcome!
    # Bonjour!
    # Bienvenidas!
    # => nil
    

    ブロックパラメータ


    通常のメソッドと同様に、ブロックはパラメータを受け取ることができます.実際には、いくつかの共通点があります.
  • ブロックパラメータはデフォルト値
  • キーワードを受け入れるためにブロックパラメータを設定できます
  • Splat演算子を使用します.* 未決定の量の引数を取得できるようにします.
  • パラメータをブロックするとき、いくつかの異なる規則があります.
  • 引数が指定されていない場合はnil パラメータに代入されます
  • ブロックの開始後、パイプの内部に引数が置かれます
  • procとして引数を受け取るprocを作りましょう.
    multiply_by_two = Proc.new do |item|
        item * 2
    end
    
    変換するProc ブロックに戻ると、我々は& 演算子の内部の演算子は、ブロックを期待します.以下の2つの例は等価です.
    [1,2,3,4,5].map do |item|
         item * 2
    end
    
    [1,2,3,4,5].map(&multiply_by_two)
    # => [2, 4, 6, 8, 10]
    
    どのようにクールです!メソッドにブロックを渡す規則をいくつかレビューしましょう.
  • proc変換を渡すときは、メソッドの最後の引数として来なければなりません..ourFunction(1, 2, &multiply_by_two)
  • つのブロックだけをメソッドに渡すことができます.電話しようとする[1,2,3,4,5].map(&multiply_by_two, &multiply_by_three) はsyntaxerrorを生成する.
  • procsによる高次関数の再生成


    上記の例から高次関数を再訪問し、ブロックとprocsの新たな知識を持って再作成しましょう.
    def multiplier(number1)
        Proc.new {|number2| number1 * number2}
    end
    
    doubler = multiplier(2)
    # => #<Proc:0x00007fe7719b91b8>
    
    doubler.call(6)
    # => 12
    

    収量を理解する


    あなたがレールに精通しているならば、あなたはおそらく見えましたyield キーワード.あなたの部品の注入を許可する.erb テンプレートの内部.同様に、普通の古いルビーでyield 現在のコードの実行を一時停止し、渡されたブロックにyieldします.
    def our_method
      puts "top of method"
      yield
      puts "bottom of method"
    end
    
    our_method {puts "we are inside the block"}
    # top of method
    # we are inside the block
    # bottom of method
    # => nil
    
    ブロックにパラメータを渡すことができます.
    def our_method_w_parameters
      puts "top of method"
      yield("karson", "nyc")
      puts "bottom of method"
    end
    
    our_method_w_parameters do |name, loc|
      puts "my name is #{name}, and I live in #{loc}"
    end
    
    # top of method
    # my name is karson, and I live in nyc
    # bottom of method
    # => nil
    
    しかし、それが難しいコード化されるとき、再利用できる方法はこのコードです?この属性を使用してインスタンス属性を使用するにはyield .

    proc範囲


    procsは定義されているスコープ内に存在します.これは、いくつかの誤解を招く可能性がありますself . 以下の例を見てみましょう.
    class Person
        attr_accessor :name, :loc
    
        def initialize(name, loc)
            @name = name
            @loc = loc
        end
    
        def ex_block
            yield
        end
    end
    
    k = Person.new("karson", "nyc")
    # => #<Person:0x00007fded014edb0 @name="karson", @loc="nyc">
    k.ex_block {puts self.name, self.loc}
    # NoMethodError (undefined method `name' for main:Object)
    
    selfmain オブジェクトがグローバルスコープにあることを示します.バインドしたいならself ブロックが呼ばれるインスタンスには、インスタンス化で定義されなければなりません.
    class Person
        attr_accessor :name, :loc, :instance_proc
    
        def initialize(name, loc)
            @name = name
            @loc = loc
            @instance_proc = Proc.new() do
                    puts self.name, self.loc
                end
        end
    
        def ex_proc(&proc)
            yield
        end
    
    end
    k = Person.new("karson", "nyc")
    # => #<Person:0x00007ff6aa94c228 @name="karson", @loc="nyc", @instance_proc=#<Proc:0x00007ff6aa94c1d8>>
    k.ex_proc(&k.instance_proc)
    # karson
    # nyc
    

    エンディング


    あなたが今知っているものを使うProcs and blocks , 方法を再作成しましょう.map カスタムクラスでMArray .
    class MArray < Array
      def initialize(*args)
        super(args)
      end
    
      def map(&block)
        newMArr = MArray.new()
        for element in self
          newMArr << yield(element)
        end
        newMArr
      end
    end
    
    m = MArray.new(1, 2, 3, 4, 5)
    # =>[1, 2, 3, 4, 5]
    m.map { |item| item * 2 }
    # => [2, 4, 6, 8, 10]
    

    結論


    procsは、私たちがコードドライを維持し、OOP概念と機能プログラミングの再生機能を実装できるようにする強力なRubyコンセプトです.オフィシャルサイトRuby-Doc procsに関するドキュメントとコメントブローであなたの考えを知らせてください!