[プログラミング演習(Ruby) >  メソッドの定義と利用(1)]

メソッドの定義と利用(1)

これまで,print,getsのような関数的メソッド, あるいは文字列のto_iのようなオブジェクトのメソッドなど,さまざまなメソッドを利用してきました. メソッドは定められた一連の処理を行う仕組みです. たとえば,printメソッドは画面にオブジェクトの内容を表示するものであり, 文字列のto_iメソッドは文字列データを整数データに変換するものです.

メソッドはRubyに用意されているものを使うだけでなく,自分で定義することもできます. 新しいメソッドを定義すれば,プログラムに新しい機能を追加できます. ここでは実用的なプログラムを書くために欠かすことのできない「メソッド」について,とくにプログラムの汎用的な部品を作って利用するという観点から簡単に解説します. なおここでは関数的メソッドのみを対象とします.

もくじ

  1. 例題
  2. 描画手順
  3. プログラム一覧
  4. プログラムver.0
  5. プログラムver.1
  6. プログラムの構造化
  7. メソッドの導入
  8. プログラムver.2
  9. メソッドの汎用化
  10. プログラムver.3
  11. プログラムver.4
  12. プログラムver.5
  13. プログラム(参考)

例題

タートルグラフィクスにより次のように四つの正方形からなる図形を描く. 正方形のサイズは小さい方から順に100x100,110x110,120x120,130x130で正方形同士の間を8ずつ空けることにする.
四つの正方形の画像

描画手順

例題の図形は具体的には次のような手順で描くことができます.


 まず四つの正方形に次のように番号をつける
   2番 1番
   3番 4番

 次のような手順で描く
  1. 1番目の正方形の左下角からスタートして1番目の正方形を描く
  2. 2番目の正方形の右下角に移動して2番目の正方形を描く
  3. 3番目の正方形の右上角に移動して3番目の正方形を描く
  4. 4番目の正方形の左上角に移動して4番目の正方形を描く
  5. 最初の点に戻る

四つの正方形の画像

プログラム一覧

ここでこの資料で用いるプログラムの一覧を示します.

プログラムver.0

次のプログラムで,例題の図形を描くことができます. プログラムを実行してみれば,例題で要求されている図形を描けることが確かめられます. ただし,このプログラムでは,どこで何をしているかを理解するのが困難です.

4tiles_ver0.rb
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]

プログラムver.1

次のプログラムでは,コメントをつけています. また適宜空行をはさんでいます. コメントを入れることで,プログラムで何をしているかが理解しやすくなっています. また空行により,プログラムの構造的な区切りが分かりやすくなっています.

4tiles_ver1.rb
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]

プログラムの構造化

先のプログラムver.1は,ver.0にくらべて理解しやすいとはいえ, 全体の構造を一目で把握することは相変わらず簡単ではありません. 一般にプログラムが長く複雑になるほど全体の構造は見えにくくなっていきます. プログラムの見通しをよくするには,プログラムを構造化することが必要です.

構造をもったモノは,プログラムに限らず,あちこちで見ることができます. たとえば,書籍を考えてみます. 書籍の内容は文章で表現されています. しかし書籍は単に文の並びでできているわけではありません. 書籍には,概要を示す目次があり, 内容の区切り毎に章が立てられ,章はさらに節に分けられ,節は段落からなります. 書籍にこのような構造があることが内容の理解に大いに役立っています.

プログラムを構造化するとは,プログラムを(機能的な)部品の集まりとして構築することです. 部品自体も,必要に応じてさらに小さな部品の集まりで構築します. Rubyプログラムでは,部品はメソッドとして作成します.

他のプログラミング言語では,同様の機能がメソッドではなく関数と呼ばれることがあります.

メソッドの導入

Rubyでは,一連の処理をまとめて,それに名前をつけることで,それをプログラムの部品であるメソッドとして利用することができるようになります. メソッドを作成することは,プログラムに新しい機能を加えることであるともいえます. メソッドは次のように定義できます(後でさらに応用のきく形を示します).

  def メソッド名()
    #
    # ここに処理を書きます.
    #    
  end

作成したメソッドの名前をプログラムで書けば,そのメソッドの内の処理が実行されます.

プログラムver.2

