简体   繁体   English

Reader monad有什么好处?

[英]What are the benefits of Reader monad?

I have read a blog post about Reader monad. 我读过一篇关于Reader monad的博客文章

The post is truly great and explains the topic in details but I did not get why I should use the Reader monad in that case. 这篇文章真的很棒,详细解释了这个主题,但我不知道为什么我应该在这种情况下使用Reader monad。

The post says: Suppose there is a function query: String => Connection => ResultSet 帖子说:假设有一个函数query: String => Connection => ResultSet

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

We can run a few queries as follows: 我们可以运行一些查询,如下所示:

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)
}

So far so good, but the post suggests use the Reader monad instead: 到目前为止一直很好,但帖子建议使用Reader monad代替:

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)

Ok, I got that I don't need to thread connection through the calls explicitly. 好的,我知道我不需要通过调用明确地connection线程。 So what ? 所以呢 ?
Why the solution with Reader monad is better than the previous one ? 为什么使用Reader monad的解决方案比前一个更好?

UPDATE: Fixed the typo in def query: = should be => This comment only exists because SO insists that edits must be at least 6 chars long. 更新:修正了def查询中的拼写错误:= should be =>此注释仅存在,因为SO坚持编辑必须至少有6个字符长。 So here we go. 所以我们走了。

The most important reason is that the reader monad allows you to build up complex computations compositionally . 最重要的原因是读者monad允许您在组合上构建复杂的计算。 Consider the following line from your non-reader example: 请考虑非读者示例中的以下行:

val rs1 = query("SELECT COUNT(*) FROM Foo")(conn)

The fact that we're passing in conn manually means that this line doesn't really make sense on its own—it can only be understood and reasoned about in the context of the doSomeQueries method that gives us conn . 我们手动传递conn的事实意味着这条线本身并没有真正意义 - 它只能在给我们conndoSomeQueries方法的上下文中理解和推理。

Often this is just fine—there's obviously nothing wrong about defining and using local variables (at least in the val sense). 通常这很好 - 显然,定义和使用局部变量没有任何问题 (至少在val意义上)。 Sometimes, though, it's more convenient (or desirable for other reasons) to build up computations out of stand-alone, composable pieces, and the reader monad can help with this. 但是,有时候,使用独立的,可组合的部分构建计算会更方便(或者出于其他原因需要),读者monad可以帮助解决这个问题。

Consider query("SELECT COUNT(*) FROM Foo") in your second example. 在第二个示例中考虑query("SELECT COUNT(*) FROM Foo") Assuming we know what query is, this is an entirely self-contained expression—there are no variables like conn that need to be bound by some enclosing scope. 假设我们知道什么是query ,这是一个完全自包含的表达式 - 没有像conn这样的变量需要被某些封闭范围绑定。 This means you can reuse and refactor more confidently, and that you don't have quite so much stuff to hold in your head when you're reasoning about it. 这意味着您可以更自信地重复使用和重构,并且当您推理它时,您没有那么多东西可以保留在脑中。

Again, this isn't ever necessary —it's largely a matter of style. 同样,这不是必要的 - 这在很大程度上取决于风格。 If you decide to give it a try (and I'd suggest that you do), you'll probably pretty quickly develop preferences and intuitions about where it makes your code more intelligible and where it doesn't. 如果你决定尝试一下(我建议你这样做),你可能会很快发展出偏好和直觉,让你的代码更容易被理解,哪些不可理解。

One other advantage is that you can compose different kinds of "effects" using ReaderT (or by adding Reader into some other stack). 另一个优点是你可以使用ReaderT (或通过将Reader添加到其他堆栈中)来组合不同类型的“效果”。 That set of issues probably deserves its own question and answer, though. 但是,这组问题可能值得自己提出问题和答案。

One last note: you probably want your doSomeQueries to look like this: 最后一点:你可能希望你的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)

Or, if this really is the end of the line: 或者,如果这真的是行尾:

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)

In your current version you're not actually using conn . 在您当前的版本中,您实际上并没有使用conn

For finding out the general benefits of using ReaderMonad I recommend Travis Brown's excellent answer - the strength of ReaderMonad lies in its compositionality and other extras provided by monads (eg the ReaderT et al). 为了找出使用ReaderMonad的一般好处,我推荐Travis Brown的优秀答案 - ReaderMonad的优势在于其组合性和monad提供的其他附加功能(例如ReaderT等)。 You get the most benefit out of it if you write your other code in monadic style too. 如果您以monadic风格编写其他代码,那么您将获得最大的收益。

You've also asked specifically what's so desirable in not having to pass the connection around explicitly. 你还明确地询问了什么是不必明确地传递connection I'll try to answer this part of your question here. 我会尝试在这里回答你的这部分问题。

First, few words less to type / less to read is already an improvement. 首先,少读字或少读的字已经是一种改进。 The more complex the whole codebase is the more I appreciate that. 整个代码库越复杂,我就越感激。 When I read a long method (not written by me of course ;) ) I find it easier when its logic isn't interwoven with dumb argument passing. 当我阅读一个很长的方法(当然不是我写的;))当它的逻辑与愚蠢的论证传递交织在一起时,我发现它更容易。

Second, ReaderMonad gives you a guarantee, that the connection is the same object all the way down. 其次,ReaderMonad为您提供了保证,即connection始终是同一个对象。 Most often you want exactly that. 大多数情况下你都想要那样。 In your first example it's very easy to call 在你的第一个例子中,它很容易打电话

query("SELECT COUNT(*) FROM Bar")(anotherConnectionWhereverItCameFrom)

regardless of whether it's been done for purpose or by mistake. 无论是出于目的还是出于错误而做出的。 When I read a long method and see ReaderMonad used I know that there'll be only one connection used. 当我阅读一个很长的方法并看到使用的ReaderMonad时,我知道只会使用一个connection No nasty surprises caused by some "tactical solution" in the 219th line of the method. 在该方法的第219行中,由于一些“战术解决方案”而没有引起令人讨厌的意外。

Note, that those benefits can be also achieved without ReaderMonad, even if it does good job in that area. 请注意,即使它在该领域做得很好,也可以在没有ReaderMonad的情况下实现这些好处。 You could for example just write: 你可以这样写:

class Query(val connection: Connection) {
  def apply(sql:String) = connection.createStatement.executeQuery(sql)
}

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

It wouldn't have neither composability nor other nice features of monads, but would achieve the ReaderMonad's goal of not passing the argument ( connection ) explicitly. 它既没有可编组性也没有monad的其他优点,但是它将实现ReaderMonad的目标,即不明确地传递参数( connection )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM