日本ソフトウェア科学会第 38 回大会 (2021 年度) 講演論文集

小規模組込みシステム向け FRP 言語における周期的タスクの記述方式
辻 裕太

森口 草介

渡部 卓雄

関数リアクティブプログラミング (FRP) は，時間とともに変化する値（時変値）によってプログラムを抽象化し，入力から出力への計算の宣言的な記述を可能にするプログラミングパラダイムである．本研究では，組込みシステム
向け FRP 言語 Emfrp に対して周期的タスクの記述を可能にする構文および処理系の拡張を導入する．周期的なパ
ルスあるいは時刻を時変値とすることで，Emfrp で周期的タスクを簡潔に表現することは可能であるが，従来の処理系で生成されるコードの実行効率は高くない．提案手法では，周期的タスクの実行におけるリアルタイム性の向上
だけでなく，入力の取得頻度および各時変値の更新頻度を適切に選び，結果として実行効率の向上を可能にする．本
稿では拡張した構文およびコンパイラによって生成されるコードについて述べ，これらについて例題を通して既存のEmfrp のものと比較し，本手法の有用性を示す．

システムを記述するプログラミングパラダイムの一1 はじめにつである．FRP では時間に依存して変化する時変値

今日において，Web サイトやアプリのようなグラ(time-varying value) と呼ばれる値を扱い，時変

フィカルユーザーインターフェース (GUI) や家電，自値から別の時変値へ変換する計算を宣言的に記述する

動販売機，券売機といった組込みシステムは，我々の日ことで，時事刻々と変化する入力と出力の関係性を見常にありふれている．これらのような外部からの入力

通しよく表現することができる．FRP は Web ベーに対して応答を行うシステムはリアクティブシステム

スのアプリケーション開発 [2]，ゲーム制作 [1]，ロと呼ばれる．

ボット制御 [4] といった典型的なリアクティブシステ

リアクティブシステムを実装する方法は様々であムに対して適用されている．

る．例えば，各イベントに対応したイベントハンドラ

Emfrp [6] は小規模組込みシステム向け FRP 言語を事前に登録しておき，その発生に応じて反応を行うである．この言語の特徴として，記述したプログラム

イベント駆動型プログラミングの適用が挙げられる．

の実行時におけるメモリ使用量の上限の推定が可能でまた，ソフトウェア開発におけるデザインパターンのある点や，再帰的データ構造や再帰関数の禁止および一つである Observer パターンを用いることで，プロループの不使用によって時変値計算の停止性が保証

グラム内の状態の変更を検出して他のオブジェクトされているという点が挙げられる．ところで，組込みへ通知し，最終的に外部へ応答を行うといった手法もシステムでは定期的なセンサー値の取得や一定のフ存在する．

レームレートで行うディスプレイへの描画といった周関数リアクティブプログラミング (Functional

期的タスクの実行がしばしば求められる．Emfrp プ

Reactive Programming, FRP) はリアクティブ

ログラムは言語処理系によって C++プログラムへとコンパイルされるが，このような周期的タスクを効率

A Method for Describing Periodic Tasks in the FRP
Language for Small-Scale Embedded Systems.
Yuta Tsuji，Sosuke Moriguchi，Takuo Watanabe, 東京工業大学, Tokyo Institute of Technology.

的に実行するランタイムを生成することができないという課題がある．
そこで本研究は， Emfrp に対する拡張 PbEmfrp

を提案する．PbEmfrp では Emfrp の時変値に対する時間的特徴付けを行い，その情報を用いて時変値計

1算を行うコードを生成する．これにより，周期的タ34スクを記述した際のリアルタイム性の向上だけでなく，計算資源を浪費しない，より効率的な実行を実現する．
本論文は，次のように構成される．まず第 2 章にて，研究の動機とともに本研究が拡張の対象とする既存の FRP 言語である Emfrp について述べる．次に第 3 章にて，提案手法である PbEmfrp の構文や新たに導入した各種概念について説明し，そのコード生

2

5
6
7

module SimFanController # モ ジ ュ ー ル 名in tmp : Double,# 温度センサーの値
hmd : Double# 湿度センサーの値
out fan : Bool
# ファンのスイッチの状態を示す論理値
use Std891011# 不快指数
node di = 0.81 *. tmp +. 0.01 *. hmd *.
(0.99 *. tmp -. 14.3) +. 46.3

12
13
14

# ファンのスイッチの状態
node init[False] fan = di >=. th

15
16
17

# ヒステリシス制御に用いる閾値
node th = 75.0 +.
(if fan@last then -.0.5 else 0.5)成の実装方針を第 4 章で示す．ケーススタディを通して PbEmfrp の具体的な適用例を第 5 章で提示し，ソースコード 1

SimFanController.mfrp

そして第 6 章では関連研究を与え，最後に第 7 章で結論と今後の課題について述べる．

おける直前のイテレーションで計算された値を参照することができる．これにより時変値間の依存関係を非

2 研究の動機

循環なものに変えることができ，コンパイラは時変値

Emfrp は組込みシステム向け FRP 言語である．

の更新順序を決定できるようになる．このプログラ

Emfrp プログラムには，各時変値の関係を式（以下，

ムでも fan と th という 2 つの時変値が互いに参照更新式）として記述することで入力時変値から出力時し合っているが，th はその定義において fan@last

変値を得る一連の計算を表現する．プログラムは入で fan を参照しており，時変値間の依存関係の循環力に対応する値を取得した後，更新式にしたがって各を回避している．

時変値の値を得る．全ての時変値の値を取得した後，Emfrp は，一定の時間間隔でシステムに対する入出力時変値を外部へ送出する．入力から出力への一連

力から出力を生成する周期的タスクを記述することもの処理は イテレーション (iteration) と呼ばれ，ラ可能であるが，そこにはいくつかの課題が存在する．

ンタイムによって繰り返し実行される．

ここでは周期的タスクの例としてデジタル温湿度計

Emfrp コード 1 は，温度と湿度から不快指数を計を取り上げ，その実装を示す．この温湿度計は周期的算し，その値によってファンの回転数の制御を行うもに温度および湿度を取得し，これらから計算される不のである．ここではヒステリシス制御が利用されて

快指数とともにディスプレイに表示するだけでなく，おり，スイッチが不快指数の閾値付近で ON/OFF を

外部記録装置に対して過去のセンサー値を用いた移頻繁に繰り返すのを防いでいる．

動平均を記録として送信する機能を持つ．以下は温

ところで，ある 2 つの時変値 u, v の依存関係に循

湿度計の仕様を示したものであり，図 1 は入力から環が存在する場合，任意のイテレーションにおいて u

出力を得る一連の計算の流れを示したものである．

を計算するのに v の現在の値が必要となり，v を計算するために u の現在の値が必要となる．この場合は時変値の更新順序が定まらない．そのため，コンパイ• 入力– 温度センサーの値∗ 5000ms 後から 5000ms 間隔で値を取得

ル時に全ての時変値の更新順序を決定する Emfrp コ

する

ンパイラは，そのようなプログラムに対してコンパイ– 湿度センサーの値

ルエラーを生成する．そこで Emfrp には @last というオペレータが定義されており，対象となる時変値に∗ 5000ms 後から 3000ms 間隔で値を取得
する

