• ベストアンサー

手続き型と関数型について。

手続き型言語の定義は、「記述された命令を逐次的に実行し、処理の結果に応じて変数の内容を変化させていくプログラミング言語」となっていて、関数型言語の定義は、「数学的な言語仕様をもつプログラミング言語のこと。一度値を与えられた変数は常にその値を維持し、計算は計算結果を引数とした関数呼び出しの繰り返しとして行われる。」とあります。 関数型の、「一度値を与えられた変数は常にその値を維持し」はどういう意味ですか? 例えば、a=2とした後に、a=3などとすればaの値は変わっているのですが。 簡単な例で説明してください。

質問者が選んだベストアンサー

  • ベストアンサー
  • chirubou
  • ベストアンサー率37% (189/502)
回答No.5

すいません、再度 No.3 です。説明が悪過ぎました。「入れ替える」という部分は忘れてください。 では、No.4 の例をもう少し進めましょう。 int poo( void ) { psum( 5 ); /* 代入 */ psum( 2 ); /* 5+2 */ pmul( 10 ); /* ( 5+2)*10 * return( x ); } int foo( void ) { return( fmul( fsum( 5, 2 ), 10 ) ); } というされに2つの関数を考えます。 printf( "ans=%d?n", poo() ); printf( "ans=%d?n", poo() ); とすると、最初の2回目の答えは当然違ってきますよね?同じにするためには、2回目の poo() を呼ぶ前に x=0; としなければなりません。ところが、 printf( "ans=%d?n", foo() ); の場合は何度やっても同じ答えになります。つまり、手続き型の場合(途中)結果を保存しているがために、結果がおかしくなることがる、ということを示したのです。 純粋な関数型言語では手続き型のように計算の(途中)結果を覚えておくことができないので、引数が同じであれば、常に同じ結果となりますが、手続き型の場合は、プログラム次第で(意図的に、あるいは意図に反して)どうにでもなってしまいます。逆に言えば、手続き型では(途中)結果を変数に保存することで計算を省略することができ、素早く結果を出す事が可能ですが、関数型の場合、それはできないので効率が悪い(それだけが効率が悪い原因の全てではないですが)という訳です。 関数型は数式のような「美しさ」があり、手続き型に比べバグが生じ難いという利点があるので、一部の人たちから大変支持されています。これ以上は他の回答者さんの参考 URL 等で勉強してください。

tatany
質問者

お礼

いろいろと分かりやすい例をありがとうございました!! もやもやしたものが少しハッキリしてきました。 本当に感謝しています。

その他の回答 (4)

  • chirubou
  • ベストアンサー率37% (189/502)
回答No.4

再度 No.3 です。「デバッグがしやすくなる」というのが気になって。 別の例を見てみましょう。 int x = 0; int psum( int y ) { x += y; } int pmul( int z ) { x *= z: } という手続きに対し、以下の関数があったとします。 int fsum( int x, int y ) { return( x * y ); } int fmul( int x, int y ) { return( x * y ); } ここで、( 5 + 2 ) * 10 を計算するとすると、 psum( 5 ); /* 代入 */ psum( 2 ); /* 5+2 */ pmul( 10 ); /* ( 5+2)*10 */ という手続きに対し、関数型では、 fmul( fsum( 5, 2 ), 10 ); という記述になります。手続き型の場合、psum() や pmul() の順序を変更できてしまいます(もちろん、こうすると望みの動作にならない場合があります)が、関数型の場合、順序は変えようがありません。私はこれが関数型言語の一番の効用だと思います。 もっと複雑なプログラムになった時、手続き型の関数の深いところで大域変数の値を変更してしまい、それがもとで思わぬ結果になる(バグ)ということはよくある事です。しかしながら、純粋に関数型で記述すると、自然とそういうことは起きないようにプログラムできる訳です。

tatany
質問者

お礼

補足をありがとうございます。 一つ分からないのが、「手続き型の場合、psum() や pmul() の順序を変更できてしまいますが、関数型の場合、順序は変えようがありません。」です。 順序を変更するというのは具体的にどういうことですか? (5+2)*10なら()内を先に計算しなければならないので、psum(5)とpsum(2)は入れ替えることができますが、pmulは入れ替えることはできないと思います。 fmul( fsum( 5, 2 ), 10)はfmul( fsum( 2,5 ), 10)に変更できると思うのですが、こういう意味ではないのですか?

  • chirubou
  • ベストアンサー率37% (189/502)
回答No.3

まず最初に「関数型」というのは精神論だと思ってください。実用的な言語の大半は、たとえそれが関数型言語と言われていても、純粋な関数という考え方「だけ」でプログラムできるようになっていません(理由は後述)。 例を挙げましょう。1から10までの整数の和を求めるという場合、手続き型の場合(以下 C 言語風に記述)、 x = 0; for( i=0; i<=10; i++ ) x += i; となります。これを関数型風に書くと、 int sum10( int x ) { if( x > 0 ) return( x + sum10( x -1 ) ); return( x ); } という具合になります。関数の中で自分自身を呼び出す事を再帰といいますが、こうすることで上の for() 文の変数 i を変化させることなく、繰り返しを実現できます。 しかしながら、全ての計算を純粋な関数型言語で行うことは、非常に実行効率が悪くなり、実用的ではなくなります。このため、世の中の多くの関数型言語では、実際には変数の(2度目、3度目の)書き換えを許しています。

tatany
質問者

お礼

ありがとうございます。 分かりやすい具体例でよく理解できました! この例ではiの値をいちいち追う必要がなくなるからデバッグがしやすくなるということがメリットになってるんですね。

  • sakusaker7
  • ベストアンサー率62% (800/1280)
回答No.2

関数型言語の厳密な定義から言うと、「代入」(破壊的代入)という操作そのものが存在しません。ですから > a=2とした後に、a=3などとすればaの値は変わっているのですが。 ということ自体ができません。突き詰めていくと「変数」自体なくなっちゃいます(すべてが関数の戻り値になる)。 ただ、「関数型言語」に分類される言語でもいわゆる代入(破壊的代入)を 行うことのできる言語はあります(Lisp, Schemeなどなど)。ですから、 積極的に変数に値の代入を行うことなく関数の呼び出しの組み合わせでプログラミングできる言語が関数型言語であるという考え方でもいいかもしれません。 興味があれば「ラムダ計算」とかいったキーワードで探してみてください。 参考URL: 関数型言語 - Wikipedia http://ja.wikipedia.org/wiki/%E9%96%A2%E6%95%B0%E5%9E%8B%E8%A8%80%E8%AA%9E ラムダ計算 - Wikipedia http://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%A0%E3%83%80%E8%A8%88%E7%AE%97 なぜ関数プログラミングは重要か http://www.sampou.org/haskell/article/whyfp.html

tatany
質問者

お礼

ありがとうございます。 参考ページを見て勉強してみます。 ご親切に感謝します。

  • ymmasayan
  • ベストアンサー率30% (2593/8599)
回答No.1

関数型言語の中で引数の値は変更してはいけないし、 もし変更してもそれは反映されないと言うことです。 C言語は関数型言語ではありませんが関数型プログラミングが可能です。 例えば(ポインターを使わずに)a=2を引数として関数xを呼んだとき xの中でa=3を実行した場合、xの中ではこれは有効ですが returnするとa=2に戻ります。 この方式はcall by value(値呼び出し)と言われ呼び出し元がaはそのままにして aのコピーを作ってxに渡すのです。 関数にミスがあったとき大事なデータを壊されないためです。 > 一度値を与えられた変数は常にその値を維持し、 > 計算は計算結果を引数とした関数呼び出しの繰り返しとして行われる。 上記のことを言っていると思います。

tatany
質問者

お礼

分かりやすいご説明ありがとうございました。 よく理解できました。

関連するQ&A

  • 関数の仮引数は宣言か式か

    ”関数の仮引数の宣言”は”変数の宣言・定義”と同じように”宣言”と明確に考えるべきなのか”関数の仮引数”を”式”と考えて良いのかという問題です。 私が迷ってしまったのは"配列を仮引数"にとった例です。 規則として ”int a[ ] が int *a と同じ意味になるのは、唯一、関数の仮引数の宣言のばあいだけである”という規則がありますが、これは”関数の仮引数の宣言”は変数の宣言・定義”と同じように”宣言”と明確に考えている例だとおもいます。 int a[ ] が int *a と型名 変数名(引数名)と宣言の形をとっているので当然だと思いますが、一方 配列は、式の中で「先頭へのポインタ」によみかえられる。               ↓ 関数の引数は式なので、配列は「先頭へのポインタ」に読み変えられる←引数部分を”宣言”ではなく”式”と捉えてる←ここが私の迷っているところ               ↓ よって、関数に渡ってくるのは、結局はつねにポインタだ。 という説明もあります。 私の今までの理解ではc言語では”宣言の部分”と”式”の部分は明確に区別されるものと考えていました。”宣言部分の初期化の="と”代入の=”とは明確に区別されていました。 それと同じように”関数の仮引数の宣言”は”宣言部分”と捕らえるのか”式”ととらえるのか ”宣言”と”式”が私の頭の中混乱しています。 宜しくお願いします。

  • 関数の呼び出しかた

    こんばんは。 最近javascriptを始めたのですが一つどうしても 分からない事があり質問させて頂きました。 ユーザー関数の呼び出しの方法なのですが、 入門書で勉強中に関数の呼び出しは、たとえば msgtest関数を定義していたとして  msgtest(); として呼び出すのが基本だと考えたのですが ウィンドウを常に前面に出すサンプルスクリプト 中、window.onblur = msgtest; という風に引数のカッコなしで呼び出しており、 カッコをつけると動作しないという事が 起こりました。理由がわからず困っています。 何故この際引数のカッコをつけてはいけないのでしょうか?

  • 関数と変数と定数と式をまとめて [C言語]

    はじめまして。私はC言語初心者です。 C言語では、関数の引数や変数への代入や配列の添え字などに、変数や定数や、式や関数の値を利用できるじゃないですか。私は今C言語について勉強したことをファイルにまとめていっているのですが、上記のようなことを説明するたびに「関数と変数と定数と式」と書くと長ったらしくなってしまいます。そこで、これらをまとめていう言葉があったら教えて欲しいのです。 回答よろしくお願いします。

  • エクセルの関数はプログラミングの何言語なんですか?

    エクセルの関数はプログラミングの何言語なんですか? =sum(b3:b5) 例えばこの場合、sumは関数で(b3:b5)は引数ですか? :コロンはなんですか? =は関数の定義ですか?

  • C言語のポインタによる関数の引数の書き方について教えてください。

    C言語を今勉強中の大学生です。 一気に複数の値をmainに返せる、参照による呼び出しによる関数の引数の書き方で困っています。配列を自作関数に引き渡したいのですが、どう書けばいいのでしょうか。 下の3つは、それぞれ(1)mainからの呼び出し、(2)自作関数での引数の引き受け、です。どこをどう変えたらエラーが出なくなるのか、分かる方、お願いいたします! (1)school(&m,h); (2)void school(int *m, float *h){ ※ちなみに変数は int m,float h[10][10]

  • 引数のある関数と引数のない関数の設定

     お世話になっております。 PHPのプログラミングで関数をつくる際に、引数を渡す関数と引数がない関数がありますが、すでに定義済みの関数のように、引数がある場合と引数がない場合の両方を取り扱うにはどうしたらよいでしょうか? function example ($a) { if ($a == 0 || $a == '') { print '引数なし'; } else { print $a; } } のような関数で、 example(); という感じで引数を入れないと、 Warning: Missing argument … というエラーがでてしまいます。関数を example(''); と呼び出すと大丈夫なのですが。。。 プログラマーにとって基本的なことだと思うのですが、調べてもなかなか出てこないので質問させて頂きました。どなたかわかる方がいたらしたらおしえてください。 よろしくお願いします! }

    • ベストアンサー
    • PHP
  • VC++ 再帰呼び出しについて

    VC++6.0にてプログラミングを行っているものですが、 関数の再帰呼び出しについて質問です。 再帰呼び出しの際にスタックに積まれる変数というのは、 再帰呼び出しをする関数に渡す引数のことですか? スタックオーバーフローを起こさないために、 staticなポインタにHeap領域上の 変数を割り当てるとよい。 と分かったのですが、 この意味は、例えば static int *a = new int; ということなのですか?

  • C言語の行列を参照する方法

    今C言語でプログラミングを書いてるのですが、関数を自作した時に、行列を引数として参照する方法が分からないのでご教授していただきたいです。 main文内でdouble x[NUM][3]と、xx[NUM][3]という変数を定義して、 xx[NUM][3]を計算した後に、それぞれの計算結果をx[NUM][3]に入れるという作業をしています。 そこで、新しくtestという関数を作って、その中でそれぞれ計算したxx[NUM][3]をx[NUM][3]に入れるという作業を行いたいのですが、行列式を引数として参照する方法が分かりません。 ちなみに、NUMはdefineで100と定義しており、xx[NUM][3]はmain文内でforを100回まわして計算しています。

  • メンバー関数ポインタ

    非常に基礎的なことで申し訳ないですが。 クラスのメンバー関数へのポインタ変数へ 代入しようとすると 関数呼び出しには引数リストがありません。 とエラーがでます。 何がわるいでしょうか? 以下のような感じのコードです。 void (classname::*P_func)() = classname::func; 定義しただけと思いますが。。 VCです。 よろしくお願いします。

  • (C,C++言語)関数の引数は自動キャストされる?

    プログラミング言語C,C++の数値計算に関する質問です. 整数データ変数同士のみの計算結果は小数点以下は切り捨てられますよね. もし実数型で計結果を得たいときは,int変数を(double)や(float)でキャストしてあげなければならないことは知っています. ここで,仮引数リストにdouble型変数が設定されている関数の引数にint型変数を与えた時,関数の呼び出し時にキャストしなくても自動でキャストされるのでしょうか. 以下のサンプルコードを作成し,実行してみた結果を次に示します. ---サンプル--- #include <iostream> using namespace std; void printDouble(double val, double val2) { cout << "(double)val = " << val / val2 << endl; } int main(void) { for (int i = 1; i < 5; i++) { cout << i / (i * 2) << endl; printDouble(i,i*2); } } -----実行結果---- 0 (double)val = 0.5 0 (double)val = 0.5 0 (double)val = 0.5 0 (double)val = 0.5 ------------------------- 関数の外での計算は整数に丸められてしまうので i / ( i * 2 ) 計算結果はゼロに,関数の中では与えられた i はdouble型として扱われるので小数点以下の値も残っているということですよね. この場合,printDouble関数へ整数を与えるときには printDouble( (double)i , (double)( i * 2 ) ); といったようにわざわざキャストしなくても自動で仮引数の型でキャストされて関数が呼び出されると理解してもよいのでしょうか. 実行結果から明らかだろ,思われてしまうかもしれませんが,何かの参考書に記述されているのを見たり,人からそう教わったわけではなく,また関数のオーバーロードのこともありますので,質問させて頂きました.よろしくお願いいたします.

専門家に質問してみよう