我经常需要做的是在很多地方用某种方式配置的核心功能 - 也就是说,它可以使用两种算法A或根据命令行开关算法B; 或者它打印额外详细信息,如果一个“调试”标志设置莫名其妙地到stdout。
我应该如何实现这样的全球性的标志吗?
我看到4个选项,所有的人都没有真正的好。
从功能读取命令行参数 - 糟糕,因为需要IO单子和核心计算功能都是纯净的,我不想在那里得到IO;
从主/ IO传递参数,一路过关斩将到需要改变行为的“叶”功能 - 完全无法使用,因为这意味着改变在不同的模块无关的功能,一打就通过这个参数,我想尝试这样的配置选项多次不用每次都改变包装的代码;
使用unsafePerformIO
得到一个真正的全局变量-感觉丑陋和矫枉过正这样一个简单的问题;
就在函数中有两个选项的代码和评论他们中的一个了。 或有功能do_stuff_A和do_stuff_B,并改变它们中的哪一个是根据什么是全局函数叫做needDebugInfo=True
说。 这就是我现在正在做的事情为debuginfo
,但它不能改变W / O重新编译,它不应该真的是最好的方式...
我不需要或不想要全局可变状态 - 我想有一个简单的全局标志,这是在运行时不变,但可以在程序启动时被莫名其妙定置。 是否有任何选项?
我们的新HFlags库正是这一点。
如果你想看到一个例子使用像你的榜样,看看这个:
https://github.com/errge/hflags/blob/master/examples/ImportExample.hs
https://github.com/errge/hflags/blob/master/examples/X/B.hs
https://github.com/errge/hflags/blob/master/examples/X/Y_Y/A.hs
需要各模块之间没有那种参数传递的,你可以用一个简单的语法定义新的标志。 它采用unsafePerformIO内部,但我们认为它是一种安全的方式,你将不必与关心自己。
有过这样的事一篇博客文章在: http://blog.risko.hu/2012/04/ann-hflags-0.html
这些天来,我更喜欢使用一个Reader
单子构建应用程序的只读状态。 环境在启动时initalized,然后提供整个方案的顶层。
一个例子是xmonad :
newtype X a = X (ReaderT XConf (StateT XState IO) a)
deriving (Functor, Monad, MonadIO, MonadReader XConf)
在程序运行中的顶层部分X
而不是IO
; 其中XConf
是通过命令行标志(和环境变量)initalized的数据结构。
所述XConf
状态然后可以作为纯数据对需要它的功能被传递。 随着NEWTYPE获得你也可以得到重用所有MonadReader代码,用于访问状态。
这种方法保留了2语义纯度,但给你少写代码,因为单子做管道。
我认为它的“真实”哈斯克尔的方式做只读的配置状态。
-
使用该方法unsafePerformIO
初始化的全局状态也行,当然,但(当你使你的程序的并发或并行EG)往往最终咬你。 他们也有有趣的初始化语义 。
您可以使用Reader
单子来获得相同的效果传递参数到处。 应用型风格可以让比较正常的功能代码的开销相当低,但它仍然可以是相当尴尬的。 这是对配置问题最常见的解决方案,但我并不觉得非常满意; 事实上,围绕传递参数明确往往是不太难看。
另一种是反射包,它可以让你通过类型类上下文通过周围像这样围绕共同的配置数据,这意味着没有你的代码必须改变下探的附加价值,只有类型。 基本上,你一个新的类型参数添加到程序中的每个输入/结果类型,让一切有一定配置的范围内操作具有对应于在类型配置类型。 使用多个配置该类型停止不小心混合值,并给出了在运行时访问该相关联的配置。
这避免了在编写应用性款式应有尽有,同时仍然安全,使您可以混合多种配置的开销。 这是一个简单了很多比它的声音; 这里有一个例子 。
(全discloure:我上的反射封装工作。)
另一种选择是GHC隐含参数 。 这些给你选择的痛苦少的版本(2):中间类型签名被感染,但你没有改变任何中间代码。
下面是一个例子:
{-# LANGUAGE ImplicitParams #-}
import System.Environment (getArgs)
-- Put the flags in a record so you can add new flags later
-- without affecting existing type signatures.
data Flags = Flags { flag :: Bool }
-- Leaf functions that read the flags need the implicit argument
-- constraint '(?flags::Flags)'. This is reasonable.
leafFunction :: (?flags::Flags) => String
leafFunction = if flag ?flags then "do_stuff_A" else "do_stuff_B"
-- Implicit argument constraints are propagated to callers, so
-- intermediate functions also need the implicit argument
-- constraint. This is annoying.
intermediateFunction :: (?flags::Flags) => String
intermediateFunction = "We are going to " ++ leafFunction
-- Implicit arguments can be bound at the top level, say after
-- parsing command line arguments or a configuration file.
main :: IO ()
main = do
-- Read the flag value from the command line.
commandLineFlag <- (read . head) `fmap` getArgs
-- Bind the implicit argument.
let ?flags = Flags { flag = commandLineFlag }
-- Subsequent code has access to the bound implicit.
print intermediateFunction
如果你运行这个程序有说法True
它打印We are going to do_stuff_A
; 有说法False
它打印We are going to do_stuff_B
。
我觉得这个方法类似于在另一个答复中提到的反射包 ,而且我认为在接受的答案中提到HFlags可能是一个更好的选择,但我加入这个答案的完整性。