2. 関数を使ったプログラムの 作成と実行 プログラミング論I
本日の内容 (前回) Scheme の式と関数の書き方の基礎とその評価結果に至る過程、およびエラー 関数を使ったプログラムの作成法の基礎: 複数の関数から構成する目的や利点など 設計レシピ :プログラム開発のガイドライン
複数の関数を使ったプログラム
仕事の分割 上司 担当者B 担当者A この仕事には△△と○○の結果が必要だ… ○○の仕事を 頼む! △△の仕事を 頼む! ○○の結果 △△の結果 ○○の開始に 必要なデータ △△の開始に 必要なデータ 担当者B 担当者A
プログラムの分割と関数 例えば関数定義に関して: 主関数(Main Function): 補助関数(Auxiliary Function): 通常プログラムは一つだけでなく数多くの定義からなる 例えば関数定義に関して: 主関数(Main Function): 本当に必要な最終結果を生成する関数 補助関数(Auxiliary Function): より容易に解決できるよう分割した部分問題を解く関数 (この部分問題の解を使い,最終結果を求める) 可読性(思考過程の再現性)や再利用性を高める ※プログラムによっては,ある主関数が別プログラムの補助関数になることもあるし, その逆もある 複雑で大きなプログラムに補助関数の使用は必須
プログラムは,しばしば,複数の関数に「分割」される プログラムの分割と関数 Bの処理を頼む! (主)関数A Cの処理を頼む! 結果 結果 呼び出す側 必要なデータ (補助)関数C 必要なデータ (補助)関数B 呼び出される側 呼び出される側 プログラムは,しばしば,複数の関数に「分割」される
例題1. 2乗の和 a と b とから,a2+b2 を求めるプログラムを関数 sum-of-squares として作成し実行 例題1. 2乗の和 a と b とから,a2+b2 を求めるプログラムを関数 sum-of-squares として作成し実行 手順を分けて考える まず2乗について:ある値 x から x2 を求める補助関数 square を考える 主関数では、補助関数 square を2回使い 2乗の値を2つ求め、それらを足す
関数squareの入力と出力 xの値: 4 結果: 16 square 入力は 1つの数値 出力は 1つの数値
関数 square の定義 (define (square x) (* x x)) 1つの関数定義 この関数の名前 を計算(出力) 「(関数の)定義である」 ことを示すキーワード この関数の名前 (define (square x) (* x x)) 1つの関数定義 x の値から (* x x) を計算(出力) 値を1つ受け取る(入力)
関数sum-of-squaresの入力と出力 x, y の値: 2, 4 結果: 20 sum-of-squares 入力は 2つの数値 出力は 1つの数値
関数sum-of-squaresの定義 1つの 関数定義 この関数の名前 (define (sum-of-squares a b) 「(関数の)定義である」 ことを示すキーワード この関数の名前 (define (sum-of-squares a b) (+ (square a) (square b))) 1つの 関数定義 値を2つ受け取る(入力) 「a2 + b2 」 を計算(結果として出力)
2乗の和のプログラム a2+b2 を求める (define (square x) (* x x)) (define (sum-of-squares a b) (+ (square a) (square b))) sum-of-squaresの定義 注: 引数名は各関数内のみ有効。例えば右のsum-of-squares内 の x と square内の x は字面は同じだがそれぞれの関数定義に閉じた異なる変数。 (define (square x) (* x x)) (define (sum-of-squares x y) (+ (square x) (square y)))
関数の間の関係 主関数 sum-of-squares 補助関数 square (define (sum-of-squares a b) (+ (square a) (square b))) 補助関数 square (define (square x) (* x x)) sum-of-squares 関数の中で, square 関数を使っている(2箇所)
データの流れ 主関数 sum-of-squares square関数 (define (sum-of-squares a b) (+ (square a) (square b))) 同様に①(渡すのは yの値), ②, ③を行う ④ これらの結果を使い最終結果を計算 square関数 (define (square x) (* x x)) ① square関数に値aを渡し,aの2乗の計算を依頼 ② 変数xを渡された値aで 置換えて本体式a2を計算 ③実行結果を呼び出しもとの sum-of-squares関数に返す
関数の分割に関して 分割しない例 分割する例 どちらのsum-of-squares も機能的には同一 可読性(思考過程の明示) (define (sum-of-squares x y) (+ (* x x) (* y y))) どちらのsum-of-squares も機能的には同一 分割する例 (define (square x) (* x x)) (define (sum-of-squares a b) (+ (square a) (square b))) 可読性(思考過程の明示) 再利用性などを考慮し、 分割するかを判断
(sum-of-squares 20 30)から1300が得られる過程 最初の式 (sum-of-squares 20 30) = (+ (square 20) (square 30)) = (+ (* 20 20) (square 30)) = (+ 400 (square 30)) = (+ 400 (* 30 30)) = (+ 400 900) = 1300 (sum-of-squares a b) の本体 (+ (square a) (square b)) を呼び出しa=20,b=30の置換を行う (square x) の本体 (* x x) を呼び出し x=20 の置換 (* 20 20) → 4 (square x) の本体 (* x x) に x=30 の置換 (* 30 30) → 900 コンピュータ内部での計算 実行結果
練習問題1 x 円の硬貨 y 枚の金額を求める関数 coins を作れ。 関数 coins を用い、1円硬貨 a 枚、5円硬貨 b 枚、10円硬貨 c 枚、50円硬貨 d 枚、100円硬貨 e 枚、500円硬貨 f 枚の合計を求める関数 total を作れ。 (define (coins x y) (* x y)) (define (total a b c d e f) (+ (coins 1 a) (coins 5 b) (coins 10 c) (coins 50 d) (coins 100 e) (coins 500 f))) ;; test >(total 1 2 3 4 5 6) >(+ (coins 1 1) (coins 5 2) (coins 10 3) (coins 50 4) (coins 100 5) (coins 500 6)) >(+ (* 1 1) (coins 5 2) ... )
設計レシピによるプログラム作成
プログラム設計法 プログラム開発には多くの段階が必要 体系的に秩序正しく開発 → 設計レシピ 問題記述の文中で何が本質で重要(不要,冗長)か 入力に何を受け取りそれを出力にどう関連づけるか 対象データに対する演算をSchemeが提供しているか(なければ対応するプログラムを開発する必要) 作成後、実際に意図した計算を実行するかチェック 体系的に秩序正しく開発 → 設計レシピ 何をすべきかのステップごとの処方箋とその順序 少なくとも4つの活動:purpose,example,definition,test
プログラム設計法 プログラム開発に必要な活動 Purpose プログラムの目的を理解し、機能の概要を記述 Example Contract:関数名とデータ受け渡しに関する約束を記述 Header:関数定義の先頭部(名前や引数)を記述 Statement:何を計算するか自然言語や数式でコメント Example プログラムの振る舞いを「例」で記述. Definition プログラムの定義を具体化する Test 検査を通じた誤り(エラー)の発見
例題2. リングの面積 まず問題をよく分析して理解する 例題2. リングの面積 外径 outer, 内径 inner からリングの面積を求めるプログラム area-of-ring を作り実行する 円の面積を求める関数 area-of-disk を使う まず問題をよく分析して理解する 真ん中に穴のあいた 円板の面積と考える inner outer 求める面積は,外側の円の面積から, 内側の穴の円の面積を引いたもの
リングの面積 外径:outer outer 内径: inner inner リングの面積 = 外側の円の面積 - 内側の円の面積 = 外側の円の面積 - 内側の円の面積 半径 outer の円 半径 inner の円 (外径 outer と内径 inner は実際の計算時に与えられる)
入力と出力 5, 3 50.24 area-of-ring 入力は 1つの数値 inner, outer 出力は 1つの数値
Purpose - contract & header 問題の解析と目的の把握 関数の名前と取り扱うデータの種類の記述 area-of-ring: number number -> number 関数名(内容を適切に表現) : 入力データの種類の列挙 -> 出力 プログラム中での記述 ;; area-of-ring : number number -> number ここで ; で始まる行は実行されない(人間が読むコメント) 引数名を決めれば関数ヘッダ(先頭部)も書ける ;; area-of-ring : number number -> number (define (area-of-ring outer inner) ...)
プログラムが何をするか,何を計算するかを書いた文章 Purpose - statement - プログラムの振る舞いに関する仕様の明確化 プログラムの仕様,目的など to compute the area of a ring whose radius is outer and whose hole has a radius of inner プログラムが何をするか,何を計算するかを書いた文章 (注:DrScheme では英文で入力) プログラム中での記述 ;; area-of-ring : number number -> number ;; to compute the area of a ring whose radius is ;; outer and whose hole has a radius of inner (define (area-of-ring outer inner) ...)
Purpose での成果 以下順に contract:関数の名前と取り扱うデータの種類 statement:プログラムが何をするかのコメント。headerの引数名をコメント中に盛り込むと,よりわかりやすい header:関数ヘッダ(先頭部)。本体式はまだない ;; area-of-ring : number number -> number ;; to compute the area of a ring whose radius is ;; outer and whose hole has a radius of inner (define (area-of-ring outer inner) ...)
Example 例題を使った,プログラムの振る舞いの例示 入出力関係を特徴づけるような「例」での記述 入力と期待される出力 area-of-ring should produce 50.24 for the inputs 5 and 3 入力と期待される出力 (プログラムの理解の助けになる) プログラム中の記述 ;;area-of-ring : number number -> number ;;to compute the area of a ring whose radius is ;;outer and whose hole has a radius of inner ;;example:(area-of-ring 5 3) should produce 50.24 (define (area-of-ring outer inner) ...)
Example の効用 特定の入力に対する出力の記述 プログラムを正確に理解するための手段(関数の入力と出力の関係や,計算過程など) 論理的なエラーの発見の手段. プログラムの本体を書き下す前に例を作るべき (記述後ではプログラム記述の影響を受ける可能性) 後でプログラムを読み返すときのための「メモ」として (プログラム作成者以外にも有用) prose : 散文
Definition Example まで出来たらプログラム本体の記述を開始. Header において「…」としていた部分を実際に作成 ドーナツ型の面積は外側の円の面積から内側の円の 面積を引いたもの プログラムの振る舞い プログラム中での記述 ;;area-of-ring : number number -> number ;;to compute the area of a ring whose radius is ;;outer and whose hole has a radius of inner ;;example:(area-of-ring 5 3) should produce 50.24 (define (area-of-ring outer inner) (- (area-of-disk outer) (area-of-disk inner)) )
area-of-ring 関数の定義 1つの 関数 定義 関数の名前 (define (area-of-ring outer inner) 「(関数の)定義である」 ことを示すキーワード 関数の名前 1つの 関数 定義 (define (area-of-ring outer inner) (- (area-of-disk outer) (area-of-disk inner))) 「外側の円の面積 - 内側の円の面積」を 計算(出力)する本体式 値を2つ受け取る(入力)
Definition 記述していた「Example」を参考に、与えられた入力から,プログラムがどのように出力を計算するのかを理解した後に行う 基本演算や定義済(予定)のSchemeプログラムを使い,入力引数から答を計算 入出力関係が,数式で与えられていれば, → 直ちにプログラムを書ける 言葉で問題が与えられていれば, → 注意深くプログラムを作る to this end このために
プログラム設計法の例(まとめ) ;;Contract: ;;area-of-ring : number number -> number (define (area-of-ring outer inner) … ) ;;Purpose(statement): ;;to compute the area of a ring whose radius ;;is outer and whose hole has a radius of inner ;;Example: ;;(area-of-ring 5 3) should produce 50.24 ;;Definition: (define (area-of-ring outer inner) (- (area-of-disk outer) (area-of-disk inner))) ;;Tests: (area-of-ring 5 3) ;; expected value 50.24 参考 Web ページ http://www.htdp.org/2003-09-26/Book/curriculum-Z-H-5.html#node_sec_2.5
実行例: (area-of-ring 5 3)から 50.24が得られる過程 最初の式 (area-of-ring 5 3) = (- (area-of-disk 5) (area-of-disk 3)) = (- (* 3.14 (* 5 5)) (area-of-disk 3)) = (- (* 3.14 25) (area-of-disk 3)) = (- 78.5 (area-of-disk 3)) = (- 78.5 (* 3.14 (* 3 3))) = (- 78.5 (* 3.14 9)) = (- 78.5 28.26) = 50.24 area-of-ring の本体 (- (area-of-disk outer) (area-of-disk inner)) に outer= 5, inner= 3 の置換 コンピュータ内部での計算 area-of-disk 本体 (* 3.14 (* r r)) に r = 5の置換 ※円周率PIは 3.14とする (* 5 5) → 25 (* 3.14 25) → 78.5 area-of-disk 本体 (* 3.14 (* r r)) に r = 3の置換 (* 3 3) → 9 (* 3.14 9) → 28.26 実行結果
ここまでのまとめ Purpose: プログラムの目的を理解し、機能の概要を記述 Contract:関数名,データ受渡しに関する約束を記述 Header:関数定義の先頭部(名前や引数)を記述 Statement:何を計算するか自然言語や式でコメント Example プログラムの振る舞いを「例」で記述. Definition プログラムの定義を具体化する Test 検査を通じた誤り(エラー)の発見
例題3. 映画館の利益計算 テキスト3.1節の例題: 「映画館」の所有者がチケット価格を決める チケット価格から,収入,支出,利益などを見積もりたい (高価格なら入場者減のトレードオフ) チケット価格と平均観客との間の関係 チケットあたり5.00ドルの価格では、120人 10セント(0.10ドル)下げると観客が15人増える しかし観客の増加は支出の増加につながる 上映ごとに180ドルの固定費 各観客ごとに4セント(0.04ドル)の費用 チケット価格と利益の間の正確な関係を知りたい $5 .. 120 $5 - 0.1x .. 120 + 15x y=$4 = $5 -0.1x -> x=10 ->120+15*10=270 cost 270 * 0.04 y=$3 -> x=20 ->120+15*20=420 cost 420 * 0.04 x=($5-y)/0.1 z=120+15*x = 120+(15 * ($5 -y)/0.1)
例題5. 映画館の利益計算 まず目的を理解し機能の概要を記述する Purpose フェーズ 今回はいくつかの数量が互いに依存 → 依存関係を一つずつ分析 (視点:自由に設定できるチケット代をいくらにするか決めたい) 利益は ticket-price に依存 (∵ 収入と支出の双方が依存) ;; profit : number -> number ;; to compute the profit as the difference between ;; revenue and costs at some given ticket-price (define (profit ticket-price) ... ) 収入も ticket-priceに依存 (∵収入を決める観客数もそれに依存) ;; revenue:number → number ;; to compute the revenue, given ticket-price (define (revenue ticket-price) ... )
例題5. 映画館の利益計算 目的を理解し機能の概要を記述する Purpose フェーズ いくつかの数量が互いに依存 → 依存関係を一度に一つずつ分析 (視点:自由に設定できるチケット代をいくらにするか決めたい) 支出もticket-price依存 (∵ 支出の算出に必要な観客数が依存) ;; cost : number -> number ;; to compute the cost, given ticket-price (define (cost ticket-price) ... ) 観客数も ticket-price 依存 ( ticket-price と観客数には相関あり) ;; attendees:number → number ;; to compute the number of attendees, ;; given ticket-price (define (attendees ticket-price) ... )
例題5. 映画館の利益計算 各関係ごとに数量が互いにどう依存するかを分析し定式化 → チケット代 ticket-price から利益(と収入、支出、観客数)を求める関数 profit, revenue, cost, attendees の計算例と本体作成 profit: 利益 = 収入(revenue) - 支出(cost) revenue: 収入 = 観客数(attendees) × チケット代(ticket-price) cost: 支出 = 固定費 + 観客数(attendees) ×観客ごとの費用 固定費: $180, 観客ごとの費用: 観客1人あたり $0.04 attendees: チケット代(ticket-price)と観客数には関係がある チケット代:$5 のとき観客数は120人、 $0.1値下げすると15人増えた
支出の見積もり式 支出= 固定費 + 観客数 × 観客ごとの費用 固定費 今回) 固定費: $180 観客数に比例して かかる部分 固定費 観客がいなくても かかる費用 (会場,設備,宣伝, 出演料その他) 観客数 支出= 固定費 + 観客数 × 観客ごとの費用 今回) 固定費: $180 観客ごとの費用: 観客1人あたり $0.04
観客数の見積もり式 チケット代と観客数には関係がある 観客数 推定された見積もり式 チケット代 チケット代 チケット代と観客数には関係がある 今回) チケット代:$5 のとき,観客数は120人だった チケット代:$0.1値下げすると15人増えた ⇒ 観客数 = -(15/0.1)×(チケット代-$5)+120 と見積もる
例題3. 映画館の利益計算:例 分析結果にもとづき詳細定義の前に example を考える ;; profit : number -> number ;; ... ;; example: (profit 5) should produce 415.2 (define (profit ticket-price) ... ) ;; revenue:number → number ;; example: (revenue 5) should produce 600 (define (revenue ticket-price) ...) ;; cost : number -> number ;; example: (cost 5) should produce 184.8 (define (cost ticket-price) ...) ;; attendees:number → number ;; example: (attendees 5) should produce 120 (define (attendees ticket-price) ...)
例題3. 映画館の利益計算:定義 profit 関数 revenue 関数 cost 関数 attendees 関数 (define (profit ticket-price) (- (revenue ticket-price) (cost ticket-price))) (define (revenue ticket-price) (* (attendees ticket-price) ticket-price)) (define (cost ticket-price) (+ 180 0.04))) (define (attendees ticket-price) (+ 120 (* (/ 15 0.10) (- 5.00 ticket-price)))) profit 関数 (スペースの都合上 ヘッダなどは省略) revenue 関数 cost 関数 attendees 関数
profit関数を起点にした呼出関係 profit 関数 revenue 関数 関数を呼び出し cost 関数 を呼び出し (define (profit ticket-price) (- (revenue ticket-price) (cost ticket-price))) revenue 関数 (define (revenue ticket-price) (* (attendees ticket-price) ticket-price)) revenue,cost 関数を呼び出し cost 関数 attendees 関数 を呼び出し (define (cost ticket-price) (+ 180 (* (attendees ticket-price) 0.04))) attendees 関数 attendees 関数 を呼び出し (define (attendees ticket-price) (+ 120 (* (/ 15 0.10) (- 5.00 ticket-price))))
練習2 関数 profit (授業の例題3)についての練習 チケット代が 3, および 4 の時の関数 profit の実行結果を示せ
(profit 3)から 1063.2 が得られる過程 最初の式 コンピュータ内部での計算 実行結果 (profit 3) = (- (revenue 3) (cost 3)) = (- (* (attendees 3) 3) (cost 3)) = (- (* (+ 120 (* (/ 15 0.10) (- 5.00 3))) 3) (cost 3)) = (- (* (+ 120 (* 150 (- 5.00 3))) 3) (cost 3)) = (- (* (+ 120 (* 150 2)) 3) (cost 3)) = (- (* (+ 120 300) 3) (cost 3)) = (- (* 420 3) (cost 3)) = (- 1260 (cost 3)) = (- 1260 (+ 180 (* 0.04 (attendees 3)))) = (- 1260 (+ 180 (* 0.04 (+ 120 (* (/ 15 0.10) (- 5.00 3)))))) = (- 1260 (+ 180 (* 0.04 (+ 120 (* 150 (- 5.00 3)))))) = (- 1260 (+ 180 (* 0.04 (+ 120 (* 150 2))))) = (- 1260 (+ 180 (* 0.04 (+ 120 300)))) = (- 1260 (+ 180 (* 0.04 420))) = (- 1260 (+ 180 16.8)) = (- 1260 196.8) =1063.2 コンピュータ内部での計算 実行結果
課題1 関数 profit (授業の例題3)についての問題 ;; cost: number -> number ;; to compute the cost, given ticket-price. ;; The cost per one attendee is $1.5 and constant cost is 0 (define (cost ticket-price) (* (attendees ticket-price) 1.5) ) 課題1 関数 profit (授業の例題3)についての問題 業務内容の変更で固定費が $0 、一人当たりの支出が $1.5 となった。対応するよう例題3 のプログラムを変更しなさい。 この場合のチケット代を 3, 4, 5 としたときの関数 profit の実行結果を報告しなさい cost: 支出=固定費+観客数(attendees) ×観客ごとの費用 固定費: $10, 観客ごとの費用:観客1人あたり $0.5 (define (cost ticket-price) (+ 10 (* (attendees ticket-price) 0.5) ) )
ところで 複数の関数に分割しなかったら, どうなっているだろう?
直接,profitを計算するような式を書いてみる ;; profit : number -> number ;; to compute profit, given ticket-price (define (profit ticket-price) (- (* (+ 120 (* 15 (/ (- 5 ticket-price) 0.1))) ticket-price) (+ 10 (* (+ 120 (* 15 (/ (- 5 ticket-price) 0.1))) 0.5)))) ところで,以下の式のエラーを,すぐに見つけることが できるだろうか? ;; profit : number -> number ;; to compute profit, given ticket-price (define (profit ticket-price) (- (* (+ 120 (* 15 (/ (- 5 ticket-price) 0.1)) ticket-price)) (+ 10 (* (+ 120 (* 15 (/ (- 5 ticket-price) 0.1)) 0.5)))))
せめてattendees関数だけでも利用するprofit関数の式を書いてみる ;; attendees : number -> number ;; to compute the number of attendees, given ticket-price (define (attendees ticket-price) (+ 120 (* 15 (/ (- 5 ticket-price) 0.1)))) ;; profit : number -> number ;; to compute profit, given ticket-price (define (profit ticket-price) (- (* (attendees ticket-price) ticket-price) ;; revenue (+ 10 (* (attendees ticket-price) 0.5)))) ;; cost 少しはすっきりするが, revenueとcostの計算式が 直接,表れている.
課題2 合衆国は以下のように、世界標準とは異なる単位制度を使っている: 以下の関数を作れ:inch->cm, feet->inches, yards->feet, rods->yards, furlongs->rods, miles->furlongs その後に 以下の関数を作れ:feet->cm, yards->cm, rods->inches, miles->feet (ポイント:関数の再利用) 合衆国 世界標準 1 inch = 2.54 cm 1 foot = 12 in. 1 yard = 3 ft. 1 rod = 5.5 yd. 1 furlong = 40 rd. 1 mile = 8 fl.
;;inch->cm:number -> number ;;to convert the unit of length from inches to cm. ;;(inch->cm 1) should produce 2.54 (define (inch->cm inch) (* inch 2.54)) ;;feet->inches:number -> number ;;to convert from feet to inches ;;(feet->inches 1) should produce 12 (define (feet->inches feet) (* feet 12)) ;;yards->feet:number -> number ;;to convert the unit of length from yards to feet. ;;(yards->feet 1) should produce 3 (define (yards->feet yard) (* yard 3)) ;;rods->yards:number -> number ;;to convert from rods to yards ;;(rods->yards 1) should produce 5.5 (define (rods->yards rods) (* rods 5.5)) ;;furlongs->rods:number->number ;;to convert from furlongs to rods ;;(furlongs 1) should produce 40 (define (furlongs->rods furlongs) (* furlongs 40)) ;;miles->furlongs:number->number ;;to convert from miles to furlongs ;;(miles->furlongs 1) should produce 8 (define (miles->furlongs miles) (* miles 8)) ;;feet->cm:number->number ;;to convert from feet to cm ;;(feet->cm 1) should produce 30.48 (define (feet->cm feet) (inch->cm (feet->inches feet))) ;;yards->cm:number->number ;;(yards->cm 1) should produce 91.44 ;;to convert from yards to cm (define (yards->cm yards) (feet->cm (yards->feet yards))) ;;rods->inches:number->number ;;to convert from rods to inches ;;(rods->inches) should produce (* 5.5 3 12)=198 (define (rods->inches rods) (feet->inches (yards->feet (rods->yards rods)))) ;;miles->feet:number->number ;;(miles->feet 1) should return 5280 (define (miles->feet miles) (yards->feet (rods->yards (furlongs->rods (miles->furlongs 1)))))