在Haskell中处理全局标志的正确方法

Pet*_*ris 14 haskell command-line-arguments

我经常需要制作一个在某些地方以某种方式可配置使用的核心功能 - 即,它可以使用算法A或算法B,具体取决于命令行开关; 如果以某种方式设置'debug'标志,或者让它向stdout输出额外的详细信息.

我该如何实现这样的全局标志?

我看到4个选项,所有选项都不是很好.

  1. 从函数中读取命令行参数 - 坏,因为需要IO monad并且核心计算函数都是纯的,我不想在那里得到IO;

  2. 将一个参数从main/IO一直传递到需要改变行为的'leaf'函数 - 完全不可用,因为这意味着改变不同模块中的十几个不相关的函数来传递这个参数,我想试试这样的配置选项多次,而不是每次都更改包装代码;

  3. 使用unsafePerformIO得到一个真正的全局变量-感觉丑陋和矫枉过正这样一个简单的问题;

  4. 在函数中间右侧有两个选项的代码,并将其中一个注释掉.或者使用do_stuff_A和do_stuff_B函数,并根据全局函数的含义更改调用​​哪一个函数needDebugInfo=True.这就是我现在正在做的事情debuginfo,但它无法通过重新编译来改变,它不应该是最好的方式......

我不需要或想要全局可变状态 - 我希望有一个简单的全局标志,它在运行时是不可变的,但可以在程序启动时以某种方式设置.有什么选择吗?

Don*_*art 16

这些天来,我更喜欢使用一个Reader单子构建应用程序的只读状态.环境在启动时初始化,然后在整个程序的顶层可用.

一个例子是xmonad:

newtype X a = X (ReaderT XConf (StateT XState IO) a)
    deriving (Functor, Monad, MonadIO, MonadReader XConf)
Run Code Online (Sandbox Code Playgroud)

该程序的顶级部分运行X而不是IO; XConf数据结构在哪里由命令行标志(和环境变量)初始化.

XConf然后可以将状态作为纯数据传递给需要它的函数.使用newtype派生,您还可以重用所有MonadReader代码来访问状态.

这种方法保留了2的语义纯度,但是由于monad完成了管道工作,因此可以减少编写代码.

我认为它是"真正的"Haskell做只读配置状态的方式.

-

unsafePerformIO当然,用于初始化全局状态的方法也可以工作,但最终会使您感到困惑(例如,当您使程序同时或并行时).他们也有搞笑的初始化语义.


ehi*_*ird 10

您可以使用Readermonad获得与在任何地方传递参数相同的效果.与普通的功能代码相比,应用风格可以使开销相当低,但它仍然很尴尬.这是配置问题最常见的解决方案,但我发现它并不令人满意; 实际上,明确地传递参数通常不那么难看.

另一种方法是反射包,它允许您通过类型类上下文传递这样的常见配置数据,这意味着您的代码不得更改为检测附加值,仅检测类型.基本上,您为程序中的每个输入/结果类型添加一个新的类型参数,以便在某个配置的上下文中运行的所有内容都具有与其类型中的该配置相对应的类型.该类型会阻止您使用多个配置意外混合值,并允许您在运行时访问关联的配置.

这样可以避免以适用的方式编写所有内容的开销,同时仍然是安全的,并允许您混合多个配置.它比声音简单得多; 这是一个例子.

(完全披露:我已经参与了反思包.)


小智 4

我们新的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