サブプログラム
大きく複雑な問題があったとき、まず、その問題をよく分析して、いくつかのより小さな、より単純な互いに独立な問題に分割します。それらの小問題を順番に解決していけば、大きな問題を解決したことになります。
プログラム作成でも、このような方法で取り組むのが普通です。これを補助するものとして、サブプログラムというものが存在します。
これも最初の方で書きましたが、現代的な観点では「モジュール化」と言う言い方の方がしっくり来るでしょう。
なお、元々C言語なんかでは、確かにmainと呼ばれるプログラムと「それ以外」のプログラムがあって、「それ以外」はサブプログラム、と呼んでも良いかもしれません(呼ばないでしょうがね)。
一般に、C言語等のコンパイラが中心の言語(※)では
- 特定の"主"プログラムしか端末から呼び出せない。
- 従って、個々の「小さな」プログラム単独では実行出来ない。
と言うような設計が成されています。従って、明らかに主従関係が存在するんですが、一方、現代的な言語では個々のプログラムは全て「単独で動く」ので、事実上、主従関係は形式的には存在しない、のです。
ここで紹介した4つの動的言語は全て「現代的」なものです。
※:ちなみに、ANSI(アメリカ国内標準規格)に従う限り、 C言語では特にコンパイラとしての動作が仕様書で定義されているわけではないようで、理論的には「Cのインタプリタ」は存在しうる。従って「Cはコンパイラ型の言語で〜」と言う説明は原理的には間違っている。
また、理論的には、ANSIで制定された言語のうち、コンパイラとしての動作がきちんと定義されているのはANSI Common Lispしか存在しないようだ。
なお、「ANSIはアメリカの基準でしょ?日本国内には関係ないんじゃない?」と思われるかもしれない。事実、日本国内ではJIS、と言う独自規格がちゃんと存在してはいるが、ことプログラミング言語に関して言うと、JISで制定されたものの中身の殆どはANSIのパクりである。
引き数
サブプログラムは、ある問題(上位の問題)を分割した小問題(下位の問題)の解決手順および段取りを書いたプログラムです。当然、いくつかのある小問題のなかで、どの小問題に対するサブプログラムであるかを明らかにするために、そのサブプログラムに名前(サブプログラム名)をつけて区別することになります。上位にある問題のプログラムでは、下位にある問題のサブプログラムの名前を呼ぶだけで、その問題は解決されます。
ここで重要なのは、上位、下位、と言う概念はどもかくとして
プログラム同士はお互いに呼び出しあっても良い
と言う事を知る事、です。
主問題のプログラムの基本要素の中に、コンピュータと外部世界との情報のやり取りの部分である入力と出力があります。サブプログラムでは、それを呼び出すより上位のプログラムと情報をやり取りしなければなりません。また、サブプログラム同士でも情報をやり取りする必要が生じるときもあります。
サブプログラムは、それ自身、独立したプログラムであり、独立した世界です。他の世界、すなわち他のプログラムとの接触は、必要最小限におさえなくてはなりません。そのために、他のプログラムと情報をやり取りする場所は、一ヶ所にまとめられ、さらに限られたものに限定されます。これは、江戸時代、幕府の鎖国政策において唯一長崎出島でオランダ人、ポルトガル人を通してのみ外国との接触が認められていたのに例えることができるでしょうか?
出島(笑)。「例えることができるでしょうか?」とか言われたってねえ(苦笑)。上手い例えなんだか下手な例えなんだか(笑)。
一つ言えるのは、「主問題のプログラムさえ」与えられる情報は制限されてる、って事です。別に出島、じゃなかった(笑)、玉川用語で言う「サブプログラム」に限った話ではない、って事です。この説明だと「主問題のプログラムだったら」どんな情報でも受け取れる、とも読めますね。んなこたあない、です。
サブプログラムにおいて、出島のオランダ人、ポルトガル人、すなわち外国の情報を持ってきて日本の情報を選び出すものにあたるものが引き数といわれるいくつかの値や変数です。サブプログラムは、この引き数といわれるもののみを通して他のプログラムと情報を交換します。サブプログラムを記述する段階では、引き数となる具体的な値や変数は決定されていません。ただし、それらのデータ型は確定しているものとします。そこで、サブプログラムを書く段階では、仮引き数と呼ばれる、実際に情報をやり取りするものの代用品を用います。
下線部:亀田
ほら、またそう言うウソ書く〜〜〜。
通大用語で言う「サブプログラム」と「引数」(通大テキストでは"引き数"と記述される)を無理矢理結びつける必要、って無いでしょうが。これじゃあ「引数」は「サブプログラムでしか使えない」ように読めちゃうでしょ?
(あるいは、Pascalがそう言う実装なのか?良く知らんけど)
主目的のプログラムでも引数は使えます。C言語なんかでも「コマンドライン引数」ってのありますよね?
「でもプログラミング初心者用テキストだし、コマンドライン引数は難しいから。」
とか言う言い訳聞こえてきそうですが、だったらなおさらこう言うシチメンド臭い解説はしないに越した事無い、です。
これ以降の解説の方が遥にマシです。
数学的な例で、これを見てみます。数学で関数f(・)を定義するとき、
f(x) = 3x + 1
という式で表現することがよくあります。ここで、変数xを用いましたが、それを変数aにかえても、変数zにかえても定義される関数は同じものです。このとき、このxが仮引き数になります。また変数xは、ここでは明記されていませんが、実数であることは暗黙のうちに仮定されています。プログラミングにおいては、原則としてこのような暗黙の指定はないものと思ってください(プログラミング言語によっては暗黙の宣言を認めているものもありますが)。関数f(・)をつかうことにより、f(2)と書けばそれは3×2+1を計算した値を意味します。今の場合、この2が実際の引き数(実引き数)となります。
下線部:亀田
プログラミング言語によっては暗黙の宣言を認めているものもありますが
そうですね。僕達は既にそう言う「動的型付け言語」を用いて、このテキストを読み始めています。
さて、1ページ以上に渡る説明の殆どは無意味で、要するに引数とは、
数学的関数をf(x)とした時、数学上はxを「独立変数」と呼ぶが、プログラミング用語では「引数」と呼ぶ
それだけ、なんです。大した話じゃあありませんし、中学生にも理解出来る話、です。
よって「サブプログラム云々」ってのはどーだって良い話、なんです。
関数と手続き
通常、サブプログラムは関数と手続きの2種類に分類されます。この2つは、共に一連の命令が記述されたプログラムであることにかわりはありません。関数は数学におけるのと同様に、なにかの値を計算しその値を返すことを主とします。それに対して、手続きはそれ以外のなにかまとまった仕事をするということが中心となります。
関数は呼び出されると、なんらかの値をその関数名で返します。これはデータを別のデータに変換することを意味します。ある関数が別のプログラムで呼び出される場所は、その返される値を使うために式の中でなければなりません。手続きは、関数と異なり値をその手続きの名前で返すことはありません。しかし、それを呼び出されたならば、なんらかの仕事をし、場合によってはその仕事の成果をもって帰ります。普通、その成果は引き数の中の一つの変数の中に納められています。
冒頭の
サブプログラムは関数と手続きの2種類に分類されます
から始まって、ツッコミどころが多過ぎるんだよな〜〜〜(苦笑)。
まあ、少なくとも言えるのは、これはプログラムの一般論、と言うよりは「半分以上が」Pascalの解説です。だからホントに困ったちゃん、なんですよ(笑)。
まず、工学的には、確かに次の区分けが存在している事は事実なんです。
- 関数:計算した「結果」を返すプログラム
- 手続き(プロシージャ):「計算」自体と何も関係無いプログラム
ここで「計算自体と何も関係無い」自体を定義する事が難しいんですよね(笑)。例えば、「表示」なんかが典型例ですよね。例えば「1」と言う数を受け取ってその「1」を画面に表示する。この場合、「1」自体が計算されて何かに変化するわけじゃありません。こう言うのを専門的には副作用って呼ぶんですが、出力命令はこの副作用の典型例なんです。
一方、「1」を受け取って「1」をどっか別のプログラムに受け渡す、場合、これは定義的には「計算を伴う」のです。この「受け渡した値」を専門的には「返り値」と呼びます。
また、このケースの場合、数学的には関数として
f(x) = x
ですから、確かに受け取った値の「1」は関数f(x)によって「1」と計算されているわけです。
上の二つの命題は、言い換えると、
- 関数:返り値を目的としたプログラム
- 手続き(プロシージャ):副作用を目的としたプログラム
となるんですが……。
慣用的な意味に於いては、「関数」も「手続き」も同じものなんです。別な言い方をすると、厳密に「関数」と「手続き」を区別した表記法を要請しているのは、知ってる限り「Pascalだけ」なのです(んで、正直な話を書くと、通大対策で「Pascalの入門書」に目を通した際、そんな分け方をしてる事を知ってぶっ飛びました・笑)。
一般的には、プログラミング言語に於いて、作成されたプログラムを「関数」と呼ぶか「手続き(あるいはプロシージャ)」と呼ぶかは、単純にその「プログラミング言語作成者」の好み、なんですよね(笑)。慣用的には、用語として「厳密な分け方をしている」ってわけでもないのです。
例えば、C言語では「どんなプログラムを書いても」、関数、と呼びます。これはC言語の作成者であるリッチー博士が「関数と呼びたかった」んでしょう(笑)。
ここで紹介した動的型付け言語でもそれぞれ特有の呼び方をします。
- 「関数」と呼ぶ言語
- Python
- R
- 「手続き」(プロシージャ)と呼ぶ言語
- Scheme
Schemeなんか結構デタラメで「関数型言語」のクセに「関数」とは呼ばずに「手続き」と呼んでいるのです(笑)。
注:ちなみに、「関数型言語」の「関数」とは、上の説明を見たら分かるでしょうが、「どんな処理をさせても必ず返り値を返す」事に由来します。
なお、通大「コンピュータ」のテキストのp.17辺りに「関数型言語」の解説が載っています。以下引用してみます。
関数型言語は、数学上の関数の考え方を使ったプログラミング言語です。すなわち、あるデータ(単数または複数)に作用させることによって他のデータを作り出す関数をいくつか用意しておき、これらの関数を組み合わせてプログラミングを行う言語です。したがって、純粋な関数型言語では、プログラミングはコンピュータの制御動作とは無縁になり、プログラマは関数の機能と関数・データの記述形式を知っていればよくなります。しかしながらプログラミング言語に持たせたいあらゆる機能を関数形式のみで表現することは難しい面もあり、また不自然でもあることから、純粋に関数型といえる言語はありませんが、APL、Lisp等は関数型に分類されます。
なお、これは90年代初頭に書かれた文章なんですが、21世紀の現代に於いては純粋に関数型といえる言語は存在します。→それどころか、実験的なんですけど、純粋に関数型といえる言語で書かれたこんなゲームまであります。
また、ここにはRubyが含まれてませんが、Rubyの場合は「関数」とも「手続き」(あるいはプロシージャ)とも呼ばずに「メソッド」と呼ぶ独自路線に走ってます(※)。
もうここまで来ると分かったでしょうが、「関数」と呼ぶか「手続き」と呼ぶか、は、慣用的に言うと、その言語設計者の「好み」なんです。わざわざ強調するような必要性も無い、って事なんです。
※:ちなみに、この「メソッド」は「オブジェクト指向プログラミング言語」の用語から来ていて、これが表すのはRubyは実は「オブジェクト指向プログラミング言語である」と言う事である(また、実はPythonにも「メソッド」がまた別に存在する)。
ただし、ここではオブジェクト指向は扱わないので、事実上「関数」=「手続き」=「メソッド」と単純に考えておいて間違いはない。これもやっぱり「どう呼ぶか?」は言語設計者の「好み」なのである。
しかも、実の事を言えば「オブジェクト指向とは何なのか?」厳密な定義、と言うのは存在していない。この辺もホントの事を言うと「プログラミング言語設計者」が「これはオブジェクト指向です」と言っちゃえばオブジェクト指向になる、のである。
このように、数学と違って、工学上の定義と言うのは「割にデタラメである」と言う事を肝に銘じる事。工学の流儀は「動けば良い」んで、数学者みたいに「厳密な定義」自体は好まないのである。
以下に関数と手続きの基本的な記述の仕方をみてみます。
関数 Max(x : 整数, y : 整数) : 整数型
- 局所変数 t : 整数型
{
- もし (x≦y) ならば t←y
さもなくば t←x- Max←t
}
これが、関数の書き方の例です。
先頭の"関数"が、このサブプログラムが関数であることを示しています。そして、Maxがその関数名になります。続く"{"と"}"で囲まれる部分に、仮引き数をそのデータ型と共に記述します。関数は必ず値を返しますから、そのデータ型を明示します。こうすることにより、変数のときと同様に関数名Maxを持つ箱をコンピュータの内部に確保します。これをもちいて、関数Maxを呼びだした他のプログラムに関数値を渡します。手続きでは、この部分は必要ありません。
さて、局所変数についての説明はいままでの中に存在しません。その名の通り、変数であることに間違いはありません。これはサブプログラムの独立性を高めるためのものです。上の場合、変数tは関数Maxを定めているこのサブプログラムのなかだけのもので、他のプログラムとはいっさいの関係を持たないことを意味します。もし、他のプログラムの中でも同じ名前の変数tがつかわれてたとしても、それはまったくの別物、別の箱となります。変数をコンピュータ内部における箱に例えたことを思い出してください。
関数を実際に定めている部分が、"{"と"}"で囲まれた部分です。関数では、必ず最後に返す値を関数名に代入します。この関数では局所変数tの値が関数名Maxに代入されています。このとき、局所変数tの値は仮引き数xとyの小さくないほうの値です。
以上から、ここで定義された関数Maxは、Max(5, 8)と呼び出されたとき、実引き数5と8のうち、小さくないほうの値8を返すことが分かります。
ええとまず、
関数では、必ず最後に返す値を関数名に代入します
ってのも大嘘ですね。と言うよりそれは「Pascalの流儀」でしょう。
全体的にこの説明は「Pascalと言う言語に於いての」説明であって、とても一般的とは思えないんですが……。
これらも実際、4つの動的言語使って、実際に書いたソースコードを提示した方が良いでしょう。
Pythonでの関数Max
# -*- coding: utf-8 -*-
def Max(x, y):
if x <= y:
t = y
else:
t = x
return t # ここがポイント
ます、Pythonでは関数を作る場合、明示的に
def
で定義し始めます。これで「Maxと言う関数を定義する」と言う意味になります。def
キーワードが書かれた行末にはコロン(:
)が必要で、これによりdef
が形成する「ブロック」が始まる、と言う事を表しています。この辺も実は制御構造と同じ文法を採用しているのがPythonの特徴です。後は、
def
キーワード以下は一段インデントを下げてコードを記述します。インデントがどう言う構造を表しているのか注目して下さい。そして、Pythonでは明示的に値を返す場合、
return
と言うキーワードを用います。これで「計算結果を返す」事を示すのです。なお、通大のテキストではいきなり「局所変数t」が登場していますが、かなりムリクリ、と言う感じです。上のPythonのコードでは通大の「疑似プログラム」に従って、「暗黙の宣言での」局所変数tを登場させましたが、実際問題、題意としては「入力された数x、yのうち、大きな数を返せ」ってだけなんで、次のコードの方がよりPythonらしい、でしょう。
# -*- coding: utf-8 -*-
def Max(x, y):
if x <= y:
return y
else:
return x
つまり、「受け取った二つの引数のうち大きい方を返せ」って書いた方がより短くって直接的ですよね。
なお、実はPythonにはデフォルトで
max
と言う組み込み関数がある事を付け加えておきます。Rubyでの関数(メソッド)Max
def Max x, y # ここはdef Max(x, y)と書いても良い
if x <= y
t = y
else
t = x
end
return t # ここがポイント
end
ます、Rubyでは関数を作る場合、明示的に
def
で定義し始めます。これで「Maxと言う関数を定義する」と言う意味になります。def
キーワードが書かれたブロックの終端ではend
が必要で、これによりdef
が形成する関数(メソッド)が終わる、と言う事を表しています。この辺も実は制御構造と同じ文法を採用しているのがRubyの特徴です。そして、Rubyでも明示的に値を返す場合、
return
と言うキーワードを用います。これで「計算結果を返す」事を示すのです(ただし、Rubyの場合、かなり設計が優秀で、かつ「関数型言語の影響が色濃い」んで、return
は省略可能です)。なお、通大のテキストではいきなり「局所変数t」が登場していますが、かなりムリクリ、と言う感じです。上のRubyのコードでは通大の「疑似プログラム」に従って、「暗黙の宣言での」局所変数tを登場させましたが、実際問題、題意としては「入力された数x、yのうち、大きな数を返せ」ってだけなんで、次のコードの方がよりRubyらしい、でしょう。
def Max x, y # ここはdef Max(x, y)と書いても良い
if x <= y
return y
else
return x
end
end
つまり、「受け取った二つの引数のうち大きい方を返せ」って書いた方がより短くって直接的ですよね。
Schemeでの関数(手続き)Max
(define (Max x y)
(let ((t 0)) ;tを0で初期化する
(if (<= x y)
(set! t y)
(set! t x))
t)) ;最後にtを返す
ます、Schemeでは関数(手続き)を作る場合、明示的に
define
で定義し始めます。これで「Maxと言う関数を定義する」と言う意味になります。そして、Schemeは関数型言語なので、どんな場合だろうと何らかの値は必ず返します。従って
return
等の特殊なキーワードは必要ありません。なお、通大のテキストではいきなり「局所変数t」が登場していて、上のコードではかなりムリクリ、通大のテキストに合わせています。そして局所変数tを宣言するにあたって一応tの中身を0で初期化しています。
(通常、初期化せずに局所変数tを宣言すると、初期値がどうなるかは未定義だから、です。別に0にする必要も無いのですが。)
また、
set!
を使って代入してますが、通常Schemeではset!
は使いません。もっと言っちゃうと「代入」なんてしないのです。従って、次のコードの方がよりSchemeらしい、でしょう。
(define (Max x y)
(if (<= x y)
y
x))
つまり、「受け取った二つの引数のうち大きい方を返せ」って書いた方がより短くって直接的ですよね。また、極めて短いコードが書ける事が分かります(タイピング量は少なければ少ない程良い、のです)。Schemeの数学性の成せる技、です。
なお、実はSchemeにはデフォルトで
max
と言う組み込み手続きがある事を付け加えておきます。Rでの関数Max
Max <- function(x, y) {
t <- 0 # tを0で初期化する。
if (x <= y) {
t <- y
}
else {
t <- x
}
return(t) # ここがポイント
}
ます、Rでは関数を作る場合、
関数名 <- function(仮引数) {
と言う形で書き始めます。これで「Maxと言う関数を定義する」と言う意味になります。関数名 <- function(仮引数) {
で書き始めて最後は"}
"で閉じます。この辺もC言語の影響が見て取れます。そして、Rでは明示的に値を返す場合、
return
と言う関数を用います。これで「計算結果を返す」事を示すのです。なお、通大のテキストではいきなり「局所変数t」が登場していますが、かなりムリクリ、と言う感じです。上のRのコードでは通大の「疑似プログラム」に従って局所変数tを登場させ、局所変数tを0で初期化しましたが、実際問題、題意としては「入力された数x、yのうち、大きな数を返せ」ってだけなんで、次のコードの方がよりRらしい、でしょう。
Max <- function(x, y) {
if (x <= y) {
return (y)
}
else {
return (x)
}
}
つまり、「受け取った二つの引数のうち大きい方を返せ」って書いた方がより短くって直接的ですよね。
なお、実はRにはデフォルトで
max
と言う組み込み関数がある事を付け加えておきます。
手続きの書き方の例を次に見ることにします。
手続き 3平方(a : 実数, b : 実数, c : 実数,
OK : 論理型変数)
- 局所変数 t : 実数型
{
- t ← (aの2乗) + (bの2乗)
- もし t = (cの2乗)
ならば OK←真
さもなくば OK←偽
}
これは3つの実数a、b、cが与えられたとき、次の関係式
$$a^2 + b^2 = c^2$$
が成立するかどうか判定する手続きです。成立するときは論理型変数OKが真になり、成立しないときは論理型変数OKは偽になります。
先頭の"手続き"はこのサブプログラムが手続きであることを示し、続いてその名が"3平方"であることを示します。"{"と"}"で囲まれた部分に、仮引き数を書きます。ここで、OKだけが論理型「変数」になっているのに注意してください。
一般に、手続きおよび関数において、他のプログラムからの情報は、"変数"がつかない仮引き数をとおして値だけが持ち込まれます。また、他のプログラムへの情報提供や結果報告は、"変数"がつく仮引き数をとおして行われます。"{"と"}"で囲まれた部分が、実際の仕事を記述するところです。この場合なにをしているかは、すぐに理解できることと思います。
手続きは、関数と異なり式の中で呼び出されることはありません。一つの命令文として呼び出されます。
ここで、2つの実数の間に等号関係が成立するか調べています。これは、数学的には正しいのですが、プログラミング上は好ましくありません。なぜならば、コンピュータでは真に実数全体を扱えず計算は近似で行われるため、その誤差を常に考慮しなくてはならないからです。そこで、実数型の計算をしているときの判断は不等号を用いて行うようにします。
ここは既に工学的な意味として考えても一般的な「手続き」(プロシージャ)としての解説としては破綻していますね。あくまで「Pascalでの」手続きの解説です。
まず、通大用語での「疑似コード」を見ても、原理的には真偽値が「返り値」になっていると思います。従って、ここで提示された疑似コードは「関数」なんです。
通大のテキストのスタンスとして「数値を返せば」関数で、「論理型を返せば」手続きだ、とでも言うのでしょうか?おかしいでしょう。論理値なんかもメモリ上では真=1、偽=0、と表現されているでしょうから、結果「数値として」返ってるんです。
これは副作用、を基準とした「手続き」の例示としては大嘘ですね。大体、プログラミング言語によっては「論理値」でデータを手渡したりしてるんで、要するにこの辺は「言語の設計次第」なんです。
もうこの辺になると、完全に「Pascalと言うプログラミング言語の説明」を実のトコ行っていて、「PascalではOK」なんですが、他の言語では「全然ダメな」解説ばっかになっています。ですから、「特定のプログラミング言語に限定することなく」ってのは完全に破綻していますね。だから最初に書いた通り、こんな無茶な解説するんだったら初めから「Pascalで解説」するべきなのです。そして通大生には十進BASICなんか配布せず、(Windowsでの入手は難しいので)「ソースからビルドした」Pascalを配布するべき、なのです(あるいはKNOPPIX/Mathを配布する、とかね)。
Pythonでの手続き(関数)の例
既に解説した通り、純粋な意味では通大テキストでの「手続き」の例は破綻しています。
取り合えず、3平方の「疑似プログラム」に従って「真偽値を返す」関数をPythonで記述してみましょう。
# -*- coding: utf-8 -*-
def Pythagorean(a, b, c):
t = a ** 2 + b ** 2
if t == c ** 2:
return True
else:
return False
こんな感じでしょうか。OKって論理型引数が無いですけど、通常そんなものはいらないでしょう。そして、実際
True
/False
と言う論理値が「返り値」になってるんで、実質上、これは(工学的定義に於いての)関数です。なお、通大の「疑似プログラム例」に合わせて適合するように上のコードを記述しましたが、論理値を返すだけ、だったら次のようにもっと短く記述する事が可能です。
# -*- coding: utf-8 -*-
def Pythagorean(a, b, c):
return a ** 2 + b ** 2 == c ** 2
これが成り立つのは、そもそも論理演算子(ここでは
==
)が元々論理値(True
/False
)を返すから、です。少なくとも引数が整数の限り、上の関数で十分目的は果たしています(通大のテキストが言う通り、一般に引数が「実数」だったら成り立ちませんが)。
Rubyでの手続き(メソッド)の例
既に解説した通り、純粋な意味では通大テキストでの「手続き」の例は破綻しています。
取り合えず、3平方の「疑似プログラム」に従って「真偽値を返す」メソッドをRubyで記述してみましょう。
def Pythagorean a, b, c
t = a ** 2 + b ** 2
if t == c ** 2
return true
else
return false
end
end
こんな感じでしょうか。OKって論理型引数が無いですけど、通常そんなものはいらないでしょう。そして、実際
true
/false
と言う論理値が「返り値」になってるんで、実質上、これは(工学的定義に於いての)関数です。なお、通大の「疑似プログラム例」に合わせて適合するように上のコードを記述しましたが、論理値を返すだけ、だったら次のようにもっと短く記述する事が可能です。
def Pythagorean a, b, c
return a ** 2 + b ** 2 == c ** 2
end
これが成り立つのは、そもそも論理演算子(ここでは
==
)が元々論理値(true
/false
)を返すから、です。少なくとも引数が整数の限り、上の関数で十分目的は果たしています(通大のテキストが言う通り、一般に引数が「実数」だったら成り立ちませんが)。
Schemeでの手続きの例
既に解説した通り、純粋な意味では通大テキストでの「手続き」の例は破綻しています。
取り合えず、3平方の「疑似プログラム」に従って「真偽値を返す」手続きをSchemeで記述してみましょう。
(define (Pythagorean a b c)
(let ((t (+ (expt a 2) (expt b 2))))
(if (= t (expt c 2))
#t
#f)))
こんな感じでしょうか。OKって論理型引数が無いですけど、通常そんなものはいらないでしょう。そして、実際
#t
/#f
と言う論理値が「返り値」になってるんで、実質上、これは(工学的定義に於いての)関数です。なお、通大の「疑似プログラム例」に合わせて適合するように上のコードを記述しましたが、論理値を返すだけ、だったら次のようにもっと短く記述する事が可能です。
(define (Pythagorean a b c)
(= (+ (expt a 2) (expt b 2)) (expt c 2)))
これが成り立つのは、そもそも論理演算子(ここでは
=
)が元々論理値(#t
/#f
)を返すから、です。少なくとも引数が整数の限り、上の関数で十分目的は果たしています(通大のテキストが言う通り、一般に引数が「実数」だったら成り立ちませんが)。
Rでの手続き(関数)の例
既に解説した通り、純粋な意味では通大テキストでの「手続き」の例は破綻しています。
取り合えず、3平方の「疑似プログラム」に従って「真偽値を返す」関数をRで記述してみましょう。
Pythagorean <- function(a, b, c) {
t = a ^ 2 + b ^ 2
if (t == c ^ 2) {
return(TRUE)
}
else {
return(FALSE) } }
こんな感じでしょうか。OKって論理型引数が無いですけど、通常そんなものはいらないでしょう。そして、実際
TRUE
/FALSE
と言う論理値が「返り値」になってるんで、実質上、これは(工学的定義に於いての)関数です。なお、通大の「疑似プログラム例」に合わせて適合するように上のコードを記述しましたが、論理値を返すだけ、だったら次のようにもっと短く記述する事が可能です。
Pythagorean <- function(a, b, c) {
return(a ^ 2 + b ^ 2 == c ^ 2) }
これが成り立つのは、そもそも論理演算子(ここでは
==
)が元々論理値(TRUE
/FALSE
)を返すから、です。少なくとも引数が整数の限り、上の関数で十分目的は果たしています(通大のテキストが言う通り、一般に引数が「実数」だったら成り立ちませんが)。
いままでは、関数や手続きを実際にプログラミングするときに必要となる知識を含めて説明してきました。ここで一番大事なのは、その関数はいかなる値をかえすのか、または、その手続きはいかなる仕事をするのかということです。すなわち、サブプログラムの仕様を正確に把握することです。詳細な実行部分を書くのが後になる場合でも、それを完全に理解していなければ関数や手続きを使う意味がありません。
<練習問題>
2次方程式の係数が与えられたときの判別式を計算する関数を書き、仮引き数を明示しなさい。
<回答例>
# -*- coding: utf-8 -*-
# Pythonの場合
def Discriminant(a, b, c) :
return b ** 2 - 4 * a * c
# Rubyの場合
def Discriminant a, b, c
return b ** 2 - 4 * a * c
end
;; Schemeの場合
(define (Discriminant a b c)
(- (expt b 2) (* 4 a c)))
## Rの場合
Discriminant <- function(a, b, c) {
return(b ^ 2 - 4 * a * c)
}
0 コメント:
コメントを投稿