在haskell中存储一些状态的任务的设计模式是什么?

在haskell中存储一些状态的任务的设计模式是什么? 例如,我想用haskell编写库,它提供配置文件读取并将配置参数存储在内存中。

例如:

我有配置文件。 配置文件的语法现在不重要。 我读配置文件,解析它到一些haskell数据结构。 接下来我想从我的程序中使用这个库来获取config的参数。 我们在haskell中没有全局变量。 我不希望每次都会读取和解析配置的调用函数。 我希望读一次配置,而不是多次获得参数。

对于Haskell中的这些类型的问题,存在哪些常见的做法?

谢谢。


有两种解决方案,我将使用一个示例程序来演示它们。 我们以下面的简单配置文件为例:

-- config.hs

data Config = Config { firstName :: String, lastName :: String }
    deriving (Read, Show)

让我们将其加载到ghci以生成一些快速示例文件:

$ ghci config.hs
>>> let config = Config "Gabriel" "Gonzalez"
>>> config
Config {firstName = "Gabriel", lastName = "Gonzalez"}
>>> writeFile "config.txt" config
>>> ^D

现在让我们定义一个读入此配置文件并打印它的程序:

-- config.hs
data Config = Config { firstName :: String, lastName :: String }
    deriving (Read, Show)

main = do
    config <- fmap read $ readFile "config.txt" :: IO Config
    print config

让我们确保它的工作原理:

$ runhaskell config.hs
Config {firstName = "Gabriel", lastName = "Gonzalez"}

现在,让我们修改程序来打印名字,尽管以一种人为的方式。 以下程序演示了配置传递的第一种方法:将配置作为普通参数传递给需要它的函数。

-- config.hs
data Config = Config { firstName :: String, lastName :: String }
    deriving (Read, Show)

main = do
    config <- fmap read $ readFile "config.txt" :: IO Config
    putStrLn $ pretty config

pretty :: Config -> String
pretty config = firstName config ++ helper config

helper :: Config -> String
helper config = " " ++ lastName config

这是最轻的方法。 但是,有时候所有那些手动参数传递对于非常大的程序来说都会变得乏味。 幸运的是,有一个monad负责为您传递参数,称为Reader monad。 你给它一个“环境”,比如我们的config变量,它将这个环境作为Reader monad中的任何函数都可以访问的只读变量传递。

以下程序演示了如何使用Reader monad:

-- config.hs

import Control.Monad.Trans.Reader -- from the "transformers" package

data Config = Config { firstName :: String, lastName :: String }
    deriving (Read, Show)

main = do
    config <- fmap read $ readFile "config.txt" :: IO Config
    putStrLn $ runReader pretty config

pretty :: Reader Config String
pretty = do
    name1 <- asks firstName
    rest  <- helper
    return (name1 ++ rest)

helper :: Reader Config String
helper = do
    name2 <- asks lastName
    return (" " ++ name2)

请注意,我们只在调用runReader地方只传递一次config变量,并且该例程中的每个函数都可以像使用只读全局变量一样使用askasks函数来访问它。 同样,注意当pretty调用helper ,它不需要将config作为参数传递给helperReader monad会在后台自动为您执行此操作。

强调Reader monad不使用任何副作用来做到这一点很重要。 Reader monad翻译为一个纯粹的函数,它只是在第一个示例中以手动方式绕过参数传递参数。 它只是为我们自动化这个过程,所以我们不必这样做。

如果你是Haskell的新手,那么我推荐第一种方法来学习如何使用参数传递来移动信息。 如果你了解它是如何工作的以及它如何自动传递给你的参数,那么我只会使用Reader monad,否则如果事情出错了,你将不知道如何解决它。

你可能想知道为什么我没有提到IORef作为传递全局变量的方法。 之所以这样,是因为即使你定义了一个IORef引用来保存你的变量,你仍然必须传递IORef本身,以便下游函数能够访问它,所以你通过使用IORef s就没有任何IORef 。 与主流语言不同,Haskell强制每个函数都声明从哪里获取信息,无论它是一个普通参数:

foo :: Config -> ...

...或作为Reader monad:

bar :: Reader Config ...

......或作为一个可变的参考:

baz :: IORef Config -> IO ...

这是一件好事,因为这意味着您可以随时检查某个功能并了解它提供的信息,更重要的是,它没有可用的信息。 这使调试函数更容易,因为函数的类型总是明确定义函数依赖的所有内容。


那么,一种做法可能是在配置器中使用的。 博客文章仅介绍了如何使用它的概述。 您可以深入研究实施,以了解可能适用于您的项目的内容。

链接地址: http://www.djcxy.com/p/43243.html

上一篇: What are design patterns for tasks with storing some state in haskell

下一篇: Should I avoid using Monad fail?