• 出力
– 外部の記録機器へ送信する，温度の 4 値の加重平均

∗ 20000ms 後から 30000ms 間隔で送信する– 外部の記録機器へ送信する，湿度の 3 値の加重平均

∗ 18000ms 後から 30000ms 間隔で送信する– ディスプレイに表示する，温度，湿度，不快指数の組∗ 温度か湿度をセンサーが読み取ったタ
イミングで表示する
ソースコード 2 は温湿度計の Emfrp による実装で
ある．
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

module ThermoHygrometerin tmp : Double, # 温 度
hmd : Double, # 湿 度
time: Int # プ ロ グ ラ ム 開 始 か ら の ミ リ 秒
out sendTmpAve: Double,# 温度の移動平均
outputSendTmpAve: Bool,# 温度の出力を行うかを示す論理値
sendHmdAve: Double,# 湿度の移動平均
outputSendHmdAve: Bool,# 湿度の出力を行うかを示す論理値
display: (Double, Double, Double),# 温 度 ，湿 度 ，不 快 指 数 の 組
outputDisplay: Bool
# ディスプレイへ出力を行うかを示す論理値
use Std# 温度の移動平均の計算
node init[(0.0, 0.0, 0.0, 0.0)] tmpHistory =if tmpUpdated
then tmpHistory@last of(tmp1, tmp2, tmp3, tmp4) -> (tmp, tmp1
, tmp2, tmp3)
else tmpHistory@last
node sendTmpAve = tmpHistory of(tmp1, tmp2, tmp3, tmp4) -> (tmp1 +. tmp2+. tmp3 +. tmp4) /. 4.0

28
29
30
31
32
33
34
35
36

# 経 過 時 刻 を も と に ，出 力 を 行 う か 決 定
node init[20000] tmpAveTargetTiming =tmpAveTargetTiming@last + (if outputSendTmpAve then 30000 else 0)
node outputSendTmpAve =time > tmpAveTargetTiming@last

37
38
39

# 湿度の移動平均の計算
node init[(0.0, 0.0, 0.0)] hmdHistory =if hmdUpdated

40
41
42
43
44
45
46

then hmdHistory@last of(hmd1, hmd2, hmd3) -> (hmd, hmd1, hmd2)
else hmdHistory@last# 出力時変値
node sendHmdAve = hmdHistory of(hmd1, hmd2, hmd3) -> (hmd1 +. hmd2 +.
hmd3) /. 3.0

47
48
49
50
51
52
53
54

# 経 過 時 刻 を も と に ，出 力 を 行 う か 決 定
node init[30000] hmdAveTargetTiming =hmdAveTargetTiming@last + (if outputSendHmdAve then 18000 else 0)
node outputSendHmdAve =time > hmdAveTargetTiming@last

55# 描画する情報の計算
func calcDi(tmp, hmd) = # 不 快 指 数
0.81 *. tmp +. 0.01 *. hmd*. (0.99 *. tmp -. 14.3) +. 46.3

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

node display = {(tmp1, tmp2, tmp3, tmp4) = tmpHistory(hmd1, hmd2, hmd3) = hmdHistory(tmp1, hmd1, calcDi(tmp1, hmd1))}# 経 過 時 刻 を も と に ，出 力 を 行 う か 決 定
node init[5000] tmpTargetTiming =tmpTargetTiming@last + (if tmpUpdated then 5000 else 0)
node tmpUpdated =time > tmpTargetTiming@last
node init[5000] hmdTargetTiming =hmdTargetTiming@last + (if hmdUpdated then 3000 else 0)
node hmdUpdated =time > hmdTargetTiming@last
node outputDisplay =tmpUpdated || hmdUpdated
ソースコード 2

ThermoHygrometer.mfrp

Emfrp プログラムはその実行においてイテレーショ
ンが繰り返し行われ，各イテレーションの実行時間は一定ではない．したがって温湿度計の仕様に示されるようなタスクの周期的実行を Emfrp プログラムに記述するには，例えば入力時変値としてプログラムが開始してからの経過時間を取り，各出力時変値に対してその実行タイミングを表現するパルスを生成し，タスクを実行すべき時刻を処理系が生成する C++プロ
グラム上で検出する必要がある．この周期的タスクの実装方法は，いくつかの欠点を抱えている．まず，プログラマが時刻を用いてパルスを生成するロジッ
クを逐一記述する必要がある点が挙げられる．仮に

入力

出力

直近 4 値の(5000, 5000)(30000, 20000) で

平均を計算

tmp

値を抽出

tmpAvetmpHistory

sendTmpAve

初期値：[0.0, 0.0, 0.0]

直近 3 値の(3000, 5000)(30000, 18000) で

平均を計算

hmd

hmdHistory

値を抽出

hmdAvesendHmdAve

初期値：[0.0, 0.0]

温度と湿度から

2 値のうち少なくとも不快指数を計算

一つの値が更新された時、(tmp, hmd, calcDi(tmp, hmd))

その時点での各値をタプルで得る(tmp, hmd)

display(π, φ) : 実行周期が π[ms] であり、最初の実行が行われる、プログラム開始からの経過時刻が φ[ms] であるようなタイミングで実行されることを示す

図1

温湿度計の構成

この処理がライブラリとして提供された場合においても，周期的タスクを実行するために必要な時刻情報

3 PbEmfrp

を出力時変値として用意する必要がある．次に，周期

PbEmfrp(Period-based Emfrp) は，周期的タ的タスクの実行タイミングが前後する，ジッターと呼スクを簡潔に記述し，かつ効率的なコード生成が行えばれる現象が起こる点が挙げられる．これは各イテるよう作成された，Emfrp のサブセットに対する拡レーションの実行開始時間の間隔が一定ではなく，時張を行なった言語である．PbEmfrp における言語拡

変値計算の開始時刻に周期性が無いという性質に起張は以下の通りである．

因している．最後に，計算資源の浪費が著しい点が挙• 時変値への時間的特徴付けげられる．Emfrp では各イテレーションにおいて必• タイミング注釈

ず入力時変値から出力時変値を求める計算が行われ，• シーケンス及び演算の導入

より低頻度の入力および出力が要求されている状況• 構文の拡張においても，不要な計算を省略することができない．

• 全時変値に対するシーケンスの静的検査

以上の議論から，Emfrp プログラムにおいて，計算

以降，これらについて各々述べていく．

資源を浪費しないような周期的タスクの記述は困難であると言える．

3. 1 時変値の時間的特徴付け

そこで本研究では，Emfrp の拡張として PbEmfrp

Emfrp では，イテレーション内で全ての時変値の更を提案し，周期的タスクの簡潔な記述，ジッターの無新式を計算していたが，PbEmfrp ではイテレーショ

い周期的タスクの実行，計算資源を浪費しないコードンの概念がなく，それぞれの時変値に定められた タの生成を試みる．第 3 章では構文および言語機能にイミング (timing) において更新式を計算する．例え関する拡張についてを述べていき，第 4 章にてこのば，時変値 v のタイミングを，単位がミリ秒であるよ

拡張を行なった PbEmfrp プログラムに対するコード

うな周期と最初の開始時刻の組 (π, φ) によって表す．

生成の実装方針を述べていく．

つまり，プログラムが開始してからの経過時刻 φ において最初に時変値 v の計算が行われ，以後 π 毎に

