2003年度前期 IT教育基礎論特論B

第6回: 計算モデルとプログラミングパラダイム

■ 概要

   計算モデル、プログラミングパラダイムの違いによる プログラミング手法の違いを学習する。

■ 目標

■ 目次


■ 講義内容

□ 計算モデル

   計算モデルとは、コンピュータの中でプログラムがどのように実行され、 どのような答えを出すかを体系化した概念である。 すなわち、コンピュータに与えられたプログラムに基づき、 コンピュータがデータをどのように処理し、 その結果として、どのような出力を行うかをモデル化したものである。

   コンピュータが直接実行できる機械語は、 ハードウェアにより提供される極めて基本的な機能と1対1に対応しており、 そのままでは人にとって理解が困難である。 そこで、コンピュータ内でのプログラムの実行にともなう処理内容を 明確な(数学的に定義可能な)枠組みで捉えることができると便利である。 このような考えから、現在、表2に示すように多数の計算モデルが提案されている。

表1: 計算モデルの例
モデル概要
機械モデル チューリングマシーン等の仮想的なコンピュータシステムにおける 状態遷移をモデル化したもの。
関数モデル コンピュータによる計算を簡単な関数の組み合わせとして捉える。 λ式とその簡約により定義できる。
論理モデル 論理式を定義し、その真偽や証明を求める。
書き換えモデル 関数モデルや論理モデルで与えられた表現を変形し、 意味の自明な表現を導く。
代数モデル 処理対象となるデータを集合として捉え、 集合に定義された演算により、 新たな集合の生成や要素の特定を行う。

□ プログラミングパラダイム

プログラミングパラダイムとは
   プログラミングパラダイムとは、 1つないし複数の何らかの計算モデルに基づき、 どのようにプログラムを記述するかを定めたものである。 コンピュータにより何らかの処理を行うためにプログラムを記述する場合、 コンピュータで提供される基本的な機能を組み合わせ、 具体的な処理内容を定義する。 このとき、そのプログラムは以下の要素からなる。

  基本式
そのプログラミング言語により提供される最も基本的な機能の表現

  組み合わせ法
複数の機能を組み合わせ、より複雑な機能を実現する合成物を作る方法

  抽象化法
新しく作られた合成物に名前をつけ、1つの機能として利用できるようにする方法

   すなわち、プログラミングパラダイムとは、 プログラミング言語により提供される基本式、組み合わせ法、抽象化法の3つが、 どのような形式になるかを定義するものである。

