Lambda

Procオブジェクトを生成するメソッドlambdaについて説明します。

lambdaの使い方

lambdaはKernelモジュールのメソッドで、使い方は前の記事のprocと同じです。 lamdaの後にブロックを書き、そのブロックがProcオブジェクトになります。

a = lambda{|x| print x, "\n"}
a.call("Hello")
a["Hi"]
  • ブロックをProcオブジェクトにして変数aに代入
  • callメソッドでブロックを実行
  • []メソッドでもブロックを実行できる

lambdaの代わりにprocを使っても結果は同じです。

lambdaとprocの違い

Lambdaとprocで作られるProcオブジェクトの振る舞いには次の違いがあります。

  • lambdaで作られたProcオブジェクトのパラメータの数と、呼び出し側の引数の数が違うとエラーになる。 procで作られたProcオブジェクトではエラーにならず、パラメータが余ればnilが代入され、引数が余れば捨てられる。
  • lambdaで作られたProcオブジェクトの中でreturnあるいはbreakが呼ばれたときエラーにならず、Procオブジェクトを抜ける。
a = proc{|x,y| p x; p y}
a.call(1)
a.call(1,2,3)

これを実行すると

1
nil
1
2

となります。 procの代わりにlambdaを使うと引数の違いによるエラーになります。

return、break、nextの違いを表にまとめると次のようになります。

  return break next
proc proc定義の外で定義されたメソッドを抜ける エラー Procオブジェクトを抜ける
lambda Procオブジェクトを抜ける Procオブジェクトを抜ける Procオブジェクトを抜ける

また、メソッド定義の中でprocメソッドで作られるProcオブジェクトを、メソッドの外で呼び出したとき、そのオブジェクトの中にreturnまたはbreakがあるとエラーになります。 lambdaではエラーになりません。

def b
  @a = proc{return}
  @a.call
  p 10
end

b
@a.call

これを実行すると

example18.rb:2:in `block in b': unexpected return (LocalJumpError)
        from example18.rb:8:in `<main>'
  • 7行目でメソッドbを呼び出している。 メソッドbはreturn文のあるブロックをProcオブジェクトにして@aに代入し、続けて@a(Procオブジェクト)を呼び出している。 Procオブジェクトはreturnを実行し、メソッドbを抜ける。 そのため次の行のp 10は実行されない
  • 8行目でProcオブジェクトが呼ばれるが、メソッド内で定義した「returnを含むProcオブジェクト」をメソッド外で呼んだのでエラー(LocalJumpError)になる

細かい説明になりましたが、ざっくりというと「lambdaで作ったProcオブジェクトは、procで作ったProcオブジェクトより、振る舞いがメソッドに近い」といえます(メソッドも引数の数のチェックがあり、returnで呼び出し側に戻ります)。

このことから、「名前のないメソッド」のようにProcオブジェクトを扱いたいときはlambdaを使うのが良いといえます。 ネット上のrubyのプログラムを見ると、procよりlambdaが多く用いられているようです。 それはこのような理由によるものと推察されます。

逆に&を使ってブロックとして使うProcオブジェクトはprocで生成するのが良いと思います。 なぜならprocの後に続くブロックをそのまま&引数の代わりにあてはめてデバッグ(テスト)することができるからです。 lambdaの場合、returnやbreakが記述されていると、直接のブロックにしたとき動作がおかしくなりますし、引数のチェックも本来のブロックとは異なります。 もちろん、注意して使えばlambdaで生成したProcオブジェクトに&をつけてブロックにしても問題はありません。

Procクラスのインスタンス・メソッド

<<、>>

a << bは糖衣構文で、a.<<(b)に等しくなります。 すなわち<<はProcクラスのインスタンスメソッドです。 このメソッドは「bに引き続きaを実行する新たなProcオブジェクト」を返します。 ちょっと分かりにくいと思うので、図で説明します。 a、bともに、整数をパラメータで受け取り、整数を返すProcオブジェクトだとします。 これを図で次のように表しましょう。

整数 --->>> (a) --->>> 整数
整数 --->>> (b) --->>> 整数

これをb、aの順に連続的に行います。

整数 --->>> (b) --->>> 整数 --->>> (a) --->>> 整数

左端の整数に対して右端の整数を返すProcオブジェクトがa << bです。 例えばaが「2を加える」、bが「2乗する」としましょう。

整数(x) --->>> (b) --->>> 整数(x^2) --->>> (a) --->>> 整数(x^2+2)