例題のプログラムにおいて,正方形を描く部分をそれぞれメソッドにしてみると,次のようになります. 四つの正方形を描く部分は,それぞれsquare1(),square2(),square3(),square4()と記述されています. メソッドsquare1(),...square4()の処理内容は別途定義されています.

4tiles_ver2.rb
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]

プログラムver.2の解説

まず,これまで特に触れませんでしたが,描画の記述全体がdrawというメソッドとして定義されていることに注目して下さい. (この授業で用いている)タートルグラフィクスでは, drawというメソッドによって亀の動作を記述するように定めてあります.

ここで『def draw』のあとに『()』がついていないことが気になるかもしれません. 『def メソッド名』は『def メソッド名()』の省略形です. この場合,どちらでも同じように解釈されます.

    def メソッド名()    def メソッド名
      #                   #
      #                   # メソッド名に『()』をつけなくてもOK
      #                   #
    end                 end

さてすでに見た通り,プログラムver.2では四つの正方形を描く部分は,プログラムの後半でそれぞれsquare1(),square2(),square3(),square4()のようにメソッドとして記述されています. draw()の中でsquare1()などの記述があると,そこで別途square1()として定義している一連の処理を実行することになります.

 18      # 1番目の正方形(100x100,青)
 19      square1()
  :
  : 
 52    # 1番目の正方形(100x100,青)
 53    def square1()
 54      set_color(0,0,255) # 色指定
 55      turnto(270)        # 東を向く
 56      forward(100) 
 57      turn(90)     
 58      forward(100) 
 59      turn(90)    
 60      forward(100) 
 61      turn(90)    
 62      forward(100) 
 63      turn(90)    
 64    end

こうしてメソッドを使って記述しておくことで,描画手順の記述(drawの定義)がコンパクトになっていることが分かります. またsquare1()などのように名前を使って書くことで,それが正方形を描く部品であることが分かりやすくなっています.

なお各メソッドの定義は,それぞれ独立に書いておく必要があります. メソッドの定義の中でさらに別のメソッドの定義を書くことはできません.

メソッドの汎用化

プログラムver.2で定義したメソッドsquare1(),square2(),square3(),square4() はそれぞれ非常に似た構造になっています.

  def square1()          def square2()          def square3()            def square4()        
    set_color(0,0,255)     set_color(255,0,0)     set_color(255,255,0)     set_color(0,255,0) 
    turnto(270)            turnto(0)              turnto(90)               turnto(180)          
    forward(100)           forward(110)           forward(120)             forward(130)       
    turn(90)               turn(90)               turn(90)                 turn(90)           
    forward(100)           forward(110)           forward(120)             forward(130)       
    turn(90)               turn(90)               turn(90)                 turn(90)           
    forward(100)           forward(110)           forward(120)             forward(130)       
    turn(90)               turn(90)               turn(90)                 turn(90)           
    forward(100)           forward(110)           forward(120)             forward(130)       
    turn(90)               turn(90)               turn(90)                 turn(90)           
  end                    end                    end                      end                  

これらのメソッドで異なっているのは次の点です.

そこでこれらのデータに名前をつけて,メソッドを書き換えてみると,次に示すように四つのメソッドが全く同じ記述になることが分かります. データの名前の部分に実際のデータを適宜当てはめれば,もとのsquare1()...square4()の記述に戻せることを確かめて下さい.

  def square()
    set_color(r,g,b) 
    turnto(dir)      
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
  end

さて,メソッドは,一連の処理をまとめて名前をつけたプログラムの部品でした. じつは,メソッドで行う処理は,ここで示したように一般化することができます. つまり,メソッドではデータを名前で表すことができるわけです. メソッドのデータに名前をつけるということは,メソッドの処理を変数を使って表現することに他なりません. メソッドを使うときには,それらの変数に値を代入して処理を行うことになります.

このようにしてメソッドを汎用化するために利用する変数のことをメソッドの引数(ひきすう;argument)といいます. メソッドの引数は次のようにして定義の先頭に記述しておきます.

  def square(len,dir,r,g,b)
    set_color(r,g,b) 
    turnto(dir)      
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
  end

このような引数つきのメソッドを利用する場合には,たとえば次のようにして各引数の値を指定します.

  square(100,270,0,0,255)   

