RLinkの関数

このチュートリアルでは,クロージャやより高次の関数等のより高度な形も含めて,RLinkのR関数を定義して使う方法について説明する.

関数の参照

RLinkのR関数は,頭部RFunctionを持つ式で表され,この頭部はRのワークスペースで定義された不明瞭な参照である.そのような参照は変数に保存するか,直接使うかして,引数としてWolfram言語式に対してR関数を呼び出し,結果をWolfram言語に返す.

関数参照を取得して使う方法

RLinkを通して任意のR関数を呼び出し,それに引数としてWolfram言語式を割り当てるためには,その関数への参照をまず取得する必要がある.関数がまだRのワークスペースで定義されていない場合には(例:無名関数),その関数は,Wolfram言語参照を得るための手順の一部として,まず定義され,Rのワークスペースに保存される.これは自動的に行われ,ユーザはこのことについて心配する必要がない.

REvaluateを通して得る関数参照

まず,RLinkをロードすることから始める.

このような参照を生成するにはいくつかの方法がある.1つの方法は,Rで関数を評価するコードの文字列に対して,REvaluateを呼び出す方法である.

sumに保存されたものは,Rのワークスペースで定義された関数(この場合は組込み関数)である.この参照を使うことができる.

そのような参照を直接使うこともできる.

しかし,REvaluateで返された関数参照に対して,そのような操作は望ましくない.その理由については,次のセクションで説明する(基本的に,呼出しのたびに新しい参照が作成されるからである).

名前付きでも無名でも,ユーザ定義された関数で,REvaluateを通して関数参照を取得することもできる.例えば,引数から1を引く関数を定義することができる.

これで参照を得ることができる.

これをテストする.

参照を得るのに,R側で関数に名前を付ける必要波ない。無名関数でも大丈夫である.

RFunctionを通して得る関数参照

まだやっていない場合には,まず,RLinkをロードする.

関数参照を得るもう一つの方法として,RFunction[code]を使う方法がある.

そのような参照は同じように使うことができる.

ここでも,Wolfram言語変数への割当てを行わずに,以下のプログラムのように直接使うことができる.これは Wolfram言語の純関数に似ている.

REvaluateで参照が作成される場合とは異なり,この場合は,直接使用する際に問題はない.以下に示すように,RFunctionが返す参照は,REvaluateが返す参照とは内部的に異なるからである.

関数参照を得るこの方法は,可能な場合(特にWolfram言語でのコード文字列の実装によって定義するユーザ定義のR関数)には,望ましい方法である.この理由は,以下で説明するが,関数参照の寿命にかかわってくる.すべての関数についてRFunctionを通して参照が得られる(クロージャはその主たる例外である)わけではないが,その多くについて得ることができる.(基本的に,名前付きの関数,あるいはコード文字列で完全に定義されるが,デフォルト(大域的なもの)を除いてどのR環境も依存しない関数であるR関数について,参照が得られる.)

関数の呼出しの結果得られる関数参照

クロージャの場合は,もう少し難解な方法で関数参照を生成する.つまり,関数は他の関数への呼出しの結果返される.クロージャについては後ほど詳しく説明するが,ここでは1つ例を挙げる.引数に固定数を加える別の関数を生成する関数の例である.

まず,RLinkをロードする.

以下はコードである.

引数に10を加え,その結果を返す関数を生成する.

これも関数参照である,以下のように使える.

REvaluateを通して得た参照のように(そしてRFunctionを通して得た参照とは違って),以下のような直接の使用は可能であるが,お勧めはできない(これに対してRFunctionを通して得た参照を直接使うことは可能であり,望ましい方法である).そのような呼出しを行うたびに同じ関数参照が新たに作成されるからである.

このような場合には,add10で行ったように,参照を変数に保存したほうがよい(RFunctionを通して ,生成器関数addNumが得られるが,add10に保存した参照は,addNumへの関数の呼出しを通して生成される.事実上,これはREvaluateのような方法で作成されるのである).

関数参照の構造と寿命

RLinkで使われる関数参照間のもっとも重要な違いは,それらが得られた方法に直接関係している.関数参照の種類の1つに,REvaluateで返されたもの,あるいはR関数の呼出しの結果としてのもの(クロージャ)がある.これを第1種参照と呼ぶ.もう一つの種類の参照には,RFunctionを通して得られたものが含まれ,これを第2種参照と呼ぶ.これらのクラスに属する参照波、寿命,キャッシュ等の面で違いがある.

