面倒な計算やファイル処理はできるだけ小さく分けた関数の中で行うようにするが、パイプライン(データの流路)の構築が終わっていざ解析、となったとき、関数を使うのはいくつかの点で不便である。
- あくまで、私のような電気生理学のデータ解析の場合で、違うジャンルではまた状況が異なるかもしれない。一度 MATLAB Answers で、データ解析は スクリプトでやるのがよい、と書いたら、激昂して(?)functionのほうが絶対よい、と食って掛かってきた人が複数いた。私は彼らのいうfunctionを使った場合の利点を(おそらく)すべて飲み込んだ上で、あえて、スクリプトそれもLive Scriptの利点を認めていることをお断りしておきます。また食って掛かってくる人がおられるかもしれないから。
データ解析の最終段階ではどんどん図を作って整形していくことが主となり、また数値データを書き出していくことも必要となる。関数を主に使った場合、プログラムが動作中にデバガを使う(エディタの行番号の横に赤い丸印を置いて実行中に動作を止めるあれ)ときのみしか関数の中身の変数などをいじって微調整することが出来ない(関数の中で使われる変数の「スコープ」が狭く、外からはアクセスできない)。このことは通常プログラミングではむしろ歓迎される事が多いのだが、図をどんどん作る際にはかえって不利である。つまり、ああ、今あそこの数字をちょっとだけ変えて様子を見たいのに、と思ったらもう一回頭からプログラムを走らせてちょうどいいところで止めてデバッグする必要がある。これは面倒でしょう。Base Workspaceに変数を保存しておいてどんどん修正できる方が小回りが利いてよい。
さらにMATLABで図をどんどん作っていくと、画面上に図のウィンドウが散乱してどれがどれだか分からなくなってしまう。プログラム的に図を一箇所にまとめようと思うと、subplotなどを多用することになるが、これも結構大変(論文用の最終的な図はこれでやるのがよいと思っているが)。
また、統計のp値などの数値データも、どこかに書き出す必要があるが、別のファイルへ保存するとあの数値は何処へ行ったっけと探すのが大変になりがち。
Live Scriptは、これらの問題を一気に解決してくれる。何も気にせずにどんどん図を作っていくとそれがコードの横(または下)に表示されるので、散逸することがない。数値データについても、disp
やfprintf
で書き出せばそれがコードの近くに保存されるので、探し回る必要がない。
さらに重宝するのが、Live Scriptから Save As...でhtmlやpdfを選ぶと、コードと図や数値結果をすべて含んだ形で、文書ファイルとして保存できる。これは「実験ノート」の形式としてとても便利である。
Live Scriptのヴァージョン管理
図や計算結果を保持することができるのがLive Scriptの長所だが、短所としては当然ファイルサイズが大きくなるということがある。100も図を作ってしまうと 5 MBとか10 MBとかいうサイズにもなる。ディスク・スペースの消費としてはそれでも大した問題ではないのだが、Gitを使ってヴァージョン管理をする場合、Live Scriptの .mlx
ファイルをcommitしていくと、これはレポジトリが相当嵩張ってしまう。また、diffが取れないので、コードの変更箇所が分からないという問題もある。
このため、Live Scriptを書いたら、最後は必ず、.html
と.m
スクリプトの二つの形式に書き出すようにしており、.mlx
と.html
は .gitignore
ファイルで指定してGitでは無視して、.m
だけをGitヴァージョン管理するようにしている。
Live Scriptの中で local function を使うべきか
R2016bにおいて、意外にもMATLABは、mスクリプトファイル・Live Scriptファイルの末尾にも local functionを書くのを許すことになった。状況を知らない人のために説明すると、それまで長きに渡り local functionはfunctionまたは classdefファイルでしか許されていなかったのだ。他の言語でサブルーチンとか呼んで、スクリプトの内部に小さな関数を書くのが割りと一般的だと思うが、それがずっと禁じられていた。
local functionを書くのは簡単で、ファイルの末尾 (functionやclassdefの場合は、function
,classdef
に対応するend
が必要で、そのさらに下のスペース)に、function
とend
で囲ってfunctionを書くだけである。こうすると、local functionを呼び出すことが出来るのは、同じファイルに書かれているコードだけに限定され(スコープが狭い)、別のファイルのコードからはアクセスできない。あまり汎用性のない繰り返し処理を実行させるのに適したテクニックである。
スクリプトへのlocal functionの追加
blahblahblah
A = local1(2,3)
function var3 = local1(var1, var2) %これがlocal function
var3 = var1 + var2;
end
local functionをスクリプトで使うことの利点は2つあると思う。
- そのスクリプト・ファイルだけで使用し、他では出番のないような繰り返し処理は、local functionにうってつけである。作業フォルダ内をあまり出番のない、マニアックな関数で埋め尽くさずにすむので、関数の名前空間がスッキリするだろう。
- また、スクリプトを他人と共有する場合にも、複数のファイルを渡す代わりに、依存する関数をできるだけlocal functionとしてスクリプトに内蔵しておけば、分かりやすく、トラブルが生じにくいだろう。
ところが、実際にLive Scriptでlocal functionを導入してみると、これが非常に不便であることに気がつく。なぜかというとLive Editorでは、GUIによるデバガが使えない(行番号の横に赤い丸印を置くことすらできない)からである。さらに、スクリプト内臓のlocal functionを使用している場合、Command Windowで、コードを評価(evaluate)することすらできない。Command Windowからはlocal functionにアクセスできないからである。あくまで、Live Editor上で、セクション単位で実行するときだけ、local functionにアクセスできるというわけである。
このためにLive Scriptに内蔵した local functionの中でどういう処理が行われているかを知るには、コードの行末の**セミコロン ;**を消して、あるいはfprintf()
関数を使ってより意図的に、計算結果を吐き出させるくらいしか術がない。完全に動作するのが保証されているコードをlocal functionとして埋め込むならばよいが、修正や微調整が必要な場合(つまりほとんどの場合)、これはあまり効率的ではない。
- と書いたが、私の友人の理論家は、かつて「デバッグツールは使ったことがないですね。要所要所にprintをぶち込んでいくだけです。」と宣うて、私を心底震え上がらせた。こういう人はここで書いているようなことで不便を感じないのでしょうな。
それではどうしたらよいだろうか?他には出番のないような関数ファイルを作業フォルダに作って置いていくというのがオーソドックスな方法だが、上で述べたように関数の名前空間が出番のない関数たちによって散らかってしまうところが嫌である。もともと汎用性のない関数なのだからできるだけ他からはアクセスできないようなのが望ましい。
Live Scriptのために utility class を準備する
この問いに対する私の答えは、static methods だけを有するutility classを作るというものである。
もし、script1.mlx
というLive Scriptで作業しているとしたなら、script1_util.m
という、同じ名前に接尾辞 suffix _util
のついた classdef ファイルを作る。
script1_util.m
classdef script1_util
methods (Static)
function varout = method1(var1,var2)
% blahblah
varout = var1+ var2;
end
function varout = method2(var1,var2)
% blahblah
varout = var1 * var2;
end
...
end
end
script1
のほうではこんな感じで上記のscript1_util
を呼び出して使う。Static methods(methods (Static)
という箇所がポイント)については、この例のようにオブジェクト u
を作らずとも、いきなりscript1_util.method1(3,5)
としてもよいのだが、u = script1_util;
と置いているのは、script1_util
の名前をいちいち繰り返すのが面倒なので書き方を省略するためだけである。別の機会に述べるが、スクリプトの名前は往々にして、すぐに内容が分かるように長たらしいものにするのがよいので、それ専用のutility classの名前も長たらしいものになりがちで、この省略がありがたいわけ。
script1.mlx
u = script1_util;
out1 = u.method1(3, 5)
out2 = u.method1(5, 7)
out3 = u.method2(3, 5)
out4 = u.method2(5, 7)
尚、必要ならば、script1_util
の中にlocal function
を書くこともできる。下の例では、local_func1
というのがlocal functionだ。classdef
に対するend
の外側、さらに下に関数を追加すると local functionとなり、このscript1_util
の中からしかアクセスすることができない。
script1_util.m
classdef script1
methods (Static)
function varout = method1(var1,var2)
% blahblah
varout = local_func1(var1,var2);
end
function varout = method2(var1,var2)
% blahblah
varout = var1 * var2;
end
...
end
end
function out = local_func1(var1,var2)
out = var1 + var2;
end
複数のスクリプトで共通の処理を行う
上の例では utility functionは、一個のスクリプトへ対応させるような形を想定した。開発初期はこれがよいが、そのうちに、複数のスクリプトで同じ処理をしたい、という欲求も出てくる。
もしそれがある程度汎用性のあるものなら、utility classの中の static methodを、独立したfunction m fileとして保存する。
汎用性はそれほどでもないと思えば、複数のスクリプトで読み込むutility classを共用すればよいだけ。簡単でしょう。
追記 2018.5.15
R2018aにおいて、ついにLiveScriptがデバガに対応した。これによって、ユーティリティ・クラスにStatic関数を書く書法のメリットはかなり減ったものと思う。実際に、Live Script内に埋め込んだローカル関数を使ってみているが、以前よりも遥かに使い勝手が良い。