インチキデータ再帰と単位換算

[追記:データ再帰を使ったコードを書こうと思っていたのですが、出来上がったものはデータ再帰に放っていませんでした…とほほ…/追記]
また、データ再帰のことです…
Recursive Monadic Bindingsの論文などを眺めていると、よく出てくるのが電子回路のサンプルです。まだ使ったことはないのですが、どうやらmdoと電子回路の表現は相性が良いようです。

では、電子回路ではよくあって、プログラムではあんまりよくないものは何でしょう?ほかはよくわかりませんが、フィードバックループは電子回路ではよく出てきますね…そもそもループができていなくては電気が流れないので「回」路になりませんよね…

中学生のころは半田ごてを握ってキットの時計とか作ったりしてましたが、回路を細かいところまで理解するところまでは行きませんでした…まぁ、回路図を見ても体がかゆくならない程度には慣れていますが…フィードバックといって思い出す回路といえば、オペアンプです…アンプというぐらいですから何か増幅するのだろうということしか理解はないのですが、回路図を引っ張ってくると、こんな感じです:

相変わらずヘタクソな図なわけですが、真ん中の三角がオペアンプ、出力(Out)から入力(In)に向かってフィードバックのラインがありますね…

電子回路では頻出するフィードバックループなのですが、プログラムではオペアンプを関数と見立てるとなるほど再帰呼び出しをしているようにも見えなくはないですねぇ…関数の帰り値がまた関数へのパラメタとなってわたっていくわけです…fixの定義を見ているようでもあります:

 fix f = x where x = f x

ただし、いまだにfixは使いこなせません。何しろ関数f::a->aにパラメタをfixの外側から渡す方法がわからないわけです…

今日も懲りずにfixを手なづけようとごにょごにょやっていたのですが、結果としてfixは使っていないのですが、ちょっとだけ面白いものができました。

お題にしたのは割りと単純にメートルとセンチメートルの単位換算をやる関数です。時々アプリのダイアログなんかの中で、入力がいくつかあって、お互いみんな中でつながっていて、どれか一つに値を入れるとほかの値が自動的に決定するみたいなのありますよね…
Excelで数式を使うと入力セルを一つ決めれば似たようなことができるわけですが、ここでは全ての入力が同時に出力としての機能を持つところがひねりです。
メートルからセンチメートルは (* 100) という結線でつながって、更にセンチメートルからメートルが(/100)という結線でつながっているループを形成しているとも言えるようないえないような…?

単位の換算をする関数を

mcm :: (Float, Float) -> (Float -> Float)

というシグネチャにしてみました。ねらいは入力と出力のタプルをデータ再帰でつなげてやることです。fstがメートルの値をsndがセンチメートルの値をそれぞれ担当します。

いくつかトライして無限再帰を経験した後に動くようになったのが次のようなコードです:

module Main
	where

import Debug.Trace

mcm :: (Float, Float) -> (Float, Float)
mcm (m, cm) = trace "mcm" 
	(trace "cm" $ cm / 100, trace "m" m * 100)

toM i = m
	where
		(m, _) = mcm (m, i)

toCm i = cm
	where
		(_, cm) = mcm (i, cm)
		
main = do
	print "toM 100"
	print $ toM 100
	print "toCm 1"
	print $ toCm 1

コードの挙動を確認するためにmcmにはtraceがいっぱい入ってますが、要は:

mcm :: (Float, Float) -> (Float, Float)
mcm (m, cm) = (cm / 100, m * 100)

ということで、メートルとセンチメートルの関係をcm->mとm->cmという2つの違う形で繰り返しただけです。当初の目論見どおりには行かずmcmの中ではデータ再帰が使われていませんが、動いてくれただけで万々歳です。変わりにtoMとtoCmの中でデータ再帰が使われています…

実行結果は以下のとおり:

"toM 100"
mcm
cm
1.0
"toCm 1"
mcm
m
100.0

面白いのはトレースでもわかるように、mcmではm->cmとcm->mの2つの計算が指定されているのにtoMもtoCmもそのどちらか一つしか実行しないことですね…toMやtoCmで_を使って必要のない値をマスクしていることで実現しています。ここをiに置き換えると無限再帰に陥ります。

データ再帰の振る舞いはなんだか不思議ですね…無限再帰と正常動作の間を行ったりきたりするのでデバッグがちょっと大変ですが…

今日はこの辺で。
ではでは。