このように書くことでメソッドの引数に順に値を代入することができます. この例の場合,具体的には次のような代入を行うことになります. この対応関係のもとでメソッドの処理を実行することになります.

  len = 100, dir = 270, r = 0, g = 0, b = 255

ここで示したように,同じような処理を行うメソッドは一つにまとめて汎用化しておくことが重要です. 同じような処理を別々に定義しておくことは混乱のもとになります.

引数の区別 --- 仮引数と実引数

さて,メソッドを呼び出すときに「〜メソッドに〜という引数を与えて呼び出す」のようにいいます. 一方すでに説明した通り,メソッド定義においてメソッドが受け取るデータを表す変数も引数と呼びます. そこでこれらを区別しなければならない場合,メソッドを利用するときに与えるデータのことを実引数,メソッドの定義で使う変数を仮引数といいます. 文脈からその区別が明らかな場合には単に「引数」としても構いません.

プログラムver.3

正方形を描くメソッドを一般化すると次のようにプログラムを書き直すことができます.

4tiles_ver3.rb
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]

プログラムver.3の解説

プログラムver.3では,正方形を描くメソッドの定義は一般化されて一つにまとめられています. 一つのメソッドで異なる正方形を描くようにしているため,メソッドを利用する際に描く正方形に応じた引数の値を指定しています. 似たようなメソッドをまとめることで, 「正方形を描く」という同じ動作を繰り返していることがハッキリ分かるようになり, プログラムの構造がより明確になります.

また類似したメソッドをまとめたことにより,プログラムの修正が楽になります. プログラムver.2では,正方形を描く部分に間違いが見つかった場合,四つのメソッド定義のすべてを修正する必要があります. 四つなら修正できると考えるかもしれませんが,描く正方形がさらに増えた場合,そのようなメソッドの書き方が適切でないことは明らかでしょう(プログラムver.0,あるいはプログラムver.1でも修正が困難であることは同様です). 一方,プログラムver.3では,描く正方形がいくつになっても,修正すべきメソッドはたった一つです. さらにメソッドを一つにまとめたことによりプログラムがコンパクトになっています.

メソッドsquare()の改良

上のプログラムでsquare()の処理は次のように定義されています.

  def square(len,dir,r,g,b)
    set_color(r,g,b) 
    turnto(dir)      
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
    forward(len) 
    turn(90)     
  end

この定義は繰り返し処理(times)を用いることでより簡潔に書き直すことができます.

  def square(len,dir,r,g,b)
    set_color(r,g,b) 
    turnto(dir)      
    4.times do 
      forward(len) 
      turn(90)     
    end
  end

プログラムver.4

square()をtimesで書き直したプログラムを示します.

4tiles_ver4.rb
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]

プログラムver.5

プログラムver.4をみると,正方形を描く部分だけでなく,正方形から次の正方形に移動する部分も似たような構造をしていることが見て取れます. そこでこれらも次のようにメソッドとしてまとめてみることにします.

  # 方位dirを向いて(何も描かずに)distだけ進む
  def turn_and_go(dir,dist) 
    set_color(nil)
    turnto(dir)
    forward(dist)
  end

最終的にプログラムは次のようになります. 最初のプログラムとくらべても,ずいぶん表現がコンパクトになり,しかも何をしているのかが一瞥できるようになっていることが分かります.

4tiles_ver5.rb
[行番号つきプログラムを別のウィンドウで開く] [行番号なしのプログラム]

メソッドを使うことの利点

ここでメソッドを使ってプログラムを書くことの利点をいくつか挙げてみます.

プログラム(参考)

さて,例題のプログラムでは,「正方形を描く,移動する」ということを4回繰り返しています. もちろん全く同じ動作をしているわけではありませんが,4回の動作が似ていることは確かです. 実際,これらの動作は繰り返しを用いてまとめることができます. そのようにしてよりコンパクトにしたプログラムを示します.

ただ,このようにしてしまうと,かえってプログラムが読みにくくなります. 配列を用いれば,読みやすさを保ったまま,繰り返しの構造をプログラムに採り入れることができます.

[プログラミング演習(Ruby) >  メソッドの定義と利用(1)]