2016/10/01 13:00- compiler seminer [前回] 関数電卓のプログラムを bison/flex を利用して作っていた 基本構造はできていた 拡張 : 組み込みの関数や、演算子を追加する事もできる 変数と代入も使えていた [今回] 「ユーザー定義関数」を追加したい 「ユーザー定義関数」: 関数「定義」なので、「プログラミング言語」らしくなる !! プログラムを作る !! 「再利用」したい : 一度目(は大変だったかもしれないが、 それができたなら、そ)の事を二度目は、簡単に済ませたい !! 変数、代入 : 一度、計算した結果を(変数にとっておいて、そこに記録されているデータを取り出す事によって)再利用する仕組み !! 今の「手続型言語」は、「変数に値を代入しながら計算を進める」形 !! 同じ値を再利用するので、(実行)効率が良い !! プログラマは、どこ(変数)になに(計算結)があるかを意識している必要がある !! 値の間には、依存関係がある !! 依存関係を積極的にプログラマが、操作の形で記述する必要がある !! -> 依存関係を、操作に変換する作業が、「手続型言語のプログラミング」作業 !! 「手続記述」=「依存関係」+「一般常識」から、「自動的に出て来る(からこそ、それをプログラマができる)」内容 !! 「自動的にできるものを人間がやる」ことの問題点 !! 計算機に遣らせればよい(AI へ...) !! 「手続(How To)」が「(ある種の計算)結果」である !! => 「情報落ち」がある !! その結果を得るための、材料や、その原理に関する情報が欠落している !! 結果の再利用はできる。 !! 原理(What is)の再利用はできなくなってしまう 変数は(計算結果となる)値の再利用(復習) 値は「コピー」が簡単なので、そのため(再利用)の仕組みが不要 -> 簡単に実現できた a = 1 + 2 ! 代入文 # 変数「a」に、式「1+2」の計算結果の値である「3」を代入 # -> 変数名「a」と値「3」の対応関係が生れる # -> 「実装」としては、「対応表」があればよい # 変数名:値 a * 3 ! 変数参照 # 変数「a」の値が再利用される # -> 変数名「a」に対応付けられている値「3」をコピーして取り出す # -> 「実装」としては、「対応表」から「a」をみつけ、それに相当する値「3」を取りだせばよい ユーザー定義関数は、式(計算方法)の再利用 式(と、それを計算するプロセス)を「コピー」できれば良い 例 : 関数定義 : add(x,y) := x + y 関数利用 : add(3,5) -> 3 + 5 -> 8 ^^ 式 ^^ 計算 !! 既に「計算」部分は出来ている !! 「式」の再利用をどうするか ? !! (安易な案) : 文字列を再利用する !! -> 強力な機能( -> リフレクション) !! -> 効率が悪い !! -> 実行時に「言語処理系」が必要 !! -> インタプリターは可能 !! -> コンパイラは無理 !! (良く利用される案) : 中間言語を利用する !! -> 中間言語として、「構文木」を使う !! -> 「構文木」の生成は、今 .y が、そのまま利用できる <設計> !! 本当は、汎用な「構文木」の話の方が面白い/役立つ !! そうすると、その分、実装が大変だし、複雑になる !! !!! 仕事の時には、十分に考える必要がある !! !!! -> 教育の場では、サボってしまってよい !! -> 今回、再利用するのは、「式」の部分だけなので、 !! 「式」の「構文木」だけを考える !! -> この様な「ショートカット(場当たり的なご都合主義)」 !! は、結果的に後に禍根を残す事になる !! -> 教育の場では、「失敗や誤りの経験」も意味がある 「式」の「構文木」だけを考える C 言語に於ける macro と関数の違い !! 全然違う !! 例1: #define add(X,Y) X+Y int add(int X, int Y) { return X+Y; } 例2: #define ifAB(V,X,Y) {if(V){return X;}else{return Y;}} int ifAB(V,X,Y) {if(V){return X;}else{return Y;}} 処理系が異る 「macro 関数」を、処理するのは C Preprocesser (前処理) 「関数」を、処理するは、C Compiler 処理対象の違い 「macro 関数」が、「文字列(token)」として対象(引数)を扱う 「関数」が、(実行時に)でデータ(数値)として対象(引数)を扱う 例 1: a = add(1,x)*3; macro : a = add(1,x)*3; -> a = (1+x)*3 -> 実行時に計算 関数 : a = add(1,x)*3; 実行時に add(1,x) が計算される b = add(1.5,y)*3.2; macro : b = add(1.5,y)*3.2 -> b = (1.5+y)*3.2 -> 実行時に計算 関数 : b = add(1.5,y)*3.2; 型があわないのでエラーになってしまうか、 add は整数の引数なので、1+y の計算をしてしまう (結果的に)評価の順序が異る 「macro 関数」は外から内 -> 安全に計算できるが、遅い 「関数」は内から外 -> 危険な事もあるが、速い 例 2: ifAB(1,1,1/0) macro: ifAB(1,1,1/0) -> {if(1){return 1;}else{return 1/0;}} -> 実行時に、if (1) なので、return 1; が実行される 関数 ifAB(1,1,1/0) -> 実行時に、1/0 の所でエラーになる 例 3: #define twotimes(X) (X+X) int twotimes(int X) { return X+X; } towtimes(/*ものすごく計算に時間がかかる式*/) macro : /*ものすごく計算に時間がかかる式*/ + /*ものすごく計算に時間がかかる式*/ -> ものすごく計算に時間がかかる式を二回計算 関数 : まず、 ものすごく計算に時間がかかる式を一度計算し、 引数 X にいれて、 本体では、「値を再利用する」ので、一度の計算で済む