関数参照の完全な内部形式

まず,RLink をロードする.

以下は,組込みのR関数への参照の簡単な例である.

これのFullFormを調べることができる.

最初の要素は文字列で,これは"builtin"または"closure"となる.これは同じ名前を持つRの2つの関数の型に対応している.Rには(比較的)少ない数の組込み関数が含まれる.これらは primitive と呼ばれ,それ自体はRに実装されず,型"builtin"を持つ.これは以下を評価するだけで確かめることができる.

その他の関数はすべて,型"closure" を持つ(Rの"closure"の型は,他の関数の環境にアクセスしてそれを実行した結果得られる,より伝統的な定義のクロージャよりも広い範囲のクラスの関数を含むので,紛らわしい.特に,Rの"closure"の型には,トップレベルのR関数も含まれる).これには,トップレベルのRで実装された組込みのR関数と,ユーザ定義のR関数の両方が含まれる.ここでも,与えられた関数についてこれをチェックするのは簡単である.

2つ目の要素は,その関数を定義するコードの文字列で,RCodeラッパーで囲まれたものである.RCodeのコードは,常に関数を完全に構築するのには十分ではないので,主に例証目的のものである(関数は,特定のR環境に依存するかもしれず,バイトでコンパイルされたものであるかもしれないが,これらの情報はすべて,この表示で失われる).

3つ目の要素は,参照指標で,これは参照をR言語ワークスペースで定義された実際の関数に関連付けるためにRLinkによって内部的に使われるものである.これは,正の整数(第1種参照)でもAutomatic(第2種参照)でもよい.

最後の要素は,RAttributes[]のコンテナで,これは対応するR関数が自明でなない属性を持つかどうかによって,空の場合もそうでない場合もある.

トップレベルのRで実装されたR関数のコード(実装)にアクセスする

まず,RLinkをロードする.

組込みであるが,トップレベルのR言語で実装されている関数も型"closure"を持つ.

以下は,この関数を使う(自明な)例である.

しばしばそのような関数に対して,そのコード(実装)を抽出することができる.

factorについて(全体的により有利な)第2種参照を作成した場合には,このようなコードは使えない.

同じことが名前付きの関数すべてについて言える.第1種参照は,少なくともその内部実装の詳細の一部を伝えるのに対して,第2種参照は,その名前だけを伝える.もちろん,Rで使用できるR関数の実装詳細に別の方法でアクセスすることもできる.

参照の寿命

第1種の関数参照の寿命は,現行のRLinkセッションに限られている.第2種の参照は永久的である.このことによって,このセクションと下のセクションで説明するように,さまざまな問題が起ってくる.

明示的なR言語コードの実行からの参照(第1種)

第1種の参照はすべて,上で述べたように,一時的な参照である(これは索引を付けるのに使われた整数の指数で示されている).このような参照は,参照が定義されたのと同じRLinkセッション内でのみ有効である.技術的には,これは,Rから返された関数が通常定義するのに使われたR環境を一緒に運ぶからで,そのような環境は動的で,与えられた実行中のRセッションと関連するからである.

まず,RLinkをロードする.

この種類の参照を定義する.

ちゃんと作動することをテストして確かめることができる.

RLinkをここで再起動する.

上と同じ参照を使おうとすると,参照が無効になったため,エラーが返される.

これは,"closure"および"builtin"の型を持つR関数の両方の参照について言える.

RFunctionを通して得た参照(第2種)

このような参照は,複数のRLinkやWolframシステムのセッションにおいて有効であり続けるという意味で,永久的である.

上と同じように簡単な例でこれを例示することができる.

まず,RLinkをロードする.

以下は関数の例である.

結果の参照のFullFormを見ると,参照の指数がAutomaticであることが分かる.

このような関数は,そのR言語コードによって明瞭に定義されており,デフォルトの大域的なR環境以外のR言語環境を含まない.このため,このような関数参照は,複数のRLink(およびWolframシステム)セッションで使うことができる.一方,このような関数は,Rのデフォルト(大域的な)環境しかアクセスできないので,この方法で定義できる関数の種類は限られてくる.

RLinkセッションを再起動する.

参照はまだ作動することが分かる.

