我想让用户首先使用import DryRun
然后使用import Do
来运行程序,如果他认为一切正确的话。
所以有一个模块可以完成实际工作:
doThis ∷ SomeStack ()
doThis = actuallyDoThis
...
doThat ∷ SomeStack ()
doThat = actuallyDoThat
和害羞用户的模块:
doThis ∷ SomeStack ()
doThis = liftIO $ putStrLn "DoThis"
...
doThat ∷ SomeStack ()
doThat = liftIO $ puStrlLn "DoThat"
问题是我无法确定Do
和DryRun
中的接口是否相同(编译器无法帮助),并且在开发过程中很难保持这种混乱。
是否有任何常用的习惯用来解决这类问题?
答案 0 :(得分:2)
您可以重新验证相关模块。也就是说,在基本模块中定义数据类型:
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
。