Ruby 画像ダウンロード編 (6) モジュール 【計算数学 I】

更新日時:

FileTest モジュール と FileUtils モジュール

モジュールの説明

これから必要とする FileTest と FileUtils は、クラスではなく、モジュールと呼ばれます。 モジュールはよく、クラスみたいなものと説明されます。 このプログラムを書く上では、究極的には、モジュールとクラスの違いを知る必要は少しもありません。 モジュールを正確に説明しだすととりとめがつきません。だから私の独断で勝手に内容の取捨選択を行います。

クラスにできて、モジュールにできないことは、次のとおりです。厳密に、この 2 つだけです。

  • インスタンスの生成ができない (だから、メソッドの集まりと理解できる)
  • 継承できない (インスタンスがないから当然であろう)

モジュールで定義された関数を、モジュール関数と呼びます。 ではなぜこんなクラスの出来損ないのようなものが重宝されるのかというと、インスタンスはいらないけどメソッドだけをひとまとめにしておきたいという動機におおよそ帰着されます。モジュール関数を呼び出す方法は次の3つです。

  1. モジュール関数を直接呼び出す (クラスメソッドみたいな使い方)
  2. あるクラスのインスタンスメソッドとして組み込む (これは正真正銘インスタンスメソッド)
  3. 個々のインスタンス(正確にはオブジェクト)に組み込む

このうち 2. が最も一般的な用法です。クラスの宣言で、include モジュール名 と書くと、モジュール内のメソッドがそのクラスのインスタンスメソッドとして導入されます。この「導入」を Mix-in といいます。この教材では方法自体は関係がないのでサンプルは省略します。概念だけ押さえてください。 3. はもっとマニアックなのでまるごと省略します。

ここでは 1. を解説します。モジュールには名前空間を作るという重要な用途がありますがここでは略します。

とはいっても実際に解説することなんてほとんどなくて、

  • モジュールの関数は (モジュール名).(関数) で呼び出す

ということだけ押さえておけばよいです。クラスメソッドの呼び出し方と何らかわりありません。

トップレベルの理解

この小節は読み飛ばして結構です。飽きてきたら次の小節に飛んで差し支えありません。無理しなくてよいです。

参考:この小節は TA には大変好評でした。

まず、用語を定義します。

  • 普通に私たちがプログラムを書いているところをトップレベルといいます。例えば class end の中はトップレベルではありません。
  • Object クラスは、ほぼ全てのクラスが継承している、大元のクラスです。自分でクラスを定義すれば、自動的に Object クラスを継承します。
  • Ruby 1.9 以降では、全てのクラスを継承する大元のクラスは BasicObject クラスです。Object クラスは BasicObject クラスを継承します。自分でクラスを定義したときに自動的に継承するクラスは Object クラスであるのは変わりません。

メソッドの話をしたときのことを思い出してください。呼び出し方は (なにか).(メソッド名) の形で呼び出しました。この (なにか) をレシーバといいます。 Ruby はオブジェクト指向プログラミング言語です。ですから、レシーバは原則としては必要になります。 レシーバを省略できるのは、

  • あるオブジェクトを取り扱っている際に、そのオブジェクトをレシーバとするメソッドを呼び出すとき

に限られます。こういう場合はレシーバを省略してしまえばよいのですが、それでもあえてレシーバをつけたいなら、self とつけます。

レシーバを省略できるかどうかはとても重要です。そういう呼び出し方しかできないメソッドをプライベートメソッドといいます。 「プライベートメソッド」とは「レシーバを省略しないと呼び出せないメソッド」のことです。これは定義です。 詳しく書くと以下のとおりです。メソッドは、呼び出し範囲により以下の 3 つに分類されます。

  • パブリックメソッド (public method)
    • 呼び出し方法に制限はつかない。
    • デフォルトではこれになる。
  • プライベートメソッド (private method)
    • レシーバを省略しないと呼び出せない。
      • 書き方例1: futaba というメソッドを定義した直後に private :futaba と書く。
      • 書き方例2: private と書いた後にメソッドを定義する。
  • プロテクテッドメソッド (protected method)
    • 同じクラスまたはサブクラスのインスタンスからしか呼び出せない。
    • 外部から呼び出し制限をつけたいなら、プライベートメソッドでよい。特殊なもの。

結論を先取りすると、トップレベルで定義された関数は、全てプライベートメソッドとして定義されます。 ではそれは、クラスメソッドでしょうか? インスタンスメソッドでしょうか? それも、何のメソッドなのでしょうか?

この疑問に答える前に、準備をします。 次の実行結果から、トップレベルは、Object クラスの main というインスタンスの中に入っていることがわかります。

irb(main):001:0> self
=> main
irb(main):002:0> self.class
=> Object

まずこのことを押さえてください。続けて、次のコードをテキストエディタで書いて保存し実行しましょう。

def futaba
  puts "野菜肉肉肉肉肉肉魚"
end

puts self.private_methods(false).include?(:futaba)
puts Object.private_instance_methods(false).include?(:futaba)
puts Object.private_methods(true).include?(:futaba)
puts Object.private_methods(false).include?(:futaba)

実行結果は以下のとおりです。

true
true
true
false

