簡體   English   中英

Reader monad有什么好處?

[英]What are the benefits of Reader monad?

我讀過一篇關於Reader monad的博客文章

這篇文章真的很棒,詳細解釋了這個主題,但我不知道為什么我應該在這種情況下使用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)
}

到目前為止一直很好,但帖子建議使用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)

好的,我知道我不需要通過調用明確地connection線程。 所以呢 ?
為什么使用Reader monad的解決方案比前一個更好?

更新:修正了def查詢中的拼寫錯誤:= should be =>此注釋僅存在,因為SO堅持編輯必須至少有6個字符長。 所以我們走了。

最重要的原因是讀者monad允許您在組合上構建復雜的計算。 請考慮非讀者示例中的以下行:

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

我們手動傳遞conn的事實意味着這條線本身並沒有真正意義 - 它只能在給我們conndoSomeQueries方法的上下文中理解和推理。

通常這很好 - 顯然,定義和使用局部變量沒有任何問題 (至少在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)

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

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)

在您當前的版本中,您實際上並沒有使用conn

為了找出使用ReaderMonad的一般好處,我推薦Travis Brown的優秀答案 - ReaderMonad的優勢在於其組合性和monad提供的其他附加功能(例如ReaderT等)。 如果您以monadic風格編寫其他代碼,那么您將獲得最大的收益。

你還明確地詢問了什么是不必明確地傳遞connection 我會嘗試在這里回答你的這部分問題。

首先,少讀字或少讀的字已經是一種改進。 整個代碼庫越復雜,我就越感激。 當我閱讀一個很長的方法(當然不是我寫的;))當它的邏輯與愚蠢的論證傳遞交織在一起時,我發現它更容易。

其次,ReaderMonad為您提供了保證,即connection始終是同一個對象。 大多數情況下你都想要那樣。 在你的第一個例子中,它很容易打電話

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

無論是出於目的還是出於錯誤而做出的。 當我閱讀一個很長的方法並看到使用的ReaderMonad時,我知道只會使用一個connection 在該方法的第219行中,由於一些“戰術解決方案”而沒有引起令人討厭的意外。

請注意,即使它在該領域做得很好,也可以在沒有ReaderMonad的情況下實現這些好處。 你可以這樣寫:

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

它既沒有可編組性也沒有monad的其他優點,但是它將實現ReaderMonad的目標,即不明確地傳遞參數( connection )。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM