LoginSignup
0
1

More than 5 years have passed since last update.

部分適用っておいしいの? on Arduino

Last updated at Posted at 2017-07-31

LCD KEYPAD SHIELD試してみたの補足です。

飛ばし過ぎだろ!意味わかんね!とリア突っ込みが聞こえてきたので、そこんとこだけでも説明を試みます。できるかな?

関数の部分適用とは?

関数に対して全ての引数を一度に渡さず、一部の引数だけ渡すことができる仕組み、だそうです。
それでどうなるかというと、渡された引数は適用され、残った引数を引数とした関数になります。
簡単にできる言語もありますが、残念ながらArduino言語には簡単に実現する方法はないようです。

部分適用して何がうれしいの?

参考にしたページには

  • ソースの可読性向上
  • ソースを簡略化可能
  • 汎用性向上

とあります。
あんまりピンときませんよね?
いえ決して説明が悪いというわけではなく、難しい策を弄してまで得られるメリットが見出せない...

私見ですが、
関数の引数には、

  • 関数を作ったり、関数の動く環境を決めるのに必要な引数と、
  • その時には決まっていなかったり、問題にならなかったりするが、関数を使うときになるととたんに大事になる引数

の二種類があるように思います。
この二つがはっきり分れている場合に、部分適用がおいしくなるような気がします。

例。
疑似的なコードですが。
消費税額を計算する関数があるとします。

税額 = 関数( 税率、金額 ) {return  税率 * 金額 }
税額( 0.08, 100 )     // 8円
  • 税額とは、税率、金額(引数)が決まると税率 * 金額(帰り値)を計算して返してくれる関数
  • 税率 0.08で金額 100円だと 税額( 8, 100 ) は 8円になる

という意味です。
いろんな時期の経理をなにかするプログラムがあったとして、

//  税率を部分適用した関数を作る

昔の税額 = 税額( 0.0 )                //  税率 0%
このあいだまでの税額 = 税額( 0.05 )     //  税率 5%
今の税額 = 税額( 0.08 )               //  税率 8%

というように部分適用した関数が作れ、

//  金額 100円 の税額を求める

昔の税額(100)           // 0円
このあいだまでの税額(100) // 5円
今の税額(100)           // 8円

のように書けたらいいな、ということです。
これを

  • 名前を換えただけ?本質的には変わってないじゃん!
  • 一手間多いし...
  • めんどくさい理屈の割に大したことない。がっかり...

と思うか、

  • 見やすくなった!
  • 書きやすいし間違いも減りそう!
  • 一つの関数から関数がたくさん作れて便利!

と思うかは人それぞれだと思います。

どうやるの?

Arduino(C++11)で、引数をただ減らして書いてみても、「足りない!」ってコンパイルの時に怒られるだけです。
これを、Arduinoで実現できる形にしてみましょう。

手順としては:

1 あらかじめ普通の関数と同じように、全部の引数と、最終的な返り値を決めておく
2 引数を部分適用したいものと実行時に残しておきたいものに分ける
3 部分適用したい引数を引数とし、小さな関数を返り値とする大きな関数を作る。

  • ここの小さな関数は、引数が実行時に残しておきたかった引数で、返り値はもともとの最終的な返り値です。

4 大きな関数に部分適用したい引数を適用すると、その引数が部分適用された新しい小さい関数が作られる。

大きな、小さなというのはわかりやすいかなあと思って言ってみました。実際の大きさとは関係ありません。
関数A、関数Bでもいいし、汎用、具体的でも、おおざっぱ、細かすぎるでもいいです。適当に読み替えてください。

自分で書いてて何ですが、まったく意味不明ですね。文章にすると難しい。
でもやってみると意外と簡単なことだったりします。

上の消費税の例で行きます。まだ擬似的なコードですが。

まず1。これは簡単です。上にもある通り:

税額 = 関数( 税率、金額 ) {return  税率 * 金額 }

ですね。

2。部分適用したい引数は 税率 、実行時に残しておきたい引数は 金額 ということにします。

3。ここがちょっと面倒なので大きな関数と小さな関数を分けて書いてみます。
大きな関数は、引数が部分適用したい引数で、返り値が小さな関数。
小さな関数は、引数が実行時に残しておきたかった引数で、返り値はもともとの最終的な返り値。

