特異メソッド、名前空間、モジュール関数

今回は特異メソッド、特異クラス定義、名前空間、モジュール関数について説明します。

特異メソッド

特異メソッドは英語ではsingleton method(一人っ子のメソッド)といいます。 英語の方が意味をとりやすいと思います。 特異メソッドは個々のオブジェクト(インスタンス)だけがレシーバになるメソッドのことを言います。

次の例はクラスAのインスタンスを2つ作り、そのうちの一方に特異メソッドを作ったときの実行結果を示すものです。 オブジェクトaに特異メソッドを定義するにはdef a.introのように「オブジェクト、ドット、メソッド名」のようにします。

class A
end

a = A.new
b = A.new

def a.intro
  print "私はaです\n"
end

a.intro #=> 私はaです
b.intro #=> エラー (NoMethodError)

実行します。

私はaです
example21.rb:12:in `<main>': undefined method `intro' for #<A:0x00007f6f3d737bd0> (NoMethodError)

b.intro
 ^^^^^^

aにはintroメソッドを定義したので「a.intro」の結果「私はaです」が出力されましたが、bには定義されていないので、「b.intro」はエラーになりました。

通常のメソッドは同じクラスから生成したインスタンスすべてで可能ですが、特異メソッドはそれが定義された個別のインスタンスでのみ可能です。 特異メソッドは様々なケースで使えると思いますが、例えば次のようなケースが考えられます。

  • クラスUserはあるシステムを使用するユーザを定義する
  • 個々のユーザはUserクラスのインスタンスとして生成する
  • ユーザの中には一人だけ管理者ユーザがいる。つまりUserクラスのインスタンスの中にひとつだけ管理者ユーザ・インスタンスがある
  • 管理者ユーザ・インスタンスには特別のメソッドを付与する(特異メソッドになる)

クラスメソッド

Rubyでは個々のクラス(例えばIntegerやString)はClassクラスのインスタンスです。 話がややこしくなりますが、クラスはクラスであると同時にインスタンスでもあるわけです。

クラス名は大文字で始まりますが、この文字は定数を表しています。 クラスを定義すると、そのクラスオブジェクト(Classクラスのインスタンス)が生成され、定数に代入されるのです。 つまり大文字で始まる名前は、クラスを表す名前であると同時に定数の名前でもあります。

クラスはインスタンスですから、変数に代入することもできます。

class A
end

c = A
p c  #=> A
p c.class #=> Class

変数cにはクラスA(のオブジェクト)が代入され、「p c」でクラス名Aが、「p c.class」でクラスAのクラスであるClassが表示されます。

このようにクラスを変数に代入することはできますが、通常はそういうことはしません。 それはプログラムを分かりにくくなることに繋がります。 定数を参照すれば、それがクラスだと直感的に分かります。 定数だけをクラスに用いるのが良い習慣です。

さて、クラスはオブジェクトですから、特異メソッドを定義することができます。 クラスの特異メソッドをクラスメソッドといいます。

class A
end

def A.intro
  print "私はクラスAです\n"
end

A.intro #=> 私はクラスAです

selfを使った定義

クラスメソッドの定義はクラスの内側でもできます。

class A
  def A.intro
    print "私はクラスAです\n"
  end
end

A.intro #=> 私はクラスAです

この方法では疑似変数selfを使うことができます。 「class A〜end」では、selfは「ClassクラスのインスタンスであるクラスA」を指します。 したがって、この区間では定数Aとselfは同じオブジェクトを指します。 ですので、

class A
  def self.intro
    print "私はクラスAです\n"
  end
end

A.intro #=> 私はクラスAです

のように、selfを使ってクラスメソッドを定義することができるわけです。

クラスメソッドの継承

クラスメソッドはそのサブクラスにも継承されます。 上記のプログラムに続き、以下を追加するとBにもAのクラスメソッドintroが継承されます。

class B < A
end
B.intro #=>私はクラスAです

モジュールの特異メソッド