プログラミングパラダイムの種類
   プログラミングパラダイムには、 基本式、組み合わせ法、抽象化法の異なる多様な種類があり、 これに基づくプログラミング言語も多様である。 以下、その主なものを述べる。
  関数型プログラミング言語
   関数型プログラミング言語における基本式は、 そのプログラミング言語において予め定義されている関数であり、 この関数を組み合わせることによって、複雑な計算を行う。 また、関数を組み合わせたものに名前をつけることによって、 新たな関数を定義し、利用することができる。

  論理型プログラミング言語
   論理型プログラミング言語の基本式は、 事実および規則を定義する述語である。 この規則を組み合わせることにより、 問い合わせられた内容の真偽を求めることができる。 このとき、関係する2つの規則が定義されれば、 自動的に、あらたな規則が生成されることとなる。 例えば、「AならばB」、「BならばC」という規則が定義されれば、 結果的に「AならばC」という新たな規則が定義される。

  手続き型プログラミング言語
   手続き型プログラミング言語では、 そのプログラミング言語で提供される基本的な手続きを順番に並べて プログラムを記述する。 すなわち、手続き型プログラミング言語の基本式は手続きであり、 その組み合わせ法は、手続きの列となる。 また、この手続きの列に名前をつけて、 新たな手続きとして定義することができる。

  オブジェクト指向プログラミング言語
   オブジェクト指向プログラミング言語の基本式はオブジェクトである、 オブジェクト間の相互作用を定義することにより、プログラムを記述する。 オブジェクトとは、手続き(メソッド)とデータ(属性)を カプセル化したものであり、 このオブジェクトに対しメッセージを送ると、 対応するメソッドを実行する。 あるオブジェクトのメソッド中に 他のオブジェクトへのメッセージ送信を記述することで、 オブジェクト間の相互作用を定義することができる。 すなわち、オブジェクト指向プログラミング言語における組み合わせ法は、 オブジェクト間のメッセージ交換により実現される。 このとき、既存オブジェクトのメソッドや属性の一部を変更した 新しいオブジェクトを定義することや、 新たなメソッドと属性を持つ新規のオブジェクトを定義することができる。

  エージェント指向プログラミング言語
   エージェント指向プログラミング言語は、現在研究が進められている 新しいプログラミングパラダイムに基づくプログラミング言語であり、 プログラミングパラダイムのその具体的な厳密な定義はまだないが、 その基本式がエージェントである。 エージェントとは、一般的には「自律的なオブジェクト」であると言われる。 すなわち、オブジェクトが他のオブジェクトからメッセージを受け取って 初めてそのメソッドを実行するのに対し、 エージェントは、他のオブジェクトの状態や周りの環境の変化に応じて、 自発的にメソッドを実行する点が異なる。

  スクリプト型プログラミング言語
   スクリプト型プログラミング言語は、特定の用途のために、 簡単な命令を組み合わせてプログラムを記述することのできる プログラミング言語である。 スクリプト型プログラミングパラダイムというものがあるわけではなく、 多くは手続き型のプログラミングパラダイムに基づいているが、 オブジェクト指向プログラミングパラダイムに基づく プログラムの記述ができるものも多い。

   以上をまとめると、主なプログラミングパラダイムの種類は 表2のようになる。

表2: プログラミングパラダイムの種類
プログラミング
パラダイム
基本式 組み合わせ法 抽象化法
関数型 関数 関数の入れ子 関数の組み合わせからなる新たな関数の定義
論理型 述語(事実および規則) 規則の組み合わせ 規則の組み合わせにより自動的に新たな規則が生成される
手続き型 手続き 手続きの列 手続きの列に名前をつけ、新たな手続きとして定義
オブジェクト指向 オブジェクト オブジェクト間のメッセージ交換 既存オブジェクトのサブクラスの生成もしくは新規クラスの定義
エージェント指向 エージェント エージェント間のメッセージ交換 既存エージェントのサブクラスの生成もしくは新規クラスの定義
スクリプト型 命令 命令の列 新規命令の定義

□ プログラミングの例

   ここでは、 関数型プログラミングパラダイムと手続き型プログラミングパラダイムの 両者に基づいたプログラムの記述を行えるインタプリタ型の プログラミング言語であるLISPを例にとりそのプログラミング例を示し、 LISPにおけるプログラミングパラダイムを確認する。

Emacs Lispの利用
   プログラミング言語を学習する場合、 実際にそのプログラミング言語を使用してプログラムの記述し、 実行してみるのがよい。 LISPによるプログラムの記述、実行を行う場合、 Emacs Lispを利用すると便利である。 Emacs Lispを利用するには、 コマンドラインからファイル名を指定せずに Emacsを起動するとよい。 すると、図1のような画面となる。

図1: Emacsの起動

   ここで、何かキーを押すと、図2のように ステータスラインに「Lisp Interaction」と示された状態となる。 この状態を「Lisp Interactionモード」と呼び、 対話的にEmacs Lispの入力、実行を行うことができる。

図2: Lisp Interactionモード

   Emacs Lispを入力、実行するには、 キーボードからEmacs Lispの命令を入力し、 [C-j]を押す([Ctrl]キーを押しながら[j]を押す)。 例えば、2つの整数: 2と3の足し算を行いたければ、 「(+ 2 3)」と入力した後、[C-j]を押す。 その結果、図3のようになる。

