Haskell中处理全局标志的正确方法
我经常需要使一个在许多地方使用的核心函数以某种方式可配置——即,它可能使用算法a或算法B,具体取决于命令行开关;或者,如果以某种方式设置了“调试”标志,则让它将额外的详细信息打印到标准输出 我应该如何实现这样的全局标志 我看到4种选择,但都不是很好Haskell中处理全局标志的正确方法,haskell,command-line-arguments,Haskell,Command Line Arguments,我经常需要使一个在许多地方使用的核心函数以某种方式可配置——即,它可能使用算法a或算法B,具体取决于命令行开关;或者,如果以某种方式设置了“调试”标志,则让它将额外的详细信息打印到标准输出 我应该如何实现这样的全局标志 我看到4种选择,但都不是很好 从函数中读取命令行参数-不好,因为这需要IO monad,而且核心计算函数都是纯函数,我不想让IO在其中 将一个参数从main/IO一直传递到需要更改行为的“leaf”函数-完全不可用,因为这意味着在不同模块中更改十几个不相关的函数来传递此参数,我希
unsafePerformIO
获得一个真正的全局变量-对于这样一个简单的问题,感觉很难看,而且有点过分函数中间有两个选项的代码,并注释其中之一。或者让函数执行任务A和任务B,并根据全局函数
needDebugInfo=True
的内容更改调用其中哪一个。这就是我现在为debuginfo
所做的,但是它不能通过重新编译来更改,而且它不应该是最好的可用方式
我不需要也不想要全局可变状态——我想要一个简单的全局标志,它在运行时是不可变的,但可以在程序启动时以某种方式设置。有什么选择吗?现在,我更喜欢用它来构造应用程序的只读状态。环境在启动时被初始化,然后在程序的顶层可用 例如: 程序的顶层部分在
X
中运行,而不是在IO
中运行;其中,XConf
是由命令行标志(和环境变量)初始化的数据结构
然后可以将XConf
状态作为纯数据传递给需要它的函数。使用newtype派生,您还可以重用所有Monader代码来访问状态
这种方法保留了2的语义纯度。但是给你更少的代码来编写,就像monad做管道一样
我认为这是Haskell实现只读配置状态的“真正”方法
--
当然,使用
unsafePerformIO
初始化全局状态的方法也会起作用,但最终会对您产生不利影响(例如,当您使程序并发或并行时)。它们也有。您可以使用阅读器
monad获得与到处传递参数相同的效果。与普通的函数代码相比,应用程序风格可以使开销相当低,但它仍然非常笨拙。这是配置问题最常见的解决方案,但我并不觉得它非常令人满意;事实上,显式传递参数通常不那么难看
另一种选择是包,它允许您通过typeclass上下文传递像这样的公共配置数据,这意味着您的代码不必更改以垂直于附加值,只需类型。基本上,您可以向程序中的每个输入/结果类型添加一个新的类型参数,以便在特定配置的上下文中操作的所有内容都具有与其类型中的配置对应的类型。该类型阻止您使用多个配置意外混合值,并允许您在运行时访问关联的配置
这避免了以应用程序风格编写所有内容的开销,同时仍然是安全的,并允许您混合使用多个配置。这比听起来简单得多;给你
(全屏显示:我正在制作反射软件包。)我们的新库正是为了实现这一点
如果您希望看到类似示例的用法,请查看以下内容:
模块之间不需要任何类型的参数传递,您可以使用简单的语法定义新标志。它在内部使用unsafePerformIO,但我们认为它是以一种安全的方式使用的,您不必担心这一点
有一篇关于这件事的博文在:另一个选择是。这些选项提供了一个不那么痛苦的版本(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
{-#语言隐式参数}
导入System.Environment(getArgs)
--将标志放入记录中,以便以后可以添加新标志
--不影响现有的类型签名。
数据标志=标志{flag::Bool}
--读取标志的叶函数需要隐式参数
--约束“(?标志::标志)”。这是合理的。
leafFunction::(?flags::flags)=>String
leafFunction=if flag?flags然后是“do_stuff_A”或“do_stuff_B”
--隐式参数约束会传播到调用方,因此
--中间函数也需要隐式参数
--约束。这很烦人。
中间函数::(?标志::标志)=>字符串
mediateFunction=“我们将使用”++leaftfunction
--隐式参数可以在顶层绑定,比如在
--正在分析命令行参数或配置文件。
main::IO()
main=do
--从命令行读取标志值。
命令行标志
{-# 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