v1

v2

v3

v4

φ

φ+π

φ + 2πφ + 3π図2

時変値の発生の模式図

値

v:
0

t

タイミング

よりも過去の発生における値にはアクセスできない．
連続時変値は離散時変値とは異なり，時変値がいかなる時刻においても値を持つ．連続的な値は本質的に直接コンピュータ上で計算することができないため，
この時変値は離散時変値としてサンプリングされて利（つまり時刻 φ + π, φ + 2π, . . . に）再計算が行われる．

用される．定時変値は連続時変値と同様に連続的にこの計算が行われることを v の 発生 (occurrence)

値を取る時変値であるが，常に一定の値を取る．このと呼ぶ．図 2 は v の発生を表す図式である．一般に特徴から，他の時変値との計算を簡略化できるため，

周期的タスクの実行時刻はその実行周期と実行開始

連続時変値とは区別される．

時刻を用いて記述できることから，（複数の）時変値

PbEmfrp では時変値の時間的特徴を区別するためのタイミングを対応づけることで周期的タスクの実にシーケンス (Sequence) という概念を用いる．こ行を表現できる．

こでは便宜上，発生を持たないことを示すシーケンス

Emfrp においては，プログラムに記述された時変を加えて以下の 4 つの値のいずれかをとる．

値計算から得られる依存関係に基づいて全ての時変(θ, l) :離散時変値のタイミングθと初期値数 l の組値が順次計算され，このイテレーションが幾度となく’cont :

連続時変値であることを示すシーケンス

繰り返されるため，常に時変値計算が発生する．した’const :定時変値であることを示すシーケンス

がって，組み込みシステムにおいてはセンサーやモ’never :発生を持たないことを示すシーケンス

ジュールからの値の読み込みおよび外部デバイスへの出力もそれに応じて頻繁に発生することになる．しかところで，離散時変値はその発生時にのみ値を持つ

し PbEmfrp では，全ての時変値の全ての発生時刻がと解釈される．しかし，このままでは異なるタイミン

コンパイル時に決定されており，発生時刻以外では時グを持つ離散時変値を参照すると値を持たない場合

変値計算を行わないようなコードが生成されるため，がある．この問題を解決するために，PbEmfrp では

計算資源を浪費せず，余分な入力値の取得や出力の生時変値から別のシーケンスを持つ時変値を生成する

成をしないような周期的タスクの実行を実現できる．

演算が定義されている．

• サンプリング3. 2 シーケンス• 時変値合成

PbEmfrp における時変値は，その時間的特徴に応• @last 参照

じて以下の 3 種類に分類される．

• @now 参照• 離散時変値

3. 2. 1 サンプリング• 連続時変値

サンプリングは e @> θ で表現され，あるタイミン• 定時変値グを持つ離散時変値 e に対して，部分的な発生を表す

離散時変値は PbEmfrp において中心となる概念

タイミング θ における e の値を取得するような離散であり，その発生時刻を示すタイミングを持つ．離散時変値へと変換する．図 3 は，サンプリングの意味

時変値には 0 個以上の初期値を持たせることができを表現したものである．

る．ある離散時変値について，それが持つ初期値の数

3. 2. 2 時変値合成

をその初期値数と呼ぶ．後述する演算によって過去

時変値合成は左結合の三項演算子 e1 @< f >@ e2 での発生における値にアクセスする際，まだ発生が存在表現され，図 4 は生成される離散時変値を示したもしていない場合の値として初期値が用いられる．例えのである．合成された時変値は，一方の離散時変値がばある離散時変値の初期値数が l であるとき，l 回前発生し他方がそうでない場合，発生した方の値をそのまでの発生における値は常にアクセスできるが，それまま返す．各時変値の発生時刻が重複していた場合，

v1

π:
t

0
v1

v2

v3

v2

v3

v4

v:
t

0

v4

c1

v1

v2

v3

v@last[1]:

v:

t

t

0
v1

v3

l=n

l =n−1..
.

v@>π :
t

0

図3

cn−1

cn−2

時変値のサンプリング

cn−4

t

0
cn−1

cn

u1

cn−3

v@last[n − 1]:

u2

cn−2

cn−3

v@last[n]:
t

0

u:

l=1

l=0

t

0
v1

v2

v3

v1

f (u2 , v2 )v3

図5

@last 参照

v:
t
u1

v1

v2

v3

v4

v:

u@<f >@v :

t

0
t

0

図4

時変値合成

v1

c

v@now:

v2

v3

v4
t

0

与えられた関数 f に各時変値の値を適用し，その結図6@now 参照

果を返す．つまり，与えられる関数 f は二引数関数
であり，また各入力の型と出力の型は一致している必変値を生成する．最初の発生までは v の初期値をと要がある．

り，その後は各々の発生の値をその次の発生までとこの演算の結果のタイミングは，少なくとも一方のる．図式では v@now は図 6 のように表現する．

時変値が発生しているようなタイミングに対応する．

3. 2. 5 多項演算

これをタイミングの合成と呼び，各離散時変値のタイ多項演算においては，一般に異なるシーケンスを持ミングが θ1 , θ2 であるとき，θ1 | θ2 で表される．

つ複数の時変値に対する演算を行う．しかし前述の通3. 2. 3 @last 参照

り，離散時変値のタイミングが異なる場合については@last 参照は v@last[n] として表現され，図 5 は値を同時に取り出すことができないため，サンプリン生成される離散時変値を示したものである．この演

グを明示的に行うことでこれを解決する必要がある．

算は，初期値が存在する時変値に対して n 回前に計

それ以外の場合については，以下の議論のように時変算された値を持つ離散時変値を生成する．また v の値の種類に応じて計算結果のシーケンスを決定する．

時変値計算が行われていない場合における@last 参照

まず，タイミングが等しい離散時変値を扱う場合おの値は，v の定義で記述した初期値に対応する．

よび連続時変値や定時変値と離散時変値が混合して

なお，v@last[n] が生成する時変値は，v のシーケいる場合については，離散時変値の初期値数を考慮

ンスにおける初期値数 l の値が n だけ減少したようなしつつ適宜サンプリングを行う必要がある．異なる 2

シーケンスを持つ．残った初期値数は，他の時変値をつの離散時変値 v1 , v2 の初期値数を各々 l1 , l2 とする

定義する際の初期値列を生成するために用いられる．

と，演算結果の時変値が持つ初期値は v1 , v2 が持つ各3. 2. 4 @now 参照

初期値を用いて計算される．しかし l1 , l2 が異なる場

初期値が存在する，すなわち初期値数 l が正である

合，双方から同時に初期値を取り出すことができる

ような離散時変値 v に対して，@now 参照は連続時のは min(l1 , l2 ) 回前の発生までである．したがって，

v1 と v2 の計算結果は初期値を min(l1 , l2 ) 個持つ．

の組で記述する．また時変値へのタイミング参照は，

次に，一方が離散時変値 v であり他方が連続時変

例えば同じタイミングを持つ入出力時変値に対する値である場合，連続時変値は初期値を持たないことか

タイミング注釈の記述を簡潔にしたり，異なるタイら演算結果も初期値を持たず，初期値数は 0 となる．

ミングを持つ 2 つの離散時変値について，サンプリ

定時変値は連続時変値と同様に連続的に値を取りう

