Reputation: 3900
I want to give a user an ability to run the program with import DryRun
first and then with import Do
if he thinks everything is correct.
So there is a module which does actual work:
doThis ∷ SomeStack ()
doThis = actuallyDoThis
...
doThat ∷ SomeStack ()
doThat = actuallyDoThat
and a module for shy user:
doThis ∷ SomeStack ()
doThis = liftIO $ putStrLn "DoThis"
...
doThat ∷ SomeStack ()
doThat = liftIO $ puStrlLn "DoThat"
The problem is I cannot be sure that interfaces in Do
and DryRun
are the same (compiler cannot help) and this mess is hard to maintain during development.
Are there any common idioms to solve this kind of problem?
Upvotes: 3
Views: 165
Reputation: 6610
You could reify the modules in question. That is, in a base module define a datatype:
module Base where
data MyModule = MyModule {
doThis_ :: SomeStack (),
doThat_ :: SomeStack ()
}
with the stuff you need each module to export (The reason for the underscores will become apparent soon).
Then you could in each of the modules define values of this type, e.g.:
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
I don't construct the MyModule
values using record syntax to make sure that if MyModule
changes, the type checker will start complaining in most cases.
In the client module you could do
module Client where
import DryRun
-- import Do -- uncomment as needed
doThis = doThis_ moduleImpl
doThat = doThat_ moduleImpl
-- do whatever you want here
Now you know that the same operations are exported by both modules. This is tedious and clunky, for sure, but since Haskell doesn't have first-class modules, you'll always have to work around the limitations of the module system. The good thing is that you only have to write the Base and Client modules once, and that you can start defining combinators operating on MyModule
values. E.g.
doNothing = MyModule (return ()) (return ())
addTracing impl = MyModule ((liftIO $ putStrLn "DoThis") >> doThis_ impl)
((liftIO $ putStrLn "DoThat") >> doThat_ impl)
will allow you to replace the DryRun
module implementation by addTracing doNothing
if I'm not mistaken.
Upvotes: 2