2022.11.15 Coskx Lab
だいたい思い通りのプログラムが書けるようになると,次に学びたいのは関数の利用です。
micro:bitの入門資料で関数に関する紹介は多いのですが,文法の説明までの場合が多いです。
ここでは関数を利用する意義を考え,関数の利用方法を紹介します。
関数の引数と,関数の返す値についても紹介します。
さらに,意外と重要な概念であるグローバル変数とローカル変数についても紹介します。
関数を使う意義を考えたいと思います。
関数を使わなくてもプログラムは書けます。関数を使うと見通しのよいプログラムになることを例を使って示したいと思います。
関数はMakecodeの「高度なブロック」から使用できるようになっています。
プログラム制作例題
次のプログラムを作ります。
(micro:bitはLED面を上にして水平に置かれているとします。)
ボタンAが押されたら次のことを連続して行います。
3秒前から1秒ごとに3,2,1とカウントダウンをLEDに表示します。
カウントダウンが終わったら,microbitの方位を測定し,だいたいの方位をN,E,S,Wの内の1文字でLEDに1秒間表示します。
その後,温度センサによる測定値をLEDに(スクロール,流れる文字)表示します。
このプログラムは関数は使わなくても次のように書くことができます。
このプログラムは3つの作業を行っているので,このままでも良いかもしれません。
もし10個の作業を行うとしたら,長い長い「ボタンAが押されたとき」ブロックが出来ます。
そしてもし,その中の一か所を変更しようとしたら,その場所を見つけるだけでも大変になります。
それぞれの作業をひとまとまりの機能ととらえて,かたまりごとのミニプログラムにします。
ひとまとまりの機能を持つミニプログラムが関数になります。
そして,「ボタンAが押されたとき」ブロックでは3つの作業をそれぞれの関数にやってもらうという感じになります。このことは「関数を呼び出す」とも表現されます。
「プログラム制作例題」のプログラムでは,「ボタンAが押されたとき」ブロックが全体構造を示し,3つの関数は個々の作業内容を表します。
大きなプログラムを書くとき,関数を使うと,機能ごとに分けて記述でき,全体の見通しをよくするごりやく(意義)があります。
どのようなことをひとまとまりの機能として関数とするかは,難しい問題です。
大きな作業を行うとき,この部分は面倒だけれどひとまとまりの機能として切り分けられるので,下請けさんに任せてしまおうという発想が,関数を利用する考え方です。
時には下請けさんが,さらに面倒な仕事を切り分けて孫請けさんにお願いすることもあります。すなわち関数が更に別の関数を呼び出すような使いかたもあります。
また,別の角度からすると,こんな下請けさんがいたら便利だなとの考え方で関数を作ることも多いです。
次の図は関数を使っているときのプログラムの実行の様子を示しています。
関数を呼び出すと,
関数の呼び出しには,「呼び出し」ブロックが使われています。
関数には名前(関数名)があり,関数名を指定して呼び出します。
関数を作るときは,「高度なブロック」→「関数」で作業を開始します。
関数の名前は関数を作る次の画面で設定できます。(doSomethingの部分を書き換えます。)
右下の「完了」緑ボタンで関数ブロックが表示されるので,その中に作りたいプログラムを書き込みます。
関数のいろいろな使い方を説明する前に,磁気センサの測定値から方位角を自分で求める関数を作ります。
この関数を修正しながら,関数の説明をします。
磁気センサの測定値から方位角を求めて,シリアル通信でMakecodeの受信コンソールに表示するプログラムは,次のようになります。
ただし,測定値は10回平均とします。
(方位角の求め方は micro:bit 磁気センサ 参照)
このプログラムでは「ボタンAが押されたとき」ブロックでは関数showHeadingを呼び出しているだけです。
(「ボタンAが押されたとき」ブロックではこの関数の前後でもっと多くの作業をしていて,方位角を表示する作業は切り分けられたので関数showHeadingを呼び出しているというイメージでこのプログラムを見てください。)
「ボタンAが押されたとき」ブロックで関数showHeadingを呼び出すと,関数showHeadingの中味が上から順に実行され,関数の最後まで到達すると,「ボタンAが押されたとき」ブロックに戻るという実行の流れになります。
その結果,磁気センサの値を10回取得した値から方位角を計算し,シリアル通信で送信し,Makecodeの受信コンソールに表示という作業が行われます。
ボタンAを押すたびに方位角がMakecodeの受信コンソールに表示されます。
「6 方位角を求めて表示する関数」の関数は,方位角を自分で求めた後,シリアル通信までしていますが,このような関数はあまりありません。
大きなプログラムでは,方位角を求めたら,その方位角を使って何かの作業をするはずです。(例えばサーボモータを動かします)
関数に期待するのは,方位角を得ることだけです。関数内でのシリアル通信は呼び出し側からすると大きなお世話です。(この関数の動作チェックをするときには必要ですが。)
次のプログラムでは,「ボタンAが押されたとき」ブロックで,関数に方位角を求めてもらって,その値で何かの作業をする例として,サーボモータを動かす代わりに,得られた方位角をシリアル通信で送信しています。(この想定をしないと「6」との違いがわからなくなります。)
関数getHeading0は,求められた方位角をG_headingという変数に保存しています。
呼び出し側である「ボタンAが押されたとき」ブロックでは,関数getHeading0から戻ってきた直後に変数G_headingをシリアル通信で送信しています。
このように関数getHeading0内で使われている変数G_headingと,それ以外のところで使う変数G_headingは同じ変数として使えます。
ある関数内で使う変数が関数外でも同じ変数として使えるのはグローバル変数と呼ばれます。Makecodeで使用する変数はほとんどがグローバル変数として扱われます。
「7 求めた方位角を変数に保存する関数」はグルーバル変数を使って,求められた値を呼び出し側に返していました。
関数には関数内で求めた値(ひとつだけ)を呼び出し側に返す仕組みがあります。
関数の末尾に「戻る」ブロックを置き,その中の値を入れる枠(次の図では0が入っています。)に返したい変数や式を入れます。
「戻る」ブロックが入った関数が作成されると,変数のところにうまくハマる形状の関数呼び出しが使えるようになります。
この関数を使ったプログラムは次のようになります。
「ボタンAが押されたとき」ブロック中では,関数から返ってきた値を変数headingに保存してからシリアル通信で送信する方法と,関数から返ってきた値をを直接シリアル通信で送信する方法を示しています。
返す値を持つ関数を使うとグローバル変数を使わずに,求めた方位角を呼び出し側に返すことができるようになります。
グローバル変数はどこからでも使える便利さより,どこからでも値を書き替えられてしまう危険の方が大きく,大きなプログラムでは嫌われ者です。
「8」までに出てきた方位角を得る関数では,平均化回数は10回に決まっていました。
グルーバル変数を使って,平均化回数を変えれらるように修正したプログラムが次のものです。
呼び出し側は,平均化回数をグローバル変数G_number_Meansに保存してから,関数getHeading2を呼び出します。
関数内のループ回数はグローバル変数G_number_Meansで決まります。
「ボタンAが押されたとき」ブロック中では,10回平均で方位角を求めるように設定してから関数getHeading2を呼び出しています。
「ボタンBが押されたとき」ブロック中では,100回で方位角平均を求めるように設定してから関数getHeading2を呼び出しています。
「9」ではグルーバル変数を使って,平均化回数を変えました。
グローバル変数は「8」で示したように嫌われ者です。
平均化回数を与えながら関数を呼び出せるように出来ればこの点が解消します。
関数に値を与えるようにする仕組みは引数と呼ばれます。
引数は関数ブロック中で見るとただの変数です。★1
引数付きの関数を作るときには,関数を作る次の画面で設定できます。
引数付きの関数が出来上がると,関数呼び出しブロックに値を挿入する枠が付きます。
引数で平均化回数を与えられるように修正したプログラムが次のものです。
関数内のループ回数は変数number_Meansで決まりますが,これはグローバル変数ではありません。★1
「ボタンAが押されたとき」ブロック中では,値10を伴わせて関数getHeading3を呼び出しています。
「ボタンBが押されたとき」ブロック中では,値100を伴わせて関数getHeading3を呼び出しています。
ミニ補足
大きなプログラムになると,大枠を考えることと,面倒で細かな部分を考える作業を分離することが必要になります。そのために関数を利用します。
関数を利用しないと,見通しの悪い(間違えやすい)プログラムになります。
ひとまとまりの機能を下請けに任せて,楽をするというのが関数を作る際の感覚です。(下請けは更に孫請けに任せます。)
また,こんなひとまとまりの機能をしてくれる関数があったら便利だなという感覚で作る関数も多いです。
引数を使うと関数の使いまわしができます。
関数では返す値と引数を使った値のやり取りをして,できるだけ変数を共有しないようにします。
関数内と呼び出し側で共通で使える変数をグローバル変数と呼びます。
グローバル変数は,関数内と呼び出し側だけでなく,関数も含めて全てのブロック内で共通に使えます。
一方,関数の引数で作った変数はその関数内でしか使えません。このような変数はローカル変数と呼ばれます。
ある名前のグローバル変数が使われているとき,関数の引数で同じ名前のローカル変数を使った場合,この2つは別の変数として扱われます。
次のプログラムではtest2はグローバル変数です。test1はグローバル変数が使用されていますが,関数の引数として同じ名前のローカル変数test1も使われています。
このプログラムでは「最初だけ」ブロックから関数testLocalVariableが呼び出されています。
関数testLocalVariableは永久ループで,test1とtest2を表示しています。
ただし,関数内の変数test1はローカル変数です。
「ボタンAが押されたとき」ブロックではtest1を100にし,test2を1増やしています。
ここで出てくるtest1はグローバル変数のtest1です。
実行結果は,次のようになります。
ローカル変数のtest1は常に1を表示していますが,グローバル変数test2はボタンが押されるたびに1ずつ増えているのがわかります。
「ボタンAが押されたとき」ブロックでtest1を100にしているのですが,こればグローバル変数のtest1なので,関数内ローカル変数のtest1に影響は与えられず1のまま変化しません。
test1= 1
test2= 0
test1= 1
test2= 0
test1= 1
test2= 0
Button pressed
test1= 1
test2= 1
Button pressed
test1= 1
test2= 2
test1= 1
test2= 2
Button pressed
test1= 1
test2= 3
Button pressed
test1= 1
test2= 4
Button pressed
test1= 1
test2= 5
Button pressed
test1= 1
test2= 6
ここで,「4」のプログラムを振り返ってみましょう。
このプログラムでは,関数showHeading中でHEAD_x,HEAD_y,HEAD_headingの3つのグローバル変数が使われています。
もし,この関数以外のところで,悪いタイミングでHEAD_x,HEAD_yに0が入れられたら,正しい方位角が求まりません。
グローバル変数が嫌われる所以です。
そのような不注意を避けるために,この関数内で用いる変数にはHEAD_で始まる変数名を持つようにして,誤って他のブロック内から操作されないようにしています。
「4」のプログラムを再掲します。
Makecodeで使用する変数は引数を除いて全てグローバル変数なので,注意が必要です。
意図的にグローバル変数として使う場合は,「7」のG_heading,「9」のG_number_Meansのように「G_」で始める変数名を使うようにしています。
また関数内で使用する変数は例えば「HEAD_」で始めるような工夫をするとよいでしょう。
関数の引数と返す値について,簡単な加算する関数makeSumを示します。
単純すぎる関数なので,関数の意義が不鮮明になりますが,関数の引数と返す値の使いかたはよくわかると思います。
加算ですので,実行結果は次のようになります。
5 + 8 = 13