このような参照について,短いシンタックスRFunction[code]を使う代りに,手作業でFullFormを構築することができる.

関数が現行のRLinkセッションで定義されていない場合でも,これはまだちゃんと作動する.

第1種と第2種の参照:その他の違い

まず,RLinkをロードする.

REvaluateへの呼出しが行われるたびに,生成されている関数独自の新しい第1種参照が作成されることに注意する.

これは,R言語のコードを実行(REvaluate)した場合には,関数がすでに定義されたことをRLinkが一般的な方法で伝えることができないためである.ほとんどの場合,そのような新しい参照は冗長である.

RFunctionで定義される関数(第2種)は,第1種とは違って,同じ参照が呼出しのたびに作成される.

これは,参照がキャッシュされるためである.RFunctionで定義される関数は,完全にコード文字列によって定義されるため,キャッシュが可能である.

この違いを考慮すると,以下のようなコードは全く望ましくない.関数の呼出しと同じ数だけ関数の参照が作成されるからである.

これらの参照はすべてR側で保存され,メモリを消費する.さらに,関数の参照を変数に保存したり,RFunction[code]を使って関数をWolfram言語から定義したり(可能な場合)すると,作業速度がゆっくりになる.

第1種と第2種の参照:どちらをいつ使うか���敒摮牥湩佧瑰潩獮敒摮牥湉牣浥 REvaluate 慴汬y��敒摮牥Type I vs type II references - when to use which

上の説明から,可能な限り,第2種の参照を使うべきであることは明らかである.第2種は永久的で,キャッシュされるからである.ユーザ定義の関数についてはほとんどの場合,これが可能である.

まず,RLinkをロードする.

以下でそのような参照を定義する.

これらは以下の代りに使うことができる.

また,組込み関数についてもほとんどの場合,これが可能である.以下はその1例である.

これらは以下の代りに使うことができる.

唯一これが可能でないのは,ある種のクロージャを作成する場合である.一般に,問題の関数がデフォルト(大域的な)環境以外のR環境を参照する場合には,これは不可能である.

Rへ関数参照を送信する

関数参照は,RLinkがRデータの型を表すのに使う内部の長い形式と,エンドユーザが通常使う短い形式の間の自明ではない変換規則を持たない.言い換えれば,関数参照にとってどちらの形式も同じである.

まず,RLinkをロードする.

次に,関数参照を作成する.

下の3つの式は同じである.

RLinkが理解するその他のR言語オブジェクトと同様に,参照をRSetを使ってR言語サー区スペースの変数に割り当てることによって,Rに関数参照を送信することができる.

これで,R側でcube関数を使うことができる.

必要なら,REvaluateRFunctionを通して,参照をこの関数に送り返すこともできる.

そしてこれらの参照を代りに使うことができる.

ただし,これらの参照は,Wolfram言語の変数cubeに保存されたもとの参照とは異なることに注意する.

高度な関数の用法

高次の関数

高次の関数とは他の関数を引数として受け入れることができる,関数のことである.典型的なWolfram言語の組込みの高次関数としてはMapApply等が挙げられる.R言語では,Wolfram言語と同様に,関数は値であり,パラメータとして直接他の関数に渡すことができる.

RLinkのRFunction構築は,高次の関数パラダイムを完全にサポートする.例として,Wolfram言語のSelectに似た,フィルタリング関数を構築する.この関数は,ベクトルとテスト関数を取り,テスト関数を満足するベクトルの要素だけを選ぶ.

まず,RLinkをロードする.

関数を定義する.

これをカスタムのフィルタリング関数と一緒に使うことができる.このフィルタリング関数もRFunctionで定義する.

もちろん,以下のように,テスト関数を変数に保存することができる.

しかし,これは不要である.以下のプログラムは,上のWolfram言語でのものと似ており,名前付きの引数Functionのシンタックスを使って類似性を強調している.

クロージャ

クロージャは通常他の関数によって結果として返される関数であり,関数が実行された後でも囲む関数の環境(変数,状態)にアクセスできる.RとRLinkはどちらも完全にクロージャをサポートする.

まず,RLinkをロードする.

以下はクロージャの標準的な例であり,上ですでに使われたものである.数を取り,関数(クロージャ)を返し,入力の引数にこの買うを加える関数である.