となります。 ここでハットマーク(^)は累乗を表します。

a = lambda{|x| x+2}
b = lambda{|x| x*x}
c = a << b
p c.call(5)
c = b << a
p c.call(5)

実行すると次のようになります。

27
49

a << bでは「5を2乗して、2を加える」ので27になり、b << aでは「2を加えてから2乗する」ので49になります。

パラメータと返し値が両方整数の例を示しましたが、どのようなオブジェクトでも良いし、パラメータと返し値が複数(その場合は配列を返すことになる)でも構いません。 ただ、a << bにおいては、bの返し値がaの引数になるので、そのオブジェクトのタイプと数が一致してないと上手く動作しません。

次の例は、文字列を単語に分解して配列にし、その配列の要素数を求めるプログラムです。

文字列 --->>> b --->>> 配列 --->>> a --->>> 整数

入出力が文字列、配列、整数の異なる3種類のオブジェクトになっています。

a = lambda{|x| x.size}
b = lambda{|x| x.scan(/\w+/)}
e = "I declare before you all that my whole life, whether it be long or short, "\
"shall be devoted to your service and to the service of our great imperial family to which we all belong."
p (a << b).call(e)

実行すると35(語)となります。 なお、これはエリザベス女王の1947年のスピーチです。

a >> bは左から右への演算記号であり、<<と逆に「aを実行してからbを実行」するProcオブジェクトを返します。

ところで、先程の例では変数名がaとbで、なんとも気が利かない名前です。 これをcountとget_wordsにすれば

p (count << get_words).call(e)

で読みやすくなるのではないでしょうか。

curry

curryはラムダ計算という数学の理論に出てくる「curry化」という変換です。 例をあげて説明します。 数式でx+yという関数は変数が2つある関数です。

\[f(x,y)=x+y\]

xとyに2と3を代入すれば、関数値は5になります。 このとき、この操作を2つに分けてみましょう。 まず2を代入した関数を考え、次に3を代入して値を求めます。

\[f(2,y)=2+y\]

この関数はyだけの関数ですので改めてgと書けば

\[g(y)=2+y\]

y=3を代入すると最終的な値5が求められます。

\[g(3)=2+3=5\]

この操作は次のように2段階になります。

2に対して「\(g(y)=2+y\)という関数」が対応 => 一般には\(x\)に対して\(g(y)=f(x,y)\)が対応。

この対応を新たに関数\(h\)とすると、\(h(x)=f(x,y)=g(y)\)。 \(h(2)=f(2,y)=g(y)\)に対して、\(y=3\)を代入すると

\[ g(3) = f(2,3) = 2+3 = 5\]

まとめると

2 --->>> h --->>> g(y)=2+y
3 --->>> g --->>> 5

すなわち

\[(h(2))(3)=g(3)=5\]

関数hの値が「数ではなくて関数」であるところがポイントです。 元の2変数関数\(f(x,y)\)は2つの1変数関数\(h(x)\)と\(g(y)\)に分解され、

\[f(x,y) = (h(x))(y)\]

となりました。 これをcurry化といいます。

curryメソッドはProcオブジェクトのcurry化をします。

a = lambda{|x,y| x+y}
b = a.curry #=> lambda{|x| lambda{|y| x+y}}
c = b.call(2) #=> lambda{|y| 2+y}
p c.call(3) #=> 5

コメントでそれぞれの計算結果を書いておきました。 2行目がややこしいのですが「xに対してProcオブジェクトlambda{|y| x+y}が対応する」というProcオブジェクトがcurry化によって返されます。 これが「\(x\)に対して\(g(y)=x+y\)が対応する」ということに対応しているのですが、ややこしいですね。

ラムダ計算をするときは、アロー演算子を用いるほうがラムダ計算らしいです。 また、callメソッドの代わりに[]を使うと更にラムダ計算らしくなります。

# アロー演算子での表現
a = ->(x,y){x+y}
b = a.curry #=> ->(x){->(y){x+y}}
c = b[2] #=> ->(y){2+y}
p c[3] #=> 5

配列のmapメソッドで各要素を2乗する計算をcurryを使って表現してみます。

# 配列のmapで要素を2乗するのをProcオブジェクトで表しcurry化
a = ->(x,y){y.map(&x)} # yが配列でxがProcオブジェクト
b = ->(x){x*x}
p a.curry[b][[1,2,3,4]]

square = a.curry[b] # cは配列要素を2乗するProcオブジェクト
p square[[5,6,7,8,9,10]]