特異メソッドは個々のオブジェクトにメソッドを定義するものでした。 クラスメソッドはクラス(クラスはオブジェクトでもある)の特異メソッドです。 ではモジュールについてはどうでしょうか? モジュールはクラス同様にオブジェクトなのでしょうか? 答えはイエスです。 モジュールはModuleクラスのインスタンスです。 したがって、モジュールの特異メソッドを作ることができます。

module C
end

def C.intro
  print "私はモジュールCです\n"
end

C.intro #=> 私はモジュールCです

モジュール定義の中ではselfを使った特異メソッド定義が可能です(クラスの場合と同じ)。

モジュールの場合はクラスと違い、サブモジュールを作ることができませんから、特異メソッドの継承はありません。 また、モジュールをincludeしても特異メソッドの継承はできません。 インクルードは通常のメソッドと定数を引き継ぐだけです。

名前空間

トップレベルに同じ名前のメソッドを2つ作ることはできません。 それをすると、2番めの定義が1番めの定義の「再定義」になり、以後2番めの定義だけが有効になります。 しかし、異なる動作をする2つのメソッドに同じ名前をつけたいときもあります。 例えば、あるプログラムはMarkdownファイルをHTMLにもPDFにも変換できるとします。 「変換する」メソッドは、convertという名前が良いでしょう。 HTMLに変換するのもconvert、PDFに変換するのもconvertにしたいのです。

このとき、convertにプリフィックスをつけて区別することが可能です。

def html_convert
... ... ...
end
def pdf_convert
... ... ...
end

このようなプリフィックス名により、区別することを「名前空間」を使うといいます。 HTMLに関するメソッドにはすべてhtml_をつけ、PDFに関するメソッドについてはpdf_をつけるわけです。

同様のことはmoduleの特異メソッドでも実現できます。

module HTML
end
module PDF
end

def HTML.convert
... ... ...
end
def PDF.convert
... ... ...
end

このようにモジュールの特異メソッドは名前空間を付与することと同一の効果があります。 同じことをクラスでもできますが、クラスには継承があるため、サブクラスを作ると別の名前空間で同じ内容のものが作られてしまいます。 したがって、名前空間としてはモジュールの利用が適しています。

しかし、クラスの特異メソッドも名前空間に似た効果があります。 例えば、IOクラスにはreadという特異メソッドがあります。

s = IO.read(ファイル名)

このメソッドはファイルを読み込み、その文字列を変数sに代入します。

単にreadという名前のメソッドでは名前の衝突が不安になりますが、IO.がついているので、これはIOクラスの特異メソッドだと分かります。 これは名前空間に似た効果です。 また、IOのサブクラスにFileがあります。 特異メソッドはサブクラスに引き継がれるので

s = File.read(ファイル名)

でも同じreadメソッドが呼ばれます。 これはファイルクラスの特異メソッドと見えますが、実はIOの特異メソッドの継承です。

特異メソッドは一般のオブジェクト(通常のクラスから生成したインスタンス)に定義するよりも、モジュールやクラスに定義する場合が多いように思います。 実際Rubyのドキュメントを見ると、クラスメソッドは数多く定義されています。 最もよく使うのはインスタンスを生成するnewメソッドでしょう。 これはほとんどのクラスの祖先であるObjectクラスの特異メソッドです。 各クラスはそれを受け継いでいるので、newが使えるのですね。

特異クラス定義

特異クラス定義とは、次のようにclass <<(式)〜endという構文のことをいいます。 ここで定義されるメソッドは(式)の指すオブジェクトの特異メソッドになります。

class A
end
a = A.new
b = A.new
class << a
  def intro
    print "私はaです\n"
  end
end
a.intro #=> 私はaです
b.intro #=> エラー(NoMethodError)

classに<< aをつけると、aの指すオブジェクトについて定義をすることになります。 そこで定義されるメソッドはすべてaの指すオブジェクトの特異メソッドです。 この記法の良いところは「多くの特異メソッドを少ないタイプ量で記述できる」ことです。

また、selfをこの記法に使い、クラスメソッドを定義することができます。