図3: Emacs Lispの実行例
(+ 2 3)            ;ここで[C-j]を押すと
5                  ;と表示される
                                                                 

   実行結果をファイルに保存したい場合、[C-x] [C-s]とタイプする。 図4のように、ステータスラインの下(最下位行)の部分でファイル名を聞かれるので、 適当なファイル名を入力し、[Enter]キーを押すことで、 指定したファイルに保存することができる。

図4: 実行結果の保存
(+ 2 3)
5
 
-EEE:**-F1  *scratch*       8:50PM    (Lisp Interaction Encoded-k
File to save in: ~/ 

LISPの基本式
   LISPのプログラムは、S式(S Expression)により定義され、 S式を実行することを評価(Evaluation)と呼ぶ。

   もっとも簡単なS式は、数字や文字列などであり、 これを評価すると、その値そのものを結果として得る。 例えば図5のように、数字128を評価すると128 を得、 また、文字列"Hello"を評価すれば、"Hello "を得る。

図5: 数字や文字列の評価
128               ;と入力し[C-j]を押すと
128               ;と表示される

"Hello"           ;と入力し[C-j]を押すと
"Hello"           ;と表示される
                                                                 

   またS式として、LISPで提供される、 +*sqrtなどの 関数による手続きを入力することもできる。 関数による手続きを評価すると、図6のように、その手続きを実行した結果を得る。

図6: 手続きの評価
(+ 2 3 4)          ;ここで[C-j]を押すと
9                  ;と表示される

(* 2 3 4)          ;ここで[C-j]を押すと
24                 ;と表示される

(sqrt 4)           ;ここで[C-j]を押すと
2.0                ;と表示される
                                                                 

   なお、LISPにおける関数の表記は、 func (x ,y )のような一般的な関数の表記方法と異なり、 (func x y)のようになることに注意する。

手続きの合成
   LISPで提供される関数による手続きを組み合わせ、複雑な処理を行うには、 図7のように関数を入れ子にした式を記述する。 この場合、内側の括弧から外側の括弧に向かって式が評価される。

図7: 関数の入れ子
(* (+ 1 2) (+ 3 4))          ;ここで[C-j]を押すと
21                           ;と表示される
                                                                 

   長い式は、図8に示すように途中で改行を入れると見やすくなる。

図8: 長い式の記述と評価
(* (+ 1 2)                   ;ここでは[C-j]を押さず改行し
   (+ 3 4))                  ;ここで[C-j]を押すと
21                           ;と表示される
                                                                 

   手続きを合成する場合、 条件分岐を行う手続きにより、 条件に応じて異なるS式の評価を行う手続きを合成することができる。 例えば、ある条件の真偽に応じて異なる式を評価したい場合、ifを利用する。 ifの書式は以下のようになる。

(if  <条件式>  <式1>  <式2> )

   ifでは、条件式を評価した結果、真であれば式1を評価し、 偽であれば式2を評価する。 例えば、ifを利用すると、図9のようになる。

図9: ifによる条件分岐
(if (> 1 2) "large" "small")      ;ここで[C-j]を押すと
"small"                           ;と表示される

(if (> 2 1) "large" "small")      ;ここで[C-j]を押すと
"large"                           ;と表示される
                                                                 

   真偽のような2つの場合分けではなく、3種類以上の場合分けを行いたい場合、 condを利用することができる。 condの書式は以下のようになる。

(cond  (<条件式1>  <式1> )
(<条件式2>  <式2> )
...
(<条件式n>  <式n> ) )

   condでは、条件式を1から順番に評価していき、 その結果が真であれば対応する式を評価し、終了する。 例えば、condを利用すると、図10のようになる。

図10: condによる条件分岐
(cond ((> 0 0) "plus")
      ((= 0 0) "zero")
      ((< 0 0) "minus"))     ;ここで[C-j]を押すと
"zero"                       ;と表示される

(cond ((> -1 0) "plus")
      ((= -1 0) "zero")
      ((< -1 0) "minus"))    ;ここで[C-j]を押すと
"minus"                      ;と表示される
                                                                 

合成された手続きの抽象化
   LISPでは、合成された手続きにに名前をつけ、抽象化するために、 λ演算に基づき新たな関数を定義することができる。 新たな関数を定義するには、defunを利用する。 defunの書式は以下のようになる。

(defun  <関数名>  <内部変数のリスト>  <定義式> )

   例えば、与えられた値の二乗を計算する関数は、 defun関数を用いて図11のように定義することができる。

図11: 新たな関数の定義
(defun square (x)
  (* x x))                 ;ここで[C-j]を押すと
square                     ;と表示され、関数squareが定義される
                                                                 

   新たに定義された関数は、図12のように、 もともとLISPで提供されている関数と同様に利用することができる。

図12: 新たに定義された関数の利用
(square 3)                 ;ここで[C-j]を押すと
9                          ;と表示される
                                                                 

   図13に示すように、複数の引数を取る関数や、 関数の入れ子により定義される関数など、 複雑な関数を定義し、利用することもできる。

図13: 複雑な関数の定義と利用
(defun magnitude (x y)
  (sqrt (+ (* x x) (* y y))))
magnitude

(magnitude 3 4)
5.0
                                                                 

   if等を利用し、条件に応じて異なる動作を行う関数を定義することもできる。 例えば、与えられた値の絶対値を求める関数absを 図14のように定義し、利用することができる。

図14: ifを利用した関数の定義と利用
(defun abs (x)
  (if (>= x 0) x (- x)))
abs

(abs 4)
4

(abs -2)
2
                                                                 

   LISPによる関数の定義では、 定義する関数自身を呼び出す関数を定義することができる。 これを関数の再帰呼び出し(recursive call)と呼び、 また再帰呼び出しを行う関数を再帰関数と呼ぶ。

   例えば、x の階乗、すなわち

x ! = x · (x - 1) · (x - 2) · · · 1

は、以下のように帰納的に定義することができる。

x ! = { x · (x - 1) !          (x > 0 の場合)
1          (x = 0 の場合)

   LISPでは、階乗計算の帰納的定義のように、 再帰呼び出しによる関数を図15のように定義し、利用することができる。

図15: 再帰関数の定義と利用
(defun fact (x)
  (if (> x 0) (* x (fact (- x 1))) 1))
fact

(fact 3)
6

(fact 5)
120
                                                                 

■ レポート課題

   以下のプログラムを作成し、実行せよ。 プログラミング言語は問わない。 レポートには、作成したプログラムとその実行結果を示すこと。
問1
   2つの数値の3、5の平均を求めよ。
問2
   2つの数値xy を引数に取り、 その平均を求める関数averrage(x, y )を定義し、 これを利用して3と5の平均を求めよ。
問3
   2つの数値xy を引数に取り、 大きい方の数値を返す関数larger(x, y )を定義し、 2および3のうち、大きな数値を求めよ。
問4
   与えられた2つの引数のうち、 絶対値が大きい方の数値を返す関数abslarger(x, y)を定義し、 2および-3のうち、絶対値が大きな数値を求めよ。 ただし、定義した関数の中で別の関数を利用してもよい(利用しなくてもよい)。
問5
   1からnまでの総和を求める関数 以下のように帰納的に定義することができる。

sum(n ) =
n
Σ i
i = 1
= {
n - 1
n + Σ i
i = 1
= n + sum(n -1)   (n > 1)
1   (n = 1)
 

   この定義に基づき、1からn までの総和を求める関数sum(n )を定義し、 1から10までの総和を求めよ。

■ 参考書籍、Web

  1. 橋本 洋志, 冨永 和人, 松永 俊雄, 小澤 智, 木村 幸男: 「図解コンピュータ概論」, オーム社, ISBN4-274-13108-4, 2,500円
  2. ジェラルド・J・サスマン他著、和田英一訳: 「計算機プログラムの構造と解釈 第二版」, ピアソン, ISBN4-89471-163-X, 4,600円

Last modified: Sun Jun 01 21:58:08 JST 2003