実行すると

[1, 4, 9, 16]
[25, 36, 49, 64, 81, 100]

となります。 最後にsquareという「配列要素を2乗する」Procオブジェクトを手に入れることができました。 これはcurry化のひとつのメリットだと思います。 つまり「整数を2乗する」手続きから「配列の各要素を2乗する」といういわば「格上げ」された手続きを得ることができました。

同様に「整数に2を加える」を「配列の各要素に2を加える」に格上げしてみましょう。 そして、さらに<<演算子で両者を繋げてみます。

add2 = a.curry[->(x){x+2}]
p add2[[5,6,7,8,9,10]] #=> [7, 8, 9, 10, 11, 12]
p (add2 << square)[[5,6,7,8,9,10]] #=> [27, 38, 51, 66, 83, 102]
p (square << add2)[[5,6,7,8,9,10]] #=> [49, 64, 81, 100, 121, 144]

このように「a.curry」によって任意の整数に対する手続きは配列に対する手続きに格上げできます。 「配列の要素に特定の操作を行う」ことがプログラムのあちこちで必要なとき、その手続きオブジェクトをcurryメソッドを用いて作っておけば無駄な作業が省け、可読性もあがります。

まとめ

最後の方が難しくなってしまいましたが、いかがだったでしょうか。 Procオブジェクトは使い方によってはプログラムを分かりやすく効率的にします。 メソッドに慣れている人が多いと思いますが、レシーバの関係無い手続きはトップレベルのメソッドでもProcオブジェクトでも可能です。 Procオブジェクトは無名のオブジェクトなので、扱いが柔軟にできるメリットがありますし、curry化も上手く使うと便利なことがあります。 ぜひProcオブジェクトに慣れて効果的に使ってください。

2023

単項マイナスと構文解析

1 minute read

単項マイナスとは 単項マイナスと括弧 括弧なし単項マイナスを許容する場合のBNF calcの場合

Raccライブラリと構文解析

3 minute read

パーサ・ジェネレータとは 少し複雑な文法 四則(加減乗除)計算のBNF Racc で実装 クラス定義、BNFの記述部分 ヘッダー、インナー、フッター コンパイルと実行 演算子の優先順位と結合における左右の優先順位 まとめ

StrScanライブラリと字句解析

less than 1 minute read

StrScanライブラリのドキュメント 字句解析とは StrScanライブラリ StrScanライブラリを使った字句解析 実例

Gem

1 minute read

lbtというgemを作って公開してみた lbtはどんなgemか ファイルの配置 lbt.gemspec Rakefile gemのビルド RubyGems.orgへのアップロード 補足・・rake/gempackagetaskサブライブラリについて

Encoding

1 minute read

文字列のエンコーディングに頭を悩ませることはほとんどなくなりました。 なぜなら、どのアプリ、システムもUTF-8を使うようになったからです。 Rubyでもエンコーディングの問題が起こることはまず無いでしょう。 ですが、今回はエンコーディングの考え方を整理してみたいと思います。

Thread

less than 1 minute read

Fiberを書いたときから、次はスレッドを書こうと思っていましたが、時間がかかってしまいました。 その理由は、期待したとおりのスレッドの効果がなかったためです。 今回はそのことを書きますが、これはRubyのスレッドの抱えている問題なのか、自分のやり方が悪いのかははっきりしていません。

Fiber

1 minute read

Fiberは「ノンプリエンプティブな軽量スレッド」とRubyのマニュアルに記載されています。

RDoc

less than 1 minute read

今回はRubyプログラムから自動的にドキュメントを作成するRDocについて書きたいと思います。 私はこのことについて、エキスパートではありません。 この記事も、初心者の体験談だと考えてください。

Back to Top ↑

2022

Ruby/GTK4

5 minute read

Ruby/Gtkの記事を先日書いたときに、「これはかなり使える」という手応えを感じたので、WordBook(Railsで作った単語帳プログラム)のGTK 4版を作りました。 プログラムは「徒然なるままにRuby」のGitHubレポジトリに置いてあります。 レポジトリをダウンロードし、ディレクトリ_example/...

Shoes – Rubyとグラフィック

5 minute read

Rubyはグラフィックについて弱い印象があります。 しかし、グラフィックはデバイスに関することなので、言語そのものには直接の関係はないはずで、あるとすればライブラリです。 今後グラフィック関係のgemが開発されることに期待しましょう。

Rails7 テスト

5 minute read