class A
  class << self
    def intro
      print "私はクラスAです\n"
    end
  end
end
A.intro #=> 私はクラスAです

これにより、class A〜endの中で、普通のメソッドもクラスメソッドも定義することができ、非常に効率的になります。 この記法が最も良いかもしれません。 モジュールにはこのタイプの記法がないので、個別に作ることになります。 ですが、次に述べるモジュール関数を作る方法を適用すると簡単に特異メソッドと通常のメソッドの2つができます。

モジュール関数

ビルトイン・オブジェクトの中にMathモジュールがあります。 数学関数を提供するモジュールです。 例えば正弦関数はMath.sin(角度)として呼ぶことができます。 また、モジュールをインクルードしておけば、sin(角度)とモジュール名を省略することができます。

p Math.sin(Math::PI/3) #=> 0.8660254037844386
include Math
p sin(PI/3) #=> 0.8660254037844386
  • Math::PIはモジュールMathの中で定義されている定数。 定数の場合はドットではなく::を使ってモジュール下の定数であることを示す。 クラス定義の中で定義された定数も「クラス名::定数」となる。 これはMathモジュール外部からの参照で用いられる
  • includeによって、モジュール内の定数やメソッドが取り込まれる。 PIにMath::を付けなくて良い。 また、sinメソッドはMathモジュールの通常のメソッドである

ここで注意が必要なのは、Math.sinとsinメソッドの違いです

  • Math.sin=>Mathモジュールの特異メソッド
  • sin=>Mathモジュールの通常のメソッドがincludeで取り込まれたもの

この2つがセットになっているので、インクルードを使う/使わないの選択が可能になります。 これは便利なことなので、モジュールの提供するメソッドに名前空間をつける目的であれば、両方のメソッドの定義をするべきです。 (Math.がつかないsinはインクルードが必要なので、その時点で名前の衝突を避けることができる)。 このようなメソッドを「モジュール関数」といいます。

以前紹介したモジュールのミックス・インは通常のメソッドを複数のクラスに提供する仕組みでした。 ミックス・インのメソッドは、特異メソッドを含まないので、モジュール関数ではありません。

モジュール関数は通常のメソッドと特異メソッドの2つを定義しなければならないのですが、それは非効率です。 それを解決するメソッドがmodule_functionメソッドです。

  • 通常のメソッドを定義する
  • module_function メソッド名」によって、そのメソッドがモジュール関数になる。 メソッド名はシンボルで表す
module X
  def intro
    print "私はモジュールXのモジュール関数です\n"
  end
  module_function :intro
end
X.intro #=> 私はモジュールXのモジュール関数です
include X
intro #=> 私はモジュールXのモジュール関数です

有用な関数をプログラム全体で使えるように提供したいときには、このようなモジュール関数として提供するのが良い方法です。 特にライブラリの作成においては、モジュール関数にするのがベストな選択です。 (ライブラリでなければ、トップレベルのメソッドでも十分です)。

定数の名前空間

前項で述べたように、モジュール内で定義された定数をモジュール外から参照するには「モジュール名::定数名」としなければなりませんでした。 これは「モジュールで、定数にも名前空間をつけることができる」ということを示しています。

定数は変数と同様にオブジェクトを指しますが、変数と違い再代入ができません。 その定数が生きている間はずっと同じオブジェクトを指します。 ユーザが定義する定数は数字や文字列(特にフリーズした文字列)が多いと思いますが、その他にクラス名やモジュール名も定数です。 このことから「クラスやモジュールにも名前空間を適用することができる」ということができます。

module ABC
  class A
    def intro
      print "モジュールABCのAです\n"
    end
  end
end
module EFG
  class A
    def intro
      print "モジュールEFGのAです\n"
    end
  end
end

a = ABC::A.new
b = EFG::A.new
a.intro #=> モジュールABCのAです
b.intro #=> モジュールEFGのAです

プログラムが大きくなり、クラス名の名前の衝突が心配であれば(特にライブラリでは)モジュールを使った名前空間の提供が解決手段となるかもしれません。

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 ↑