0xA*_*xAX 4 state haskell design-patterns
在haskell中存储某些状态的任务的设计模式是什么?例如,我想用haskell编写库,它提供配置文件读取并在内存中存储配置参数.
例如:
我有配置文件.配置文件的语法现在并不重要.我读取配置文件,将其解析为一些haskell数据结构.接下来我想从我的程序中使用这个库从config获取参数.我们在haskell中没有全局变量.我不希望每次都会读取和解析配置的调用函数.我想读取配置一次,而不是多次获取params.
在haskell中存在这些类型问题的常见做法是什么?
谢谢.
Gab*_*lez 13
有两种解决方案,我将使用示例程序来演示它们.我们使用以下简单配置文件作为示例:
-- config.hs
data Config = Config { firstName :: String, lastName :: String }
deriving (Read, Show)
Run Code Online (Sandbox Code Playgroud)
让我们加载它ghci以生成一些快速示例文件:
$ ghci config.hs
>>> let config = Config "Gabriel" "Gonzalez"
>>> config
Config {firstName = "Gabriel", lastName = "Gonzalez"}
>>> writeFile "config.txt" config
>>> ^D
Run Code Online (Sandbox Code Playgroud)
现在让我们定义一个读取此配置文件并打印它的程序:
-- config.hs
data Config = Config { firstName :: String, lastName :: String }
deriving (Read, Show)
main = do
config <- fmap read $ readFile "config.txt" :: IO Config
print config
Run Code Online (Sandbox Code Playgroud)
让我们确保它有效:
$ runhaskell config.hs
Config {firstName = "Gabriel", lastName = "Gonzalez"}
Run Code Online (Sandbox Code Playgroud)
现在,让我们修改程序以打印出名称,尽管是以一种人为的方式.以下程序演示了配置传递的第一种方法:将配置作为普通参数传递给需要它的函数.
-- 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
Run Code Online (Sandbox Code Playgroud)
这是最轻量级的方法.但是,对于非常大的程序,有时所有手动参数传递都会变得乏味.幸运的是,有一个monad负责处理参数传递,称为Readermonad.您给它一个"环境",例如我们的config变量,它将该环境作为只读变量传递给Readermonad 中的任何函数.
以下程序演示了如何使用Readermonad:
-- 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)
Run Code Online (Sandbox Code Playgroud)
注意我们如何只config在我们调用的位置传递一次变量runReader,并且该例程中的每个函数都可以使用ask或asks函数访问它,就像只读全局变量一样.同样,请注意当pretty调用时helper,它不需要再config作为参数传递helper.该Reader单子自动完成,对你的背景.
重要的是要强调Readermonad不会使用任何副作用来做到这一点.该Reader单子转化为刚刚绕过手动参数我们在第一个例子之前做同样的方式在引擎盖下一个纯函数.它只是为我们自动化这个过程,所以我们不必这样做.
如果您是Haskell的新手,那么我建议第一种方法来练习学习如何使用参数传递来移动信息.我只会使用Readermonad,如果你理解它是如何工作的,以及它如何自动传递给你的参数,否则如果出现问题,你将不知道如何解决它.
您可能想知道为什么我没有提到IORefs作为传递全局变量的方法.原因是,即使你定义了一个IORef引用来保存你的变量,你仍然必须绕过它IORef自己,以便下游函数能够访问它,所以你不能通过使用IORefs 获得任何东西.与主流语言不同,Haskell强制每个函数声明它从哪里获取信息,无论它是否作为普通参数:
foo :: Config -> ...
Run Code Online (Sandbox Code Playgroud)
......或作为Readermonad:
bar :: Reader Config ...
Run Code Online (Sandbox Code Playgroud)
......或作为可变参考:
baz :: IORef Config -> IO ...
Run Code Online (Sandbox Code Playgroud)
这是一件好事,因为这意味着您可以随时检查一个功能并了解它可用的信息,更重要的是,它可以提供哪些信息.这使得调试函数变得更容易,因为函数的类型总是显式定义函数所依赖的所有东西.