Reader monad有什么好处?

Mic*_*ael 6 monads functional-programming scala

我读过一篇关于Readermonad 的博客文章.

帖子真的很棒,详细解释了这个主题,但我不明白为什么我应该Reader在这种情况下使用monad.

帖子说:假设有一个功能 query: String => Connection => ResultSet

def query(sql:String) = conn:Connection => conn.createStatement.executeQuery(sql)

我们可以运行一些查询,如下所示:

def doSomeQueries(conn: Connection) = {
  val rs1 = query("SELECT COUNT(*) FROM Foo")(conn)
  val rs2 = query("SELECT COUNT(*) FROM Bar")(conn)
  rs1.getInt(1) + rs2.getInt(1)
} 

到目前为止一直很好,但帖子建议使用Readermonad代替:

class Reader[E, A](run: E => A) {

  def map[B](f: A => B):Reader[E, B] =
    new Reader(?=> f(run(?)))

  def flatMap[B](f:A => Reader[E, B]): Reader[E, B] =
    new Reader(? => f(run(?)).run(?))  
}

val query(sql:String): Reader[Connection, ResultSet] =
  new Reader(conn => conn.createStatement.executeQuery(sql))

def doSomeQueries(conn: Connection) = for {
  rs1 <- query("SELECT COUNT(*) FROM Foo")
  rs2 <- query("SELECT COUNT(*) FROM Bar")
} yield rs1.getInt(1) + rs2.getInt(1)

好的,我知道我不需要connection明确地通过调用.所以呢 ?
为什么Reader monad 的解决方案比前一个好?

更新:修正了def查询中的拼写错误:= should be =>此注释仅存在,因为SO坚持编辑必须至少有6个字符长.所以我们走了.

Tra*_*own 10

最重要的原因是读者monad允许您在组合上构建复杂的计算.请考虑非读者示例中的以下行:

val rs1 = query("SELECT COUNT(*) FROM Foo")(conn)
Run Code Online (Sandbox Code Playgroud)

我们conn手动传递这一事实意味着这条线本身并没有真正意义 - 它只能在doSomeQueries给我们的方法的上下文中理解和推理conn.

通常这很好 - 显然,定义和使用局部变量没有任何问题(至少在val某种意义上).但是,有时候,通过独立的,可组合的部分构建计算会更方便(或者出于其他原因),并且读者monad可以帮助解决这个问题.

请看query("SELECT COUNT(*) FROM Foo")你的第二个例子.假设我们知道是什么query,这是一个完全自包含的表达式 - 没有像conn这样的变量需要被某些封闭范围绑定.这意味着您可以更自信地重复使用和重构,并且当您推理它时,您没有太多的东西可以保留在您的头脑中.

同样,这不是必要的 - 这在很大程度上取决于风格.如果你决定尝试一下(我建议你这样做),你可能会很快发展出偏好和直觉,让你的代码更容易被理解,哪些不可理解.

另一个优点是你可以使用ReaderT(或通过添加Reader到其他堆栈中)来组合不同类型的"效果" .但是,这组问题可能值得自己提出问题和答案.

最后一点:您可能希望doSomeQueries看起来像这样:

def doSomeQueries: Reader[Connection, Int] = for {
  rs1 <- query("SELECT COUNT(*) FROM Foo")
  rs2 <- query("SELECT COUNT(*) FROM Bar")
} yield rs1.getInt(1) + rs2.getInt(1)
Run Code Online (Sandbox Code Playgroud)

或者,如果这真的是行尾:

def doSomeQueries(conn: Connection) = (
  for {
    rs1 <- query("SELECT COUNT(*) FROM Foo")
    rs2 <- query("SELECT COUNT(*) FROM Bar")
  } yield rs1.getInt(1) + rs2.getInt(1)
).run(conn)
Run Code Online (Sandbox Code Playgroud)

在您当前的版本中,您实际上并未使用conn.