您可以具体化有问题的模块。也就是说,在基本模块中定义一个数据类型:
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 模块,并且您可以开始定义对MyModule
值进行操作的组合器。例如
doNothing = MyModule (return ()) (return ())
addTracing impl = MyModule ((liftIO $ putStrLn "DoThis") >> doThis_ impl)
((liftIO $ putStrLn "DoThat") >> doThat_ impl)
如果我没记错的话,将允许您替换DryRun
模块实现。addTracing doNothing