ング演算を用いて一方に他方のタイミングを合わせるが，いかなる時刻においても一定値 c をとる．このる処理を記述する際に有用である．そして匿名タイ

性質から，どのようなタイミングでサンプリングしてミングは，シーケンスの検査の過程で時変値のタイも，初期値が c であり全ての発生で c を返すようなミングを推定させる用途で記述する．これは例えば，離散時変値となる．したがって演算結果の初期値は v

入力時変値を定タイミングで与え，時変値合成によっの初期値と c によって計算できるため，得られる時変て出力時変値のタイミングが複雑になった場合，この値の初期値数は v と同じである．

タイミングを推定させる目的で用いることができる．

最後に，双方が離散時変値でない場合も考慮する必PbEmfrp の言語処理系はこれらの注釈から全ての時要がある．この場合の演算結果のシーケンスは，定時変値のシーケンスを決定し，得られたシーケンスの値

変値間の演算のみ ’const となり，そうでない場合はを用いてコード生成を行う．

’cont となる．これは，少なくとも一方が連続時変値である場合は演算結果の値が必ずしも一定ではなく

3. 4 構文なるためである．

簡単のため，PbEmfrp は構文のベースとして Em-

以上の議論より，2 つの時変値に関する演算から得frp のサブセットを用い，これに対して拡張を行う．

られる時変値のシーケンスは表 1 の通りである．た

形式的な構文定義および使用するメタ変数は，それぞだし 3 つ以上の時変値に関する演算については，以上

れ図 9 および図 7 を参照されたい．

のシーケンスの検査を最初の 2 項から順次適用する．

PbEmfrp のモジュールは，以下の 4 要素からなる．

v2(θ, l2 )’cont’const’never(θ, min(l1 , l2 )) * (θ, 0)(θ, l1 )’neverv1(θ, l1 )’cont(θ, 0)

’cont

’cont

’never’const(θ, l2 )’cont’const’never’never’never’never’never’never

*3. 4. 1 モジュール

タイミングが一致しない場合，’never を返す．
表1

v1 と v2 の演算におけるシーケンス• 入力時変値宣言• 出力時変値宣言• 時変値定義• 関数定義入出力時変値宣言では，PbEmfrp プログラムに対して入力および出力を行うタイミングをタイミング注
釈として記述する．時変値定義は Emfrp と異なり，初期値を複数設定することができる．

3. 3 タイミング注釈

3. 5 シーケンスの静的検査

タイミング注釈は，プログラマが入出力時変値の宣PbEmfrp プログラムにおける全ての時変値は各々言や離散時変値のサンプリングを行う際に用いる注

シーケンスが定まっている必要があるが，入出力時変釈であり，以下の 3 種類が存在する．

値を除いて，プログラマはこれを明示的に記述しな• 定タイミング

い．これは，入出力時変値やサンプリング演算に与え• 時変値へのタイミング参照られたシーケンス注釈をもとに PbEmfrp の処理系が• 匿名タイミング

シーケンスの静的検査を行うためである．そして全て

定タイミングは時変値のタイミングを直接与えるの時変値に対してシーケンスが決定した場合にのみ方法であり，周期的タスクの実行周期と実行開始時刻

コードを生成し，そうでない場合はコンパイルエラーこのように得られるのは，次の理由からである．ま

図7

v

:

時変値名

τ

:

データ型

π

:

実行周期

φ

:

初期オフセットl:

離散時変値の初期数

f

:

関数

n

:

整数

c

:

定数

q

:

タイミング注釈

PbEmfrp の各種定義で用いるメタ変数

ず，初期値数が l である時変値 v は，その l 回前の発生の値を参照するために l + 1 要素の配列 a に履歴を記録しておく必要がある．その記録の更新方法についてであるが，単純には i(∈ 1, . . . , l) 回前の発生の値を a[i] に保持するために，発生の値を計算す
る前に a[0] から a[l − 1] までの値を a[1] から a[l]までコピーすることで実現できる．しかしこれは vのサイズが大きい際にコピーコストが高くなってしまうため，最新の値を格納するインデックスおよび過去の発生の値を参照する際のインデックスをずらすことでコピーを回避する．つまり，i 回前の発生を生成する．PbEmfrp の各構文要素に対する形式的の値を示す初期値を a[i − 1] へ代入することで初期な型判断 (type judgements) と変数はそれぞれ図 11,化し，最新の値を格納するインデックス j を l とす

10 の通りであり，そこで利用される各種演算は図 8

る．以降は，各発生で v@last[i] の参照に対してはの通りである．

a[j + i (mod l + 1)] を返すようにして更新式を計算

4 コード生成し，a[j] に格納，その発生での処理が終わるとインデックスを j + l (mod l + 1)†1 に更新する．このよ4. 1 時変値の表現方法

うに実装することで，インデックス j のサイズは増え

Emfrp では，全ての時変値に対して現在のイテレー

るもののアクセスするインデックスを変更するのみ

ションの値と直前のイテレーションの値を長さ 2 ので履歴のコピーは不要になり，全体として必要なメモ配列として持たせ，各イテーションでアクセスする

リ使用量は v の型のサイズの l + 1 倍と，インデック

インデックスを 0 と 1 で切り替えることでプログラ

ス j のサイズの和となる．

ムを実行していた．これに対して PbEmfrp では，時変値 v が持つシーケンスによって必要なメモリ領域

4. 2 時変値の更新順序のサイズが異なる．まず v が連続時変値の場合，任PbEmfrp における時変値更新は，発生した時変値

意のタイミングでサンプリングされる可能性がある．

のインデックスの更新と現在値の更新からなる．イ

したがって必要なタイミングで値を取り出すことがンデックスを更新した直後は，現在値への参照が履歴できるよう，関数として生成する．次に v が定時変の中で最も古い発生における値を指すため，時変値の値の場合，これはいかなる時刻においても一定の値を更新を無秩序に行うと，プログラムに記述した通りの生成するため，コンパイル時に計算を行うことができ計算が行われなくなる可能性がある．そこで，過去のる．そのため，これは計算結果を示す定数として展開発生の値を参照しないような更新順序を決定する必される．最後に v が離散時変値の場合，必要なメモ

要がある．

リサイズ mv は以下で計算される．
mv = size(τv ) ∗ (lv + 1) + size(τInt )更新順序の決定方法と同様のアプローチをとることで

ただし，この問題は，Emfrp の言語処理系が行う時変値の解決できる．まず @last 参照を除く時変値間の参照

size(τ ):型τ のサイズにおける全ての依存関係を抽出し，これらに対してト

τv

:

v の型

ポロジカルソートを適用する．すると，循環した時変

Int

:

整数型

lv

:

v の初期値数†1 直感的には j − 1 に対する剰余だが，負になる場合を考慮して l + 1 を加えている．

minify(s1 , s2 )

=



s1







s2







(θ1 , 0)(θ2 , 0)






’cont






(θ, min(l1 , l2 ))






’never(s2 = ’const)(s1 = ’const)(s1 = (θ1 , l1 ) ∧ s2 = ’cont)(s2 = ’cont ∧ s2 = (θ2 , l2 ))(s1 = s2 = ’cont)(s1 = (θ, l1 ) ∧ s2 = (θ, l2 ))(otherwise)θ1 ≤ θ2:

θ2 の任意の発生時刻において，θ1 が発生している

θ 1 | θ2:

タイミングの合成

hv, ei ← vs:node v = e ∈ vs のとき，v, e の組を得る操作

hv, n, ei ← vs:node init[c1 , . . . , cn ] v = e ∈ vs のとき，v, n, e の組を得る操作

hf, ni ← fs:

func f (v1 , . . . , vn ) = e ∈ fs のとき，f, n の組を得る操作
図8

型判断に用いる演算

宣言

M

::=

(I, O, L, fs)(モジュール)I::=

in v1 : τ1 q1 , . . . , vn : τn qn(入時変値宣言)O::=

out v1 : τ1 q1 , . . . , vn : τn qn(出力時変値宣言)∗(時変値定義)

L

::=

vdef

vdef

::=

node v = e

fs

::=

|

(初期値無し時変値定義)

node init[c1 , . . . , cn ] v = e(func f (v1 , . . . , vn ) = e)∗

(初期値付き時変値定義)(関数定義)

タイミング注釈q::=|’(π, φ)(定タイミング)’v(時変値へのタイミング参照)’(匿名タイミング)

式

e

::=

c

(定数)

|

v

(時変値参照)|(e1 , . . . , en )

(タプル)

|

uop(e)(単項演算)|

bop(e1 , e2 )(二項演算)|

if e1 then e2 else e3

(条件式)

|

f (e1 , . . . , en )(関数適用)|

e @> q(サンプリング)|

e1 @< f >@ e2(時変値合成)|v@last[n](@last 参照)

|

v@now

(@now 参照)

図9

PbEmfrp の構文

環境

Σ

::=

∅ | Σ [v 7→ s](時変値環境)fs::=∅ | fs [func f (v1 , . . . , vn ) = e](関数環境)

vs::=∅ | vs [node v = e] | vs [node init[c1 , . . . , cn ] v = e]

(時変値定義環境)

変数

θ

::=

(π, φ)(タイミング)|(π, φ) | θ(タイミングの合成)

s::=(θ, l)(離散時変値)|’cont(連続時変値)|’const(定時変値)|’never(発生がないシーケンス)

図 10

シーケンスの型判断に用いる変数

値間の依存関係が記述されていない限りは有向非循ラマがタイマー割り込みの各種設定を行うことがで環グラフ (DAG) が得られる．この DAG はある時変きるようなコード生成を行う．このときタイマー割

値の計算にどの時変値の現在値が必要かを示したもり込みで実行するハンドラを設定する必要があるたのになっているため，DAG の根にあたる依存先を持め，割り込み時に行う処理が書かれた関数が参照できたない時変値から順に時変値更新を行なっていけば，るようになっている．

求めたい計算順序が得られる．

Emfrp では入力（出力）を行うタイミングが同期されており，全ての入力（出力）は同時に処理される．

4. 3 周期的タスクの実行方法

そのため，関数スケルトンはプログラムの入力（出周期的タスクは一度タスクの処理が完了すると次力）に対し 1 つしか提供されない．一方 PbEmfrp でにタスクを実行する時刻まで待機するが，この際にビは入出力を行うタイミングが同期されていないため，

ジーループを用いると計算資源を浪費してしまう．そ各時変値に対してそれぞれ独立に入出力を設定する

こで PbEmfrp では，記述された全ての時変値の発生必要がある．したがって，入力時変値と出力時変値のに応答できる最長の実行周期 T を言語処理系によっ個数だけ関数スケルトンを生成する．

て決定し，その周期を持つタイマー割り込みの設定を行うことでこの問題に対処する．

最後に，PbEmfrp が提供するプログラムでは各種の初期化後に割り込みを待機するための無限ループをところで，Emfrp プログラムでは 1 イテレーショ行う．このループも関数スケルトンとして提供されンを行う処理のみを記述し，それ自体には副作用をており，ここに記述することで待機時にハードウェア

持たない．外部デバイスからの読み取りや書き込み

依存の割り込み待ちを利用できる．ハードウェアにのようなハードウェア依存となり得る処理は，Emfrp

よってはタイマー割り込みを低消費電力状態で待機の言語処理系が提供する中身が空の関数スケルトンする機能が備わっており，例えば ARM アーキテク内に記述する．つまり Emfrp プログラム自体のコン

チャには割り込み命令を受け取るまで CPU を低消費

パイルはハードウェアに依存せず，関数スケルトンを電力状態へと移行する WFI (Wait for Interrupt) 命

変更するだけで同じイテレーションを行う実行可能

令が存在している．これをループ内で用いて各タイ

バイナリを生成することができる．PbEmfrp はタイ

マー割り込みを待機することにより，ビジーループを

マー割り込みを利用するが，このハードウェア依存の行う場合と比較して消費電力を抑えることができる．

処理も Emfrp 処理系のコード生成と同様に，プログ

以上をまとめると，PbEmfrp の処理系がプログラタイミング注釈

fs, Σ `e v : (θ, l)`q ’(π, φ) : (π, φ) (S-CONSTPERIOD)

fs, Σ `q ’v : θ(S-VARPERIOD)

fs, Σ `q ’ : θ(S-ANONYMOUSPERIOD)`e c : ’const(S-CONST)

式

fs, Σ `e v : (θ, l)0<n≤lfs, Σ `e v@last[n] : (θ, l − n)fs, Σ `e v : (θ, l)(S-LAST)hv : si ∈ Σ
fs, Σ `e e1 : s1

(S-NOW)fs, Σ `e v@now : ’contfs, Σ `e e : s(S-VAR)fs, Σ `e v : s

l>0(S-UOP)fs, Σ `e uop(e) : s

fs, Σ `e e2 : s2

minify(s1 , s2 ) 6= ’never(S-BOP)fs, Σ `e bop(e1 , e2 ) : minify(s1 , s2 )fs, Σ `q q : θ′fs, Σ `e e : (θ, l)θ ≤ θ′(S-SAMPLE)fs, Σ `e e @> q : (θ′ , l)fs, Σ `e e1 : (θ1 , l1 )hf, 2i ← fs

(S-MERGE)

fs, Σ `e e1 @< f >@ e2 : (θ1 | θ2 , 0)[]fs, Σ `e ei : si
minify(s1 , . . . , sn ) 6= ’neverhf, ni ← fs[

fs, Σ `e e2 : (θ2 , l2 )i∈{1,...,n}(S-APP)fs, Σ `e f (e1 , . . . , en ) : minify(s1 , . . . , sn )]fs, Σ `e ei : si
minify(s1 , . . . , sn ) 6= ’neveri∈{1,...,n}(S-TUPLE)fs, Σ `e (e1 , . . . , en ) : minify(s1 , . . . , sn )fs, Σ `e e1 : s1

fs, Σ `e e2 : s2

fs, Σ `e e3 : s3

minify(s1 , s2 , s3 ) 6= ’neverfs, Σ `e if e1 then e2 else e3 : minify(s1 , s2 , s3 )(S-IF)

宣言

hv, ei ← vsfs, Σ `e v : sfs, Σ `e e : ss 6= ’never(S-NODEDEF)

vs, fs, Σ `s node v = e
hv, n, ei ← vsfs, Σ `e v : (θ, n)

fs, Σ `e e : (θ, l)(S-NODEDEFINIT)