今度は特定の数に対してクロージャを生成するのに使うことができる.

これを使う.

クロージャとは,他の関数を実行した結果であるので,クロージャを頻繁に使うことによって別のスタイルのプログラミングを簡単にする.このプログラミングは他の関数を作るのに使われる動的なブロックとして,ほかのデータ型と同じように,関数を強調する.

例えば,そのようなクロージャのリストを作成することができる.

そしてこれを以下のように使うことができる.

あるいは以下のように使うことができる.

もっとおもしろい例は,変化しやすい状態にアクセスして修正することもできるクロージャの例である.例えば,いくつかのベクトルで反復子を作ることができる.以下の関数は,そのような反復子の生成器の可能な実装の1つである.

これはベクトルを取り,ベクトルが埋め込まれている反復子の関数を作成する.反復子の関数はパラメータと可変の状態(変数i)を持たない.可変の状態は呼び出されるたびに変化する.ベクトルの最後に到達すると,その後の呼出しに対してはRのNULLが返される.特別の割当ての演算子"<<-"に注意する.これは囲む(親)環境から変数を割り当てるのに使われる.

ベクトル(リスト){5,6,7,8,9,10}でのカスタム反復子である.

これはベクトルの要素を一つずつ返す.

もちろん,これがWolfram言語側で使われる際には,効率的ではない.RLink関数の呼出しのオーバーヘッドは小さくないからである.ただしここでは,基本的に抽象的な意味では,RLinkがクロージャをサポートすることを示すことを目的としている.「性能」のセクションでこの例をもう一度使って,性能を向上させる方法について見る.

エラーの処理

ここでは,RLinkにおいてR関数を定義したり使ったりする際によく起るエラーについて挙げる.

無効なRのコード

関数を定義して参照を得ようとする際に無効なR言語コードのシンタックスを使うと,エラーメッセージが返され,結果は$Failedになる.

まず,RLinkをロードする.

RFunctionを使う場合には,エラーメッセージは通常(シンタックスのエラーが起った厳密な場所は示さないが)かなり役に立つ.

REvaluateを使った場合には,エラーメッセージはもっと分かりにくいものである.

有効期限が切れた(あるいは無効な)関数の参照

まず,RLinkをロードする.

ここでは,関数の参照を非常に大きな指数を使って手作業で定義しようとする.そのような指数を使った関数の参照はすでに現行の RLinkセッションで生成されたはずである.

これは,過去のRLinkセッションの1つで定義された関数への参照を,そのような参照がRのワークスペースで現在定義されているどの関数にも対応しないような新しいRLinkセッションで使う場合に似ている.

参照の式自体は不活性なので,定義自体は行われる.しかし,これを使おうとするとエラーが起る.

これは,第1種の関数参照に対してのみ起る.第2種の関数参照の場合はこのような問題はない.

複数のRLinkセッションでの参照の衝突

第1種の参照については,単に参照の有効期限が切れるよりも悪いことが起ることがある.新しいRLinkセッションで,ある関数の参照が,以前のRLinkセッションで保存された参照と同じ参照指数を割り当てられるということがある.そのような場合,新しい関数は古い関数が使われるべきところで知らないうちに使われている.

まず,RLinkをロードする.

例として,新しいRLinkセッションを始め,関数を定義する.

この関数をテストする.

新しいセッションをはじめ,別の関数を定義する.

ここで間違ってtestRefを使おうとすると,以下のような結果になる.

これはもちろん,古い関数ではなく新しい関数が行う操作である.このようなエラーは見付けにくいが,第1種の参照の場合にだけ起り得る現象である.このことからも可能な場合には第2種参照を使うほうが望ましいことが分かる.

第1種の参照を保存し,単一の RLink セッション以上にその使用を(これらの参照を使うWolfram言語コードで書いて)拡張するのは誤りである.参照の期限切れや参照の衝突のような問題を避けるためには,これが起らないようにWolfram言語コードを書かなければならない.

この状況は,Moduleで生成された局所変数の場合に少し似ている.これらは単一のWolframシステムセッション内では一意的であるのに対して,RLinkの第1種の関数参照は,単一の RLink セッション内(単一のWolframシステムセッション内に複数のRLinkセッションが存在する場合もある)で確実に一意的であるという点で異なる.

間違った数の引数

