2017-06-15 92 views
3

我一直在嘗試掌握IO monad一段時間,它是有道理的。如果我沒有弄錯,我們的目標是將副作用和實際執行的描述分開。正如在下面的例子中,Scala有一種方法來獲得一個不引人注意的環境變量。出現了兩個問題。貓效應和IO monad

問題1:這是一個指涉TRANSPARANT

問題2:如何正確(單位/財產依據)測試呢?檢查相等是不可能的,因爲它會檢查內存引用,並且不可能檢查內部函數,因爲如果我沒有弄錯,函數比較是不可能的。但是,我不想在單元測試中運行實際的副作用。另外,這是IO monad的設計錯誤還是濫用?

case class EnvironmentVariableNotFoundException(message: String) extends Exception(message) 

object Env { 
    def get(envKey: String): IO[Try[String]] = IO.unit.flatMap((_) => IO.pure(tryGetEnv(envKey))) 

    private[this] def tryGetEnv(envKey: String): Try[String] = 
    Try(System.getenv(envKey)) 
     .flatMap(
     (x) => 
      if (x == null) Failure(EnvironmentVariableNotFoundException(s"$envKey environment variable does not exist")) 
      else Success(x) 
    ) 

}

回答

2

這是很好的使用IO收官值在你的程序,有來自不純源,就像在你的榜樣系統調用。這會給你一個IO[A],其內容爲「我可以通過不純的方式獲得A」。此後,您可以使用純粹/透明的透明功能,通過map,flatMapA作用。

這導致了兩個答案。我會問,你試圖測試這個屬性是什麼?

看着那段代碼,我注意到tryGetEnv中的flatMap作爲一些可能足夠複雜的東西來保證測試。你可以通過將這個邏輯提取爲純函數來實現。你可能會(例如)重寫這個函數,所以有一個函數返回IO[String],然後編寫一個(測試過的)函數將它轉換爲你想要的類型。 IO確實如你所說,但這明確不包括使代碼透明透明!如果你想在這裏測試實際的副作用,你可能會考慮將系統作爲參數傳遞並嘲笑它進行測試,就像你在一個不使用IO的程序中一樣。

因此,在總結,我會考慮創建一個最小函數,它調用System創建IO[A](在這種情況下,IO[Try[String]])。你可以選擇通過嘲諷來測試這個最小功能,但只有當你覺得你正在通過這樣做來增加價值。圍繞這一點,你可以編寫一些函數,通過將這些函數傳遞給一個A並測試這些函數。請記住,mapIO上的簽名如下,並且這裏的f是純粹的(可測試的)功能!

sealed abstract class IO[+A] { 

    def map[B](f: A => B): IO[B] 
      ^f is a pure function! 
       test it by passing A values and verifying the Bs 

採取極端情況下,這種模式鼓勵你只能在你的程序(例如,您的main功能)的最邊緣創造IO值。然後可以使用純函數來創建程序的其餘部分,這些函數將在程序運行時處理來自IO類型的值。