vs, fs, Σ `s node init[c1 , . . . , cn ] v = e
Σ′ = {hv1 : ’consti, . . . , hvn : ’consti}
hf, ni ← fs[

fs, Σ′ `e e : ’const(e は時変値演算を含まない)(S-FUNCDEF)

vs, fs, Σ `s func f (v1 , . . . , vn ) = e(vi , qi ) ∈ I[

fs, Σ `q qi : (πi , φi )fs, Σ `e vi : ((πi , φi ), 0)

vs, fs, Σ `s in v1 : τ1 q1 , . . . , vn : τn qn(vi , qi ) ∈ O

fs, Σ `q q : (πi , φi )fs, Σ `e v : ((πi , φi ), l)]i∈{1,...,n}i∈{1,...,n}(S-OUTDEF)

vs, fs, Σ `s out v1 : τ1 q1 , . . . , vn : τn qn
モジュール(∃vs ∃fs ∃Σ vs, fs, Σ `s I

vs, fs, Σ `s O

vs, fs, Σ `s L[

vs, fs, Σ `s f`m (I, O, L, fs)
図 11

シーケンスの静的検査のための型判断(S-INDEF)

]

]

)
f ∈fs

(S-MODULE)

マへ提供する関数は以下の通りである．

• タイマーの初期化を行う関数スケルトン• タイマー割り込みフラグの無効化を行う関数スケルトン• 割り込みハンドラの実装が記述された関数への参照• 割り込み待機を行う関数スケルトン• 各入力時変値を設定する関数スケルトン• 各出力時変値を設定する関数スケルトン
実際のコード例については，次章のケーススタディ
で述べる．

処理系は以下の 3 つのプログラムを生成する．

• ThermoHygrometer.cpp– 時変値に関する処理が記述された C++プ
ログラム

• ThermoHygrometer.h– 上記の 2 ファイル間での関数へのアクセスに用いるヘッダファイル• ThermoHygrometerMain.cpp– main 関数を含む，プログラマがハードウェ
ア依存の処理を記述する C++プログラム
それぞれのファイル名はモジュール名（ThermoHygrometer）から付けられる．以下、二つ5 ケーススタディの C++プログラムについて元のプログラムとの対応

ここでは PbEmfrp を用いた周期的タスクの実装を簡単に述べる．

方法と生成されるプログラムを述べていく．ただし，5. 2. 1 時変値に関する処理生成されるプログラムの言語は C++を仮定する．

ソースコード 4 および 5 は PbEmfrp のプログラ
ムにおける時変値計算やタイミングの処理などを実

5. 1 実装方法
温湿度計を PbEmfrp で実装した場合，ソースコー
ド 3 のようになる．

現したものである．
ソースコード 4 の 4 行目から 12 行目では，各時変値の情報を格納するグローバル変数が定義されていソースコード 2 と比べると，時間に関する時変値

る．既に 4. 1 節にて述べたとおり，各時変値には発生（...Timing）や出力のタイミングであることを知らの値を格納する配列と，最新の値を格納するインデッ

せる論理値の時変値（output...）がなくなっておクスの組として定義される．プログラムの経過時刻にり，代わりの記述としてサンプリング（16 行目，23

ついては，直接タイミングにある数値を使うのではな

行目）が追加されている．もう一つの出力時変値であく，時変値更新の実行間隔 (interval) と FRP ロジッ

る display についても，tmp や hmd のタイミングをクの実行開始時刻 oﬀset およびカウンタによって管理

merge によって組み合わせることで，それぞれの更新する．ただし，カウンタの値が n である時，プログラ

時に display が更新されるようになっている．ソー

ムが開始してからの経過時間は n ∗ interval + oﬀset

スコード 3 での時間に関する記述は入力時変値やサで計算されることに注意する．プログラム中においてンプリングに限られており，非常に見通しよく書けては，このカウンタは 14 行目の Counter によって定義いる．この意味で，PbEmfrp での記述が（少なくとされ，また interval と oﬀset は 39 行目の InitTimer

もある種の）周期的タスクの記述に有用であることが関数の引数として与えられている．

わかる．なお，merge のそれぞれのタイミングでは互

ソースコード 5 の 63 行目から 90 行目には，割りい違いに@now 参照を行っているが，このような（一込み処理が行われた際に実行する内容が ISR 関数と方の現在値参照と@now 参照を組み合わせる）処理はして定義されている．後述のプログラマが処理を記タイミングの合成を用いる際に頻出すると思われる

述する部分からは，この関数を呼ぶことで発生していため，より容易に記述できる構文を今後用意したい．

る時変値の更新処理が行われる．各時変値における発生の判定は ISR 関数内に記述されており，実際の5. 2 生成されるコードの概観
ソースコード 3 を C++ にコンパイルする場合，更新処理は 93 行目から 122 行目に記述されている．

1
2

module ThermoHygrometer

3

in

4
5
6
7
8
9

tmp: Float ’(5000, 5000), # 温 度
hmd: Float ’(3000, 5000) # 湿 度# 温度の移動平均# 湿度の移動平均# 温 度 ，湿 度 ，不 快 指 数 の 組

out sendTmpAve: Float ’_,
sendHmdAve: Float ’_,display: (Float, Float, Float) ’_

10
11
12
13
14
15
16
17

# 温度の移動平均の計算
node init[0.0, 0.0, 0.0] tmpHistory = tmp
node tmpAve = (tmpHistory+ tmpHistory@last[1]+ tmpHistory@last[2]+ tmpHistory@last[3]) / 4.0
node sendTmpAve = tmpAve @> ’(30000, 20000)

18
19
20
21
22
23
24

# 湿度の移動平均の計算
node init[0.0, 0.0] hmdHistory = hmd
node hmdAve = (hmdHistory+ hmdHistory@last[1]+ hmdHistory@last[2]) / 3.0
node sendHmdAve = hmdAve @> ’(30000, 18000)

25# 描画する情報の計算
func calcDisplay((tmp, hmd)) # デ ィ ス プ レ イ へ の 出 力 を 求 め る 関 数= (tmp, hmd, calcDi(tmp, hmd))
func calcDi(tmp, hmd)
# 不快指数を求める関数= 0.81 * tmp + 0.01 * hmd * (0.99 * tmp - 14.3) + 46.3
func makePair ((t1, h1), (t2, h2)) = (t1, h2)

26
27
28
29
30
31
32

node display = calcDisplay((tmp, hmdHistory@now) @< makePair >@ (tmpHistory@now, hmd))
ソースコード 3

ThermoHygrometer.pbm

5. 2. 2 ハードウェア依存の処理
4. 3 節で述べたように，タイマーの設定や各入出力

6 関連研究

時変値に対する処理のようなハードウェア依存の処

6. 1 Fran理は中身がほぼ空の関数スケルトンのみが提供され，Fran [3] は FRP の起源であり，ここでの時変値は連そこにプログラマ自身が処理を記述する．ソースコー

続的に変化する値を表現する Behavior と時刻と値のド 3 の場合，この関数群はソースコード 6 のように組を表現する Event という異なる 2 種類のプリミティ

なる．

ブから構成されている．このような時変値の表現方法