間違った数の引数で関数の参照を呼び出そうとすると,R言語側でエラーが起る.ただしエラーメッセージは不可解であることもある.

性能

現行のRLinkの実装では,RとWolfram言語の間でデータ送信が複数の段階で行われるため,非常に大きな関数の呼出しとデータ送信のオーバーヘッドが起ることが多い.このセクションでは,このようなオーバーヘッドを関数の呼出しにおいて最小限にする方法について説明する.

関数の呼出しの効率性

関数の呼出しのためのこのシンタックスは,便利であるが,オーバーヘッドが起り,そのオーバーヘッドはしばしばかなり大きなものになる.

まず,RLinkをロードする.

以下は,オーバーヘッドを示す:

ベクトル化された操作をより多く使い,Rでまとめて作業を行う(データ交換を最小限にし,さらに大切なことに,データ交換の粒度を最大限にする)ようにすると,コードはより高速化される.

関数の呼出しには常にオーバーヘッドが存在し,これが上の例の実行時間の大部分を占めていた.ここでは,点の数を1000倍に増やしたが,結果を計算する時間はほとんど変わらない.

非効率な関数の呼出しのように見えるものが実際にはデータ送信に関連したオーバーヘッドであるということがある.

次のコードはかなり遅い.

しかしここでの問題は,関数の呼出しのオーバーヘッドではなく,Rからリストを送信する際のオーバーヘッドであり,このオーバーヘッドがかなり大きくなることもある.どちらがコードをゆっくりにしている主な理由であるかを推定する方法の一つに,関心関数を呼び出す結果として期待するものと似たテストデータを送信してみる方法がある.

この場合,実行時間の大部分は,大きなリストをRからWolfram言語に送信する時間に占めてられていることは明らかである.

クロージャと高次の関数

上で見たように,RLinkでは参照をRのクロージャに返し,これをWolfram言語で使うことができる.しかし,これは普通に使うと時間がかかることが多い.

まず,RLinkをロードする.

先ほどの反復子の生成器の例をもう一度見てみよう.以下にコードをもう一度示す.

あまり大きくない大きさのリスト(ベクトル)の反復子を生成する.

これを例えば各要素で反復して二乗するのに使うと,時間がかなりかかる.

これと比較して,以下はこれに似たトップレベルのWolfram言語の実装である.

ここで同じような反復子が生成すると,この操作は,1000倍速くなる.しかもこれはトップレベルのWolfram言語コードである.

以下は所要時間をテストするものである.

つまりこの形式では,非常に小さいリストに使う場合を除いて,iterL は使い物にならないということである.

しかし,反復子をR側で定義した場合には,反復もR側で行うほうが道理にかなっている.反復の過程をRに動かすためには,もう一つの方法を取り,適用する関数と反復子を取るより高次の関数を定義し,反復をR側で行うとよい.

反復子の新しいインスタンスを定義する.

平方する関数を定義する.

これをテストすることができる.

これは100倍速く実行できる.この実行時間にはデータを送信する時間も含まれる.スピードアップしたのは,反復がR側でされたが,Wolfram言語の柔軟性(これはある意味であたかも Wolfram言語関数を呼び出して,他のWolfram言語関数に渡しているように見える)をすべて利用できるからである.これでも使用はできるが,ループを使う純に命令的で厳格な実装に比べるとまだゆっくりである.

iterGenで定義された反復子は,単一の完全な反復のみを許すという意味で,「一度きりの」オブジェクトである.

この例からさまざまなことが分かる.1つ目は,(例えばWolfram言語で大きな反復を行うことによって)簡単な関数の呼出しの数を最大にすると,それぞれの関数の呼出しがR側ではほとんど何も行わないが,(性能的に)最悪の状況が作成されるということである.

2つ目は,RLink関数の呼出しのオーバーヘッドによって時間がかかるコンストラクトでも,Wolfram言語でプロトタイプ化してテストしてから,Wolfram言語の最初の実装からRへ計算にメモリを要する部分(ループ,反復等)を徐々に動かすという自然に最適化の経路を取ることができるということである.言い換えれば,機能的にRとWolfram言語を混ぜてプログラムを書くことができ,Rのコマンドプロンプト自体がそうであるように,少なくともこの増分コード開発とプロトタイプ化の面においては,Wolfram言語をRのプロトタイプ化された環境とほとんど同じものとして扱うことができる.