問題のモジュールを具体化できます。つまり、基本モジュールでデータ型を定義します。
module Base where
data MyModule = MyModule {
doThis_ :: SomeStack (),
doThat_ :: SomeStack ()
}
各モジュールをエクスポートする必要があるもの (アンダースコアの理由はすぐに明らかになります)。
次に、各モジュールでこのタイプの値を定義できます。たとえば、次のようになります。
module DryRun(moduleImpl) where
import Base
moduleImpl :: MyModule
moduleImpl = MyModule doThis doThat
-- doThis and doThat defined as above, with liftIO . putStrLn
module Do(moduleImpl) where
import Base
moduleImpl :: MyModule
moduleImpl = MyModule doThis doThat
-- doThis and doThat defined as above, where the real work gets done
MyModule
レコード構文を使用して値を構築しないのは、MyModule
変更された場合に型チェッカーがほとんどの場合に文句を言い始めることを確認するためです。
クライアントモジュールでできること
module Client where
import DryRun
-- import Do -- uncomment as needed
doThis = doThis_ moduleImpl
doThat = doThat_ moduleImpl
-- do whatever you want here
これで、同じ操作が両方のモジュールによってエクスポートされることがわかりました。確かにこれは面倒で扱いにくいですが、Haskell にはファーストクラスのモジュールがないため、常にモジュール システムの制限を回避する必要があります。良い点は、Base モジュールと Client モジュールを 1 回記述するだけでよく、MyModule
値を操作するコンビネータの定義を開始できることです。例えば
doNothing = MyModule (return ()) (return ())
addTracing impl = MyModule ((liftIO $ putStrLn "DoThis") >> doThis_ impl)
((liftIO $ putStrLn "DoThat") >> doThat_ impl)
私が間違っていなければ、DryRun
モジュールの実装を に置き換えることができます。addTracing doNothing