特に注意が必要なのは InitTimer 関数（23–29 行は Emfrp が持つものとは異なるが，本研究で提案する目）である．この関数はプログラムの初期化の一環とPbEmfrp における離散時変値・連続時変値の意味づ

して呼び出され（ソースコード 4 の 39 行目），プロ

けやシーケンスの検査は，Fran の Behavior と Event

グラム中の単位時間を作り出す処理になる．この関の意味論に大きく影響を受けている．たとえば，離散数でハードウェアタイマーなどを用いて ISR 関数が時変値と連続時変値間の二項演算は計算結果が離散時（ハンドラとして、または別途定義したハンドラから）変値として得られるという点は，Event の意味論にお呼び出されるよう設定することで，周期的な処理を実

ける snapshot : Event α → Behavior β → Event α×β

現する．つまり，プログラマはこの周期が適切に処理に対応している．

されるように InitTimer 関数を実装することで、全体の処理が進むことが保証される．

1
2#include "ThermoHygrometer.h"

3
4
5
6
7
8
9
10
11
12
13
14
15

// 時 変 値 を 管 理 す る グ ロ ー バ ル 変 数 の 宣 言
double node_tmp[1]; int node_tmp_index = 0;
double node_hmd[1]; int node_hmd_index = 0;
double node_tmpHistory[4]; int node_tmpHistory_index = 3;
double node_tmpAve[1]; int node_tmpAve_index = 0;
double node_sendTmpAve[1]; int node_sendTmpAve_index = 0;
double node_hmdHistory[3]; int node_hmdHistory_index = 2;
double node_hmdAve[1]; int node_hmdAve_index = 0;
double node_sendHmdAve[1]; int node_sendHmdAve_index = 0;
Tuple3DoubleDoubleDouble node_display[1]; int node_display_index = 0;

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

// ThermoHygrometerMain.cpp で 定 義 さ れ る 関 数 の 宣 言
extern int Input_tmp();extern int Input_hmd();extern void Output_sendTmpAve(double node_sendTmpAve);extern void Output_sendHmdAve(double sendHmdAve);extern void Output_display(Tuple3DoubleDoubleDouble display);extern void InitTimer(int offset_ms, int interval_ms);
extern void ResetInterruptFlag();[[noreturn]] extern void SleepCPU();void InitThermoHygrometer();void ActivateThermoHygrometer();void MainTask();
Tuple3DoubleDoubleDouble calcDisplay(Tuple2DoubleDouble);double calcDi(double, double);
Tuple2DoubleDouble makePair(Tuple2DoubleDouble, Tuple2DoubleDouble);

32
33
34
35
36

void InitThermoHygrometer() { // こ こ で 全 時 変 値 の 初 期 化 を 行 う
node_tmpHistory[1] = 0.0; node_tmpHistory[2] = 0.0; node_tmpHistory[3] = 0.0;
node_hmdHistory[1] = 0.0; node_hmdHistory[2] = 0.0;
}

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

// ThermoHygrometerMain.cpp の main 関 数 で 実 行 さ れ る 関 数[[noreturn]] void ActivateThermoHygrometer() {InitThermoHygrometer(); InitTimer(1000, 5000); SleepCPU();}

int Counter = 0;

double calcDi(double tmp, double hmd) {return 0.81 * tmp + 0.01 * hmd * (0.99 * tmp - 14.3) + 46.3;}
Tuple3DoubleDoubleDouble calcDisplay(Tuple2DoubleDouble tmp_hmd) {double tmp = tmp_hmd._0; double hmd = tmp_hmd._1;
return {tmp, hmd, calcDi(tmp, hmd)};}
Tuple2DoubleDouble makePair(Tuple2DoubleDouble t1_h1, Tuple2DoubleDouble t2_h2) {double t1 = t1_h1._0; double h1 = t1_h1._1; double t2 = t2_h2._0; double h2 = t2_h2._1;
return {t1, h2};}
double node_hmdHistory_now() { return node_hmdHistory[node_hmdHistory_index]; }double node_tmpHistory_now() { return node_tmpHistory[node_tmpHistory_index]; }ソースコード 4

ThermoHygrometer.cpp(前半)60
61bool cond_1; bool cond_2; bool cond_3; bool cond_4;

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

// 時 変 値 処 理 を 行 う 割 り 込 み ハ ン ド ラvoid ISR() {cond_1 = Counter >= 5 && (Counter - 5) % 5 == 0;
cond_2 = Counter >= 5 && (Counter - 5) % 3 == 0;
cond_3 = Counter >= 20 && (Counter - 20) % 30 == 0;
cond_4 = Counter >= 18 && (Counter - 18) % 30 == 0;
node_tmp_index = (node_tmp_index - cond_1 + 1) % 1;
node_tmpHistory_index = (node_tmpHistory_index - cond_1 + 4) % 4;
node_hmd_index = (node_tmpHistory_index - cond_2 + 1) % 1;
node_tmpAve_index = (node_tmpAve_index - cond_1 + 1) % 1;
node_sendTmpAve_index = (node_sendTmpAve_index - cond_3 + 1) % 1;
node_hmdHistory_index = (node_hmdHistory_index - cond_2 + 3) % 3;
node_hmdAve_index = (node_hmdAve_index - cond_2 + 1) % 1;
node_sendHmdAve_index = (node_sendHmdAve_index - cond_4 + 1) % 1;
node_display_index = (node_display_index - (cond_1 || cond_2) + 1) % 1;// 入 力 時 変 値 の 処 理
if (cond_1) { node_tmp[node_tmp_index] = Input_tmp(); }if (cond_2) { node_tmp[node_tmp_index] = Input_hmd(); }// 時 変 値 計 算
MainTask();// 出 力 時 変 値 の 処 理
if (cond_3) { Output_sendTmpAve(node_sendTmpAve[node_sendTmpAve_index]); }if (cond_4) { Output_sendHmdAve(node_sendHmdAve[node_sendHmdAve_index]); }if (cond_1 || cond_2) { Output_display(node_display[node_display_index]); }Counter++;// 割 り 込 み 許 可 フ ラ グ の リ セ ッ ト ．
ResetInterruptFlag();

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

}
// 入 力 時 変 値 か ら 出 力 時 変 値 の 計 算 を 行 う ．
void MainTask() {if (cond_1) {node_tmpAve[node_tmpAve_index] =(node_tmpHistory[node_tmpAve_index] +node_tmpHistory[(node_tmpAve_index + 1) % 4] +node_tmpHistory[(node_tmpAve_index + 2) % 4] +node_tmpHistory[(node_tmpAve_index + 3) % 4]) / 4.0;
if (cond_3) {node_sendTmpAve[node_sendTmpAve_index] = node_tmpAve[node_tmpAve_index];}}if (cond_2) {node_hmdAve[node_hmdAve_index] =(node_hmdHistory[node_hmdAve_index] +node_hmdHistory[(node_hmdAve_index + 1) % 3] +node_hmdHistory[(node_hmdAve_index + 2) % 3]) / 3.0;
if (cond_4) {node_sendHmdAve[node_sendHmdAve_index] = node_hmdAve[node_hmdAve_index];}}if (cond_1 || cond_2) {Tuple2DoubleDouble _tmp1 = {node_tmp[node_tmp_index], node_hmdHistory_now()};
Tuple2DoubleDouble _tmp2 = {node_tmpHistory_now(), node_hmd[node_hmd_index]};
Tuple2DoubleDouble _tmp3;
if (cond_1 && cond_2) { _tmp3 = makePair(_tmp1, _tmp2); }else if (cond_1) { _tmp3 = _tmp1; }else if (cond_2) { _tmp3 = _tmp2; }node_display[node_display_index] = calcDisplay(_tmp3);}}
ソースコード 5