税額 = 関数( 税率 ) {return  ある税率の時の税額 }
ある税率の時の税額 = 関数( 金額 ) {return  税率 * 金額 }
  • 税額とは、税率を引数として、ある税率の時の税額を返す関数
  • ある税率の時の税額とは、金額を引数として、税率 * 金額を返す関数

これはそんなに違和感ないんじゃないでしょうか?ただ分けてかいただけに見えます。
これを合体します。

税額 = 関数(税率) {return  関数(金額) {return  税率 * 金額 } }
  • 税額とは、税率を引数として、( 金額を引数として、税率 * 金額を返す関数 ) を返す関数

うーん、複雑になっちゃったぞ。
でも同じことを言ってるというのはギリギリ納得していただけるでしょうか?
何でわざわざ合体させるの?
Arduino(C++11)のコードにするときにこんな形になっているからです。
あとが楽。

4。小さな関数を作って使ってみます。

//  税率を部分適用した関数を作る

昔の税額 = 税額( 0.0 )
このあいだまでの税額 = 税額( 0.05 )
今の税額 = 税額( 0.08 )

//  金額 100円 の税額を求める

昔の税額(100)           // 0円
このあいだまでの税額(100) // 5円
今の税額(100)           // 8円

これだといつも引数の数が定義した通りなので、コンパイラさんに怒られることはなさそうです。

Arduino(C++11)で書いてみましょう。
※ 簡単にするため、型は全部floatということにします。

//   税額=関数(          税率 ){return 関数(           金額  )              {return  税率 * 金額   } }
auto tax=[](const float RATE){return [=](const float PRICE)mutable->float{return RATE * PRICE;};};                                                      

//  税率を部分適用した関数を作る
auto oldTax = tax( 0.0 );       // 昔の税額 = 税額( 0.0 )
auto formerTax = tax( 0.05 );   // このあいだまでの税額 = 税額( 0.05 )
auto currentTax = tax( 0.08 );  // 今の税額 = 税額( 0.08 )

//  金額 100円 の税額を求める
oldTax( 100.0 );        // 昔の税額(100)            // 0円
formerTax( 100.0 );     // このあいだまでの税額(100)  // 5円
currentTax( 100.0 );    // 今の税額(100)                // 8円

謎の記号やら単語やらがちりばめられていますが、

  • 対応する言葉が同じ順で出てきて、
  • 同じようにかっこで括られていたり、
  • 同じように二回returnしていたりと

どうやら同じことをやっているようです。また、

  • []関数 っていうこと?
  • =mutable ってどういう意味?

推理や疑問がわいてきますね。

これは、関数の部分適用を、C++11のラムダ式のクロージャーという仕組みを使って実現しているのです。
lambda 完全解説 , ラムダ式(C++11) , クロージャデザインパターン
あたりを読んで究めていただければOKなのですが、とりあえずのテンプレート的なものを書いておきます。

auto 大きな関数 = [](部分適用したい引数の型と名前){
    引数以外に使いたい変数があればここで宣言と初期化;
    return [=] (残しておきたい引数の型と名前) mutable -> 返り値の型 {
        return 返り値;
    };
};
auto 小さな関数1 = 大きな関数( 部分適用したい引数1 );
auto 小さな関数2 = 大きな関数( 部分適用したい引数2 );
//....

小さな関数1( 残しておいた引数1 );      // 返り値1
小さな関数2( 残しておいた引数2 );      // 返り値2
//....

参照ページを読むとたくさんのオプションが出てきますが、とりあえずこのパターンだけ使えれば大概の人は困らないようです。

これで終わります。
つっこみ大歓迎! ですが、

  • 部分適用に焦点をあてたので、クロージャーの大事なおいしいところが抜け落ちてます。
  • カリー化とかいうとまた別の難しいことになるので、そっちの方向にいかないように話をそらしてます。
  • 本編では、初期化に()を使ってるので見た目がアレです。ここではより一般的な=で初期化してます。ほぼ同じです。お好みで読み替えてください。
  • ArduinoにはSTLがありません。環境がとても貧弱です。std::functionだと、とかBoostが、とかの話はしないでください。うらやましくてよだれが出ます。
  • 同様にC++14,17の話題もご遠慮ねがいます。遠い目になってしまいます。

わからないよ、おかしいよ、というところがあればコメントお願いします。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1