【问题标题】:Preferring Reader monad over directly passing the environment as parameters更喜欢 Reader monad 而不是直接将环境作为参数传递
【发布时间】:2018-11-25 11:55:30
【问题描述】:

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

Servant 设置 API 端点,Opaleye 将数据存储在 DB 中。

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

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

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

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

附: - 作为 Haskell 的初学者,我可以使用 Monads,按照教程运行我的程序,但我并不真正了解它们背​​后隐藏的美丽数学。这就是为什么,我想避免使用单子(直到我完全理解单子背后的想法)。

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

【问题讨论】:

  • 切线注释:“这就是为什么,我想避免使用单子(直到我完全理解单子背后的想法)。” -- 我建议你改用它们。美丽的数学可以等待。
  • 并非所有有经验的haskellers都喜欢使用Reader来传递参数:reddit.com/r/haskell/comments/8p6rjv/…
  • @danidiaz 是否有任何 W 使得 所有 有经验的 W 用户更喜欢使用 X i> 做Y?当然,大多数事情都有一个权衡。在 IMO 的情况下,Reader 的好处往往更大。
  • @leftaroundabout 你能写一个收益/成本分析的答案吗?实际上,“使用 monads 是最好的方法......”是一个很好的论点,但我想知道其他优点和缺点是什么。

标签: haskell monads reader-monad


【解决方案1】:
  1. Monad Reader 更方便,当您想将参数传递到调用堆栈更深的几个级别时。

  2. Monad Reader 便于代码更改/扩展。假设您想从数据库中获取一些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 窗口管理器。某处有XConfig 数据类型 在X monad 的内部,但大多数时候我不想知道它,别管它了。

【讨论】:

  • 就像Handler 一样,您可以将Connection 设为类型别名(例如modify1 :: Handler -&gt; Int -&gt; IO),因此在#2 中没有任何好处/成本。 #1 有点含糊,请您详细说明。
  • 我同意#2,除了...如果它是项目定义的数据类型,可以为Connection 创建别名。如果数据Connection 来自某个库,则将其设为别名会导致混淆:type Connection = (Lib.Connection, Bar) 相当不全面。
猜你喜欢
  • 1970-01-01
  • 2021-06-11
  • 2015-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-07
  • 1970-01-01
  • 2022-01-03
相关资源
最近更新 更多