ThermoHygrometer.cpp(後半)

1
2
3
4
5
6
7

#include "ThermoHygrometer.h"// PbEmfrp 処 理 系 に よ っ て 生 成 さ れ る ，// 時 変 値 計 算 を 行 う ISR 関 数 は こ こ で// 参 照 で き る た め ，割 り 込 み ハ ン ド ラ を// 設 定 す る 際 に 用 い る ．

記述なく共有状態を安全に扱うことができる点や，処理系はシングルスレッドで動作する C プログラムを生成するといった点が，組込みシステムにおける並行動作する複数のタスクの簡潔な実装を実現している．

int main() {// こ こ で 各 種 ハ ー ド ウ ェ ア の 初 期 化 を 行 う ．
ActivateThermoHygrometer();}

これに対して PbEmfrp では，複数の周期的タスクは

12
13
14
15
16

// 入 力 時 変 値 を 設 定 す る 処 理 を 記 述 す る ．
int Input_tmp() {}し，安全な状態共有を実現する．

int Input_hmd() {}することができ，副作用を単一のプログラム内で記述

17
18
19
20
21
22

// 出 力 時 変 値 を 用 い た 処 理 を 記 述 す る ．
void Output_sendTmpAve(double sendTmpAve) {}できる．しかし PbEmfrp はリアクティブシステム

void Output_sendHmdAve(double sendHmdAve) {}ようなプログラムの記述を強制している．これによvoid Output_display(Tuple3DoubleDoubleDouble
display) {}り，ハードウェア依存の副作用を持つ処理に関する移void InitTimer(int offset_ms, intinterval_ms) {// こ こ で タ イ マ ー の 初 期 化 を 行 う ．
//// 引 数 の offset と interval は PbEmfrp// の 処 理 系 に よ っ て 計 算 さ れ た タ イ マ ー 割 り// 込 み の 開 始 時 刻 と 周 期 で あ り ，こ の 値 を 用
いてプログラマが// タ イ マ ー を 初 期 化 す る ．
}

いる．

8
9
10
11

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

void ResetInterruptFlag() {// 設 定 さ れ た 割 り 込 み フ ラ グ を リ セ ッ ト す る
処理を記述する ．
// こ の 関 数 は 割 り 込 み ハ ン ド ラ の 最 後 に 呼 ばれる ．
}

それらの周期をサンプリングや@now 参照によって明
示的に同期させることで値の取得タイミングを指定

また，Céu はプログラム内で C 言語の API を利用

から副作用を分離して純粋なロジックを独立させる

植性の確保や，ロジック単体でのテストを容易にして

6. 3 Hae
Hae [7] は Haskell をホスト言語とする内部 DSLであり，C++プログラムを生成する．FRP プログラ
ム内に時変値の発生周期を指定することができ，そのコンパイラは各時変値の発生周期と時変値間の依存関係を抽出して解析することで，各イテレーションの周期を最適化している．PbEmfrp では同様の時間
解析を行なっているが，タイミングでは最初の発生時刻も考慮されているため，Hae のものより複雑な[[noreturn]] void SleepCPU() {while (true) {// 周 期 的 タ ス ク は 割 り 込 み ハ ン ド ラ 内 で 行われるため ，// こ こ に は 消 費 電 力 を 削 減 す る た め に ス リ
ープモードに入る// 処 理 等 を 記 述 す る こ と が で き る ．
}}
ソースコード 6

ThermoHygrometerMain.cpp

解析となっている．また，PbEmfrp では初期値を持つ離散時変値から連続的な時変値へと変換する@now
参照が存在しているため，異なる時変値に対して強制的に同期をとる手段が存在するが，Hae にはそのような方法が存在しない点が異なる．

7 結論と今後の課題
本研究では既存の組込みシステム向け FRP 言語で

6. 2 Céu

ある Emfrp に対してシーケンスの概念を導入し，構Céu [5] は組込みシステム向け手続き型言語であ

文・意味・型システム・コード生成の各種に拡張を与る．この言語では trail と呼ばれる手続きを定義し，える PbEmfrp を提案した．これにより，周期的タス

これら複数組み合わせてプログラムを記述する．複クの簡潔な記述と実行時に計算資源を浪費しないコー数の異なる trail は見かけ上スレッドを生成したかのド生成を可能にすることをケーススタディを通してように並行に実行されるが，異なる trail から複雑な確認した．

本稿では構文およびシーケンスに関する型判断について形式的に述べ，C++を対象としたコード生成の概要について述べたが，言語処理系の実装には至っておらず，これは今後の課題である．また PbEmfrp
は扱う時変値を周期的なものと仮定していたため，入出力の発生時刻が動的に決定されるような非周期的タスクと混合してのプログラムの記述においては，これを考慮したシーケンスの検査および実行モデルを提案できておらず，言語の拡張に余地がある．
謝辞 本研究の一部は JSPS 科研費 21K11822 および 19K20245 の助成を受けている．
参 考 文 献[1] Courtney, A., Nilsson, H., and Peterson, J.:
The Yampa Arcade, Proceedings of the 2003 ACM
SIGPLAN Workshop on Haskell, New York, NY,
USA, Association for Computing Machinery, 2003,
pp. 7–18.
[2] Czaplicki, E. and Chong, S.: Asynchronous Functional Reactive Programming for GUIs, 34th ACM
SIGPLAN Conference on Programming Language
Design and Implementation (PLDI 2013), ACM,
2013, pp. 411–422.

[3] Elliott, C. and Hudak, P.: Functional Reactive Animation, 2nd ACM SIGPLAN International
Conference on Functional Programming (ICFP
1997), ACM, 1997, pp. 263–273.
[4] Peterson, J., Hudak, P., and Elliott, C.: Lambda
in Motion: Controlling Robots with Haskell, Proceedings of the First International Workshop on
Practical Aspects of Declarative Languages, Berlin,
Heidelberg, Springer-Verlag, 1999, pp. 91––105.
[5] Sant’ Anna, F., Ierusalimschy, R., and Rodriguez,
N.: Structured Synchronous Reactive Programmingwith Céu, Proceedings of the 14th International
Conference on Modularity, MODULARITY 2015,
New York, NY, USA, Association for Computing
Machinery, 2015, pp. 29–40.
[6] Sawada, K. and Watanabe, T.: Emfrp: A Functional Reactive Programming Language for SmallScale Embedded Systems, Companion Proceedingsof the 15th International Conference on Modularity,
MODULARITY Companion 2016, New York, NY,
USA, Association for Computing Machinery, 2016,
pp. 36–44.
[7] Sheng, W. and Watanabe, T.: Functional Reactive EDSL with Asynchronous Execution for
Resource-Constrained Embedded Systems, Software Engineering, Artiﬁcial Intelligence, Networking and Parallel/Distributed Computing, Studies
in Computational Intelligence, Vol. 850, Springer,
Aug. 2019, pp. 171–190.