3

我想让用户能够先运行程序,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是相同的(编译器无能为力),而且这种混乱在开发过程中很难维护。
有没有常见的成语来解决这类问题?

4

1 回答 1

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

于 2012-04-28T15:01:46.760 回答