※この記事は「RUNTEQ Advent Calendar 2023」の4日目を担当した記事です。
はじめに
Rubyにおける無名関数について学習したところ、他言語と比較してとても使い勝手が良く、様々な場所で利用できると感じたので、私なりに理解したことを記事にしました。
誤りがございましたがご指摘をお願いいたします。
今回説明すること
- 無名関数についての基本と無名関数のメリット
- Rubyで使用されている無名関数4種類
- 無名関数のバリデーションでの活用法
前提として
無名関数に関しては、言語によって様々な呼び方がなされています。
無名関数、ラムダ式、匿名関数、lambdaといった呼び方がありますが、この記事の中では、無名関数は広義の意味で使用しており、lambdaやProcに関しては個々の機能を含めた意味合いで使用しております。
無名関数とは?
無名関数は、その名の通り「名前のない関数」のことです。
普段私たちが関数やメソッドを使用する際には、その名前を記載して関数やメソッドの処理を呼び出します。
Rubyの簡単なメソッドでは、"putsメソッド"や"sumメソッド"、"eachメソッド"などがあります。
また自作でメソッドを定義する際にも「def…end」を使用して必ず名前をつけていたと思います。
#自作のメソッドの定義
def add(a,b)
a+b
end
#putsメソッド
puts add(4,7) #=> 11
#sumメソッド
a=[1,2,3]
puts a.sum #=> 6
#eachメソッド
numbers = [1,2,3,4]
add = 0
numbers.each do |n|
add += n
end
puts add #=> 10
あくまでも、こういった関数やメソッドは、コードの再利用という観点から「何回も使うためのもの」「だから名前をつけているよ」という立ち位置にあります。
そこで私が考えていたのが「使い回さないから名前をつけないのかな?」と思っていたのですが違いました。最大のメリットは他の部分にありました。
無名関数のメリット
無名関数が「名前が付けられていない関数」であることは理解しているのですが、あえて名前をつけないメリットは何なのでしょうか?
以下が無名関数に関して調べるとよく出てくるメリットです↓
- 一度しか使わない関数で名前を付けなくて済む(名前の衝突が起きない)
- コードが短くなることで可読性が高まる
- 関数の引数などに直接渡せる
私が感じた無名関数の最大メリットは3つ目の「関数の引数などに渡せる」という点だと思います。(1つ目と2つ目は個人の学習レベルでは恩恵を感じないかもしれません…)
Rubyにはブロックという概念があり、それが無名関数と相性が良くコードが読みやすいため、様々なところで活用できる理由なんだと思います。
Rubyにおけるブロックという概念
まず、Rubyには「ブロック」という概念があります。
ブロックはメソッドの引数として渡すことができる処理の塊です。
先ほどのeachメソッドの例で説明すると「do…end」までがブロックにあたります。
numbers.each do |n|
add += n
end
上記の例では、eachメソッドで要素を順番に取り出し、ブロックに取り出した要素をどう扱うかが記載されています。
他言語では、forループやforEachメソッドをよく使用すると思うのですが、Rubyではあまり使用しません。
それはブロックという概念があり、Each文を使用するよりもブロック内に処理を記載する方が簡単で分かりやすいからです。
例えば冒頭で紹介したRubyのeachメソッドをJavaScripで記載すると以下のようになります。
> var numbers = [1,2,3,4]
> var add = 0
> for (var i = 0; i<numbers.length; i++){
> add += numbers[i]
> }
> console.log(add)
> //=> 10
Rubyのコードと大きく異なる点はfor分の後の()の中の処理で、iというカウント変数を使用していることだと思います。
iに0を代入して初期値を設定(i=0)し、ループが1回終わるごとにiの値を1つ増やしていきます(i++)。その後、i変数の値と配列の長さよりも小さい場合は処理を繰り返す…という処理が書かれえています。
また、上記のコードをもう少し短くわかりやすくしようとした場合、JavaScriptではコールバック関数という手法があります。
これがいわゆる、JavaScriptにおける無名関数のやり方です。
> const numbers = [1,2,3,4]
> let add = 0
> numbers.forEach(function(n){ //コールバック関数
> add += n
> })
> console.log(add)
> //=> 10
コールバック関数(無名関数)はfunction(n)の部分にあたり、配列numbersの値をfor Eachメソッドの引数として渡しています。
Rubyではコールバック関数ではなく、ブロックを引数として渡せるのが大きな特徴です。
また、for文は使用せずにdoを使用して配列自体に繰り返しの指示を行います。ブロックの中に処理を記載することで、可読性が高まりわかりやすくなります。
このように便利なRubyのブロックという考え方ですが、コールバック関数を比較した時の最大の違いは「渡せる引数の数」にあります。
Rubyのブロックは、JavaScriptのコールバック関数とは異なり、メソッドに渡せる数は”1つ”だけというルールがあります。
ブロックのデメリットを解消するのが無名関数のProc
では、Rubyにおけるブロックは限定的にしか使えないのかというとそんなことはありません。
ブロックのデメリットを解消するのが無名関数であり、Proc(プロック)です。
そして、Procとは、ブロックをオブジェクト化するためのクラスです。
メソッド側から受け取れる引数は、ブロックであれば1つしか受け取れませんが、オブジェクトであれば制限はありません。
また、オブジェクト化することで、変数に代入して別のメソッドに渡したり、Procオブジェクトに対してメソッドを呼び出したりすることができます。
Rubyで使用されている無名関数4種類
まず、大きく分類すると「ラムダ(lambda)」と呼ばれるものと「Proc(プロック)」と呼ばれるものがあります。
この分類は、無名関数を記載する方法によって挙動が少し異なることから、このような分類がされています。
さらに、上記の2種類の無名関数を作成する際のコードの書き方が4種類にわかれます。
具体的に、4種類の書き方は以下の通りです。
「ラムダ」の書き方
ラムダ(lambda)の書き方はその名前の通り、lambdaメソッドを使用する方法と、ー>(アロー演算子)を使用する方法があります。
#lambdaメソッドを使用した書き方
lambda{|a,b|a+b}
#->を使用した書き方
->(a,b){a+b}
「Proc(プロック)」の書き方
Proc(プロック)の書き方はprocメソッドを使用する方法と、Proc.newを使う方法があります。
#procメソッドを使用した書き方
proc{|a,b|a+b}
#Proc.newを使用する方法
Proc.new{|a,b|a+b}
最初に学習した時には「何で無名関数が4つもあるの…?」と困惑したのですが、現在ではProc.newが主流になっているようなので、とりあえずはそれを覚えておけば大丈夫かと。
また、これらの無名関数ですが、ラムダとProcでは動作が多少異なります。
ざっくりとした違いは以下の通りです。
ラムダ(lambda) | Proc(プロック) | |
---|---|---|
返り値 | 式の値を返す | 返り値を設定しないとnilを返す |
引数の取り扱い | 引数の数が合わないとエラーになる | 省略可能 |
returnの挙動 | returnでラムダ内のみ返す | 呼び出し元まで返す |
ここではProc.new以外の詳細な説明については割愛しますので、詳しくは以下の記事をご参照ください。
実際にProcを使用してみる
では、実際にProc.newを使用してRubyのブロックをブロックとしてではなく、普通の引数として渡してみましょう。
先ほど記載したproc.newのコードと構文を以下に記載します。
#Proc.newを使用する方法
Proc.new{ |a,b|a+b }
#Procの構文
Proc.new{ |引数1,引数2| 引数を使用した処理 }
{}の中に記載した処理をProcオブジェクトの中に入れ込んでしまう様なイメージでしょうか。
これをすることでメソッドの引数にProcオブジェクトの中の計算処理を複数個渡すことができます。
> #引数を3つ持つgreetメソッド
> def greet ( proc_1, proc_2, proc_3)
> puts proc_1.call("おはよう")
> puts proc_2.call("こんにちは")
> puts proc_3.call("こんばんは")
> end
> #Procオブジェクトの生成
> shuffle_proc = Proc.new{ |text|text.chars.shuffle.join } #textの文字をシャッフルする処理
> repeat_proc = Proc.new{ |text|text * 2 } # textを2倍にする処理
> question_proc = Proc.new{ |text| "#{ text }?" } #textに?をつける処理
> #3種類のオブジェクトをgreetメソッドに渡す
> greet(shuffle_proc, repeat_proc, question_proc)
> #=> はおうよ
> # こんにちはこんにちは
> # こんばんは?
Procをオブジェクトとして引数に渡すコードを色々と調べている中で、私の中で一番わかりやすかったのが上記のコードです。
上記のコードは以下の書籍の中に記載されているコードですので、是非手に取ってみて下さい。めちゃくちゃ分かりやすい良本です。
無名関数バリデーションでの活用法
Procに関しては、引数として個数制限なしで渡せるのがとても便利だなということは理解できたので無名関数をバリデーション(ある値に対して指定した条件に当てはまるかどうかというのを検証するもの)で活用できる例をご紹介します。
ActiveRecodeのScope(スコープ)
Active Recordとはデータを絞り込んだり条件に合うかどうかを判断して制限をかけたりすることができるRailsの機能です。
そのActive Recordの中でscopeというメソッドを使用して、繰り返し利用される絞り込み条件をスッキリ読みやすくすることができます。
以下のActive Recordのscopeでは、引数を2つ渡していて、そのうちの一つに無名関数の->{}が使用されています。
> class Task < ApplicationRecord
> belong_to :user
> scope :recent, ->{order(cleated_at: :desc)}
> ...
> end
上記の第一引数は、scopeの名前の部分の ”recent” で、第二引数の部分で->{}が使用されているので、ラムダ式が使われています。
上記のコードは以下の書籍から引用させていただきました。↓
ifを使用してvalidatesで条件分岐する
バリデーションには、validatesメソッドを使用して判断することもできます。
以下のコードはvalidatesメソッドとifを組み合わせて条件を設定しています。
> class Computer < ActiveRecord::Base
> validates :mouse, presence: true, if: :retail_desktop?, unless: Proc.new { |c| c.trackpad.present? }
> end
> private
> def retail_desktop?
> market.retail? && desktop?
> end
ここではProc.newが使用されており、unlessにPeocオブジェクトの処理を渡しています。
コードが少し長くはなってしまいますが、Procが処理の塊であるオブジェクトだと理解できていれば、unlessに{}の処理を渡しているのが分かります。
参照サイト:5.条件付きバリデーション
おわりに
Java言語ではじめて無名関数(ラムダ)について学習した時には、いまいち活用できるイメージが湧かなかったのですが、今回Rubyを学習しJavaScriptと比較しながら学習を進めることで、バリデーションでも使用できるとても便利な機能なんだなと理解できました。
回り道になるかもしれないけど、色々なことにアンテナを張って興味を持って学習を進めることが大切なのだと思いました。
せっかく学習したので、是非開発の時に活用したいです!