前回作ったWordbook(リソースフル)のテストを書いてみます。 RailsのテストはminitestをRails用に拡張したものです。

Rails7 モデルとデータベース

2 minute read

今回はRailsにおけるデータの作成と保存、そして変更について説明します。 そのベースになるモデルとデータベースの話から始め、appendとchangeの動作について詳しく説明します。

Rails7とBootstrap

2 minute read

一般に、HTMLは文書の構造を表し、CSSはその体裁(見栄え)を表します。 Railsは最終的にCSSを含むHTML文書を出力するので、この2つについての理解が必須です。 この記事ではとくにCSSの人気ライブラリであるBootstrapを紹介します。 BootstrapはJavascriptも含んでいます。

Rails7のインストール

2 minute read

Rubyの最も人気のあるアプリケーションであるRuby on Railsを取り上げようと思い、書き始めました。 予想してはいましたが、相当な分量になってしまいました。 そのため、何回かに分けて記事にすることにします。 また、対象となる読者のレベルをどうしようかと考えましたが、「徒然Ruby」が基礎的な内容から始ま...

GemとBundler

1 minute read

Rubyのライブラリ管理システムのRubygemsとコマンドgemおよびbundlerについて説明します。

minitest(3)モックの詳細

2 minute read

minitestについて連続して2回書いてきました。 「minitestはドキュメントが少ない」という人がいますが、私も同感です。 例えば、モックとスタブの説明も少ないです。 そこで、今回はmock.rbのソースコードを参考に、モックの私的ドキュメントを書いてみました。 あくまで私個人の考えであり、minites...

minitest(1)テストとは

2 minute read

アプリ作成の記事でminitestを使いました。 今回はminitestについて、また一般にテストについて、私の考えを書こうと思います。

public、private、protected

2 minute read

今回はメソッドの呼び出し制限ついて説明します。 呼び出し制限にはpublic、private、protectedの3つがあります。

アプリ制作、インストール、テスト

1 minute read

2023/10/29 追記:この記事は新しく書き直しました。 古い記事で使っていたGitHubのCalcが大幅にアップデートされたためです。 そこで、この記事に合うようなプログラムsimple_calcを新たに作りました。 このプログラムは本レポジトリの_example/simple_calcにあります。

case文

2 minute read

if〜elsif〜・・・〜else〜endは皆さん良く使うでしょうか? これは場合分けで良く使われる方法です。 これと同様の制御構造にcase文があります。 Cのswitch文に似ていますが、より強力な機能を持っています。 if-else-endよりも高い能力があるといえます。

Lambda

2 minute read

Procオブジェクトを生成するメソッドlambdaについて説明します。

Proc オブジェクト

2 minute read

今回はブロックを一般化したオブジェクトProcを説明します。

モジュール

1 minute read

モジュールには名前空間とミックスイン(Mix-in)の2つの機能があります。 ここではミックスインについて説明します。

Kernelモジュール

less than 1 minute read

Kernelモジュールのメソッドはどこでも使うことができます。 そのメソッドの中には便利で有用なものが多いです。

便利なメソッド

1 minute read

ここでは私が便利だと思ったメソッドを紹介します。

文字列と正規表現

3 minute read

文字列は最も使うオブジェクトのひとつです。 特にウェブ・アプリケーションでは、コンテンツだけでなくHTMLのタグやCSSを含めすべてが文字列です。 Rubyは文字列オブジェクトのメソッドが充実しており、またパターンマッチのための正規表現も充実しています。

配列

2 minute read

配列は、どのプログラミング言語にもあると思います。 複数の要素を一括して扱うことができるのが配列です。 Rubyの配列はメソッドが充実しているので、プログラムを効率的、機能的に書くのに役立ちます。

トップレベルのメソッド

1 minute read

今回はメソッド定義です。 メソッド定義はRubyの核心ですが、今回はトップレベルに限って説明します。 この限定によって、内容はかなり易しくなっています。

ブロックとイテレータ

less than 1 minute read

ブロックはRubyの特長です。 ブロックのおかげで記述が非常にすっきりと分かりやすくなります。 今回はブロックをイテレータの本体として使う方法を説明します。

整数

less than 1 minute read

ここではRubyの最も基本的なオブジェクトである整数について説明します。

Hello world

less than 1 minute read

「徒然なるままに」をネットで調べてみると、「することもなく、手持無沙汰なのにまかせてという意味」とありました。 まさに、自分の現状を言い当てた言葉。 しかも、ブログに書くネタもなかなか思いつかない日々。

Back to Top ↑