ではこのことを踏まえて解説します。

  • まず private_method で、そのオブジェクトが持つプライベートメソッドを一覧で(配列として)返します。private_instance_method も似たようなものです。
    • その引数は true または false です。引数が true のときは、プライベートメソッドを全て返します。false の場合は、継承または Mix-in されたプライベートメソッドを除いたものを返します。
  • include? は、配列に引数が入っているかどうかを true or false で返します。
  • :futaba はシンボルと呼ばれるもの(文字列のようなものだが違うもの)です。
  • private_method で返ってくる配列の中にはメソッド名をシンボルにしたものが入っています。

実行結果からわかることは、以下のとおりです。

  • puts self.private_methods(false).include?(:futaba)true であるということは、 トップレベルで定義されたメソッド futaba は main のメソッドであるということである。
  • puts Object.private_instance_methods(false).include?(:futaba)true であるということは、 トップレベルで定義されたメソッド futaba は Object クラスのインスタンスメソッドであるということである。
    • 以上の 2 つは整合している。(わからなければ、この教材の最初でやったメソッドの分類まで戻ってください)
  • puts Object.private_methods(true).include?(:futaba)true であるということは、 トップレベルで定義されたメソッド futaba は Object クラスのメソッドであるということである。
    • これはなぜか? 実は、Object クラスは、Class クラスのインスタンスであるからである。Class クラスは、Object クラスを継承している。ゆえに、Object クラスはObject クラスのインスタンスであると言える。(ここが一番わかりにくいです)
  • puts Object.private_methods(false).include?(:futaba)false であるということは、 トップレベルで定義されたメソッド futaba は Object クラスで定義されたメソッドではなく、継承により作られたメソッドである。
    • この事実は 3 番めの解釈を補強している。

ですから、トップレベルで定義された関数はどうなるかというと、以下のとおりにまとめられます。

  • トップレベルで、self は main を指す。main は Object クラスのインスタンスである。
  • トップレベルに定義された関数は、Object クラスのプライベートなインスタンスメソッドとして定義される。
    • 以上より、トップレベルで定義された関数は、トップレベルのどこでもレシーバなしで呼び出せる。
  • Object クラスは Object クラスのインスタンスだから、トップレベルに定義された関数は、Object クラスのプライベートなクラスメソッドでもある。
    • 以上より、トップレベルで定義された関数は、どのクラスからもレシーバなしで呼び出せる。Object クラスは全てのクラスが継承するからである(例外を除く)。

で、ここまで話を進めてきたのには理由があります。モジュール関数の呼び出し方として

  • トップレベルでモジュールを include し、(関数) で呼び出す

という方法があるからです。これは (モジュール名) というレシーバを省略できています。なぜレシーバを省略できるのでしょうか? トップレベルでモジュールを include すると、Object クラスに Mix-in されるからです。モジュール関数は、Object クラスのインスタンスメソッドとなりますが、 Object クラスは Object クラスを継承した Class クラスのインスタンスですから、Object クラスのメソッドになります。つまり Object クラスのクラスメソッドです。 こうしておけば、一部例外を除いて全てのクラスで、そのモジュール関数を呼び出せます。なぜならほぼすべてのクラスは Object クラスを継承しているからです。

ここまで読めた人は、大変理解力がある方だと思います。そして、ここまで読んでもらって大変申し訳無いのですが、 普通ですと、FileTest モジュールや FileUtils モジュールは、main に include する積極的意味は無いかと思います。 だからここで書いたことの後半部分は、画像 333 個ダウンロードするプログラムとは直接関係ありません。 一般には、レシーバを書くのがめんどくさいという意味で include することはあるかもしれません。 情報科学(アルゴリズム入門)では Math モジュールでこの方法が用いられることが多いようです。

ディレクトリやファイルが存在するかどうかを確認するには?

FileTest モジュールの exist? を用います。引数は相対パスまたは絶対パス(フルパス)の文字列で記述します。

例えば、 irb を実行したディレクトリの下に、yoko と futaba というディレクトリがあって、かつ、teru がない場合は、以下のようになります。

irb(main):001:0> FileTest.exist?('yoko')
=> true
irb(main):002:0> FileTest.exist?('futaba')
=> true
irb(main):003:0> FileTest.exist?('teru')
=> false

ディレクトリを作成するには?

FileUtils モジュールの mkdir_p を用います。引数は相対パスまたは絶対パス(フルパス)の文字列で記述します。 これも例を見てもらえばわかるでしょう。以下では irb を実行したディレクトリに teru というディレクトリを作成して、 それが存在するかどうかを FileTest.exist? で判定しています。

irb(main):001:0> require 'fileutils'
=> true
irb(main):002:0> FileUtils.mkdir_p('teru')
=> ["teru"]
irb(main):003:0> FileTest.exist?('teru')
=> true

すでにディレクトリが作成されている場合は、何もしません。

演習

次のプログラムの空欄を埋めて、「このプログラムと同じディレクトリの『 icons 』というディレクトリ」がなければ作るプログラムを作成しましょう。

require 'fileutils'

dir = # File.expand_path で「このプログラムと同じディレクトリの『 icons 』というディレクトリ」のフルパスを得る

if # dir に指定されたディレクトリがなければという条件式
  # dir を作成する
  puts "ディレクトリ #{dir} を作成しました。"
end

参考:true false といった真偽値を否定したい場合は、 前に ! をつけます。

ナビゲーション

この教材は、東京大学理学部数学科専門科目「計算数学 I」のために執筆されたものです。 このサイトに掲載する際に、記事を分けてあります。 他の回はRuby 画像ダウンロード編 一覧 #ks1-ruby-downloadから御覧ください。

Ruby 入門 (計算数学実習資料集)には他の TA が書いた教材があります。

コメントする