更喜欢 Reader monad 而不是直接将环境作为参数传递

Preferring Reader monad over directly passing the environment as parameters

我在 Haskell 中使用库 Servant 和 Opaleye 编写了一个基本的 CRUD 应用程序。

Servant 设置 API 端点和 Opaleye 将数据存储在数据库中。

假设有一个端点 GET /users,它 returns 来自数据库的所有用户的列表,另一个端点 POST /user 创建一个新用户并将其保存在数据库中。

程序通过启动与数据库的连接,然后将此连接作为参数传递给这些 API 端点函数(使用 Servant 设置)作为参数。

有人向我推荐更好的方法是使用 Reader Monad 并将连接存储在环境中。

我能够做到,但我不明白为什么 Reader Monad 是共享环境而不是直接传递参数的首选方式。

P.S。 - 作为 Haskell 的初学者,我可以使用 Monads,遵循教程并编写我的程序 运行,但我并不真正了解它们背​​后隐藏的美丽数学。这就是为什么,我想避免使用 monad(直到我完全理解 monad 背后的想法)。

这是我的 code,顺便说一句。

  1. Monad Reader 更方便,当你想将参数传递到调用堆栈的更深层次时。

  2. Monad Reader 简化代码 change/extension。假设你想从数据库中获取一些 Foo 类型的值,更新它(以不纯的方式)并将它存储回去。这里有两个版本,Reader 和显式参数传递。

    data Foo = ...
    modifyFoo :: Foo -> IO Foo
    type Handler a = Reader Connection IO a
    
    fetch1 :: Connection -> Int -> IO Foo
    fetch2 :: Int -> Handler Foo
    
    store1 :: Connection -> Foo -> IO ()
    store2 :: Foo -> Handler ()
    
    modify1 :: Connection -> Int -> IO ()
    modify1 conn key = do
      prev <- fetch1 conn key
      new  <- modify prev
      store1 conn new
    
    modify2 :: Int -> Handler ()
    modify2 key = do
      prev <- fetch2 key
      new  <- liftIO $ modify prev
      store2 new
    
    -- for brave souls
    modify2' :: Int -> Handler ()
    modify2' = fetch2 >=> liftIO . modify >=> store2
    

如果有一天 fetch2store2 将参数从 Connection 更改为其他内容(或更大),您只需更新 Handler 类型别名,modify2 保留相同的。 如果 modify1Connection 在类型签名中是显式的,您也必须更改它。

Reader的另一个用法示例 我建议 xmonad window 经理。某处有 XConfig 数据类型 在 X monad 的内部,但大多数时候我不想知道它,不要管它。