Cats(4)- 疊加Free程式運算結果,Stacking monadic result types

来源:http://www.cnblogs.com/tiger-xc/archive/2016/09/13/5867122.html
-Advertisement-
Play Games

在前面的幾篇關於Free編程的討論示範中我們均使用了基礎類型的運算結果。但在實際應用中因為需要考慮運算中出現異常的情況,常常會需要到更高階複雜的運算結果類型如Option、Xor等。因為Monad無法實現組合(monad do not compose),我們如何在for-comprehension中 ...


   在前面的幾篇關於Free編程的討論示範中我們均使用了基礎類型的運算結果。但在實際應用中因為需要考慮運算中出現異常的情況,常常會需要到更高階複雜的運算結果類型如Option、Xor等。因為Monad無法實現組合(monad do not compose),我們如何在for-comprehension中組合這些運算呢?假如在我們上一篇討論里的示範DSL是這樣的:

1 trait Login[+A]
2  case class Authenticate(uid: String, pwd: String) extends Login[Option[Boolean]]
3  
4  trait Auth[+A]
5  case class Authorize(uid: String) extends Auth[Xor[String,Boolean]]

這兩個ADT在for-comprehension里如果我們勉強將Option和Xor疊加在一起就會產生所謂下臺階式運算(stair-stepping),因為monad do not compose! 我們可以看看下麵的示範: 

1  type Result[A] = Xor[String,Option[A]]
2  def getResult: Result[Int] = 62.some.right       //> getResult: => demo.ws.catsMTX.Result[Int]
3  for {
4    optValue <- getResult
5  } yield {
6    for {
7      valueA <- optValue
8    } yield valueA + 18                            //> res0: cats.data.Xor[String,Option[Int]] = Right(Some(80))
9  }

我們必須用兩層for-comprehension來組合最終結果。這就是所謂的下臺階運算了。如果遇到三層疊加類型,那麼整個程式會變得更加複雜了。其實不單是程式結構複雜問題,更重要的是運算效果(effect)無法正確體現:出現None和Left值時並不能立即終止for-comprehension、再就是如果第一層是有副作用(side-effect)運算時,由於我們必須先得出第一層的運算結果才能進行下一層運算,所以這個for-comprehension產生了不純代碼(impure-code),如下:

1 for {
2   optionData <- IO {readDB()}
3 } yield {
4   for {
5     data <- optionData
6   } yield Process(data)
7 }

我們必須先運算IO才能開始運算Process。這就使這段程式變成了不純代碼。我在一篇scalaz-monadtransform的博客中介紹瞭如何用MonadTransformer來解決這種類型堆疊的問題,大家可以參考。cats同樣實現了幾個類型的MonadTransformer如:OptionT、EitherT、StateT、WriterT、Kleisli等等,命名方式都是以類型名稱尾綴加T的規範方式,如:

final case class OptionT[F[_], A](value: F[Option[A]]) {...}
inal case class EitherT[F[_], A, B](value: F[Either[A, B]]) {...}
final class StateT[F[_], S, A](val runF: F[S => F[(S, A)]]) extends Serializable {...}
final case class WriterT[F[_], L, V](run: F[(L, V)]) {...}

我們可以從MonadTransformer的value或run,runF獲取其代表的數據類型,如:

OptionT[Xor,A](value: Xor[?,Option[A]]) >>> 代表的類型:Xor[?,Option[A]]

XorT[OptionT,A](value: Option[Xor[?,A]]) >>>代表的類型:Option[Xor[?,A]]

我們可以用Applicative.pure來把一個值升格成堆疊類型:

 1 import cats._,cats.instances.all._
 2 import cats.data.{Xor,XorT}
 3 import cats.syntax.xor._
 4 import cats.data.OptionT
 5 import cats.syntax.option._
 6 import cats.syntax.applicative._
 7 
 8  type Error[A] = Xor[String,A]
 9  type XResult[A] = OptionT[Error,A]
10  type OResult[A] = XorT[Option,String,A]
11  Applicative[XResult].pure(62)             //> res0: demo.ws.catsMTX.XResult[Int] = OptionT(Right(Some(62)))
12  62.pure[XResult]                          //> res1: demo.ws.catsMTX.XResult[Int] = OptionT(Right(Some(62)))
13  Applicative[OResult].pure(62)             //> res2: demo.ws.catsMTX.OResult[Int] = XorT(Some(Right(62)))
14  62.pure[OResult]                          //> res3: demo.ws.catsMTX.OResult[Int] = XorT(Some(Right(62)))

註意,用Applicative.pure來升格None或者Left會產生錯誤結果:

 1  Applicative[XResult].pure(none[Int])             
 2 //> res4: demo.ws.catsMTX.XResult[Option[Int]] = OptionT(Right(Some(None)))
 3  (None: Option[Int]).pure[XResult]                
 4 //> res5: demo.ws.catsMTX.XResult[Option[Int]] = OptionT(Right(Some(None)))
 5  Applicative[XResult].pure("oh no".left[Int])    
 6  //> res6: demo.ws.catsMTX.XResult[cats.data.Xor[String,Int]] = OptionT(Right(Some(Left(oh no))))
 7  (Left[String,Int]("oh no")).pure[XResult]      
 8 //> res7: demo.ws.catsMTX.XResult[scala.util.Left[String,Int]] = OptionT(Right(Some(Left(oh no))))
 9  Applicative[OResult].pure(Left[String,Int]("oh no"))
10 //> res8: demo.ws.catsMTX.OResult[scala.util.Left[String,Int]] = XorT(Some(Right(Left(oh no))))
11  "oh no".left[Int].pure[OResult]                  
12 //> res9: demo.ws.catsMTX.OResult[cats.data.Xor[String,Int]] = XorT(Some(Right(Left(oh no))))
13  Applicative[OResult].pure(none[Int])             
14 //> res10: demo.ws.catsMTX.OResult[Option[Int]] = XorT(Some(Right(None)))
15  (None: Option[Int]).pure[OResult]                
16 //> res11: demo.ws.catsMTX.OResult[Option[Int]] = XorT(Some(Right(None)))
17  Applicative[OResult].pure("oh no".left[Int])
18 //> res12: demo.ws.catsMTX.OResult[cats.data.Xor[String,Int]] = XorT(Some(Right(Left(oh no))))
19  (Left[String,Int]("oh no")).pure[OResult]
20 //> res13: demo.ws.catsMTX.OResult[scala.util.Left[String,Int]] = XorT(Some(Right(Left(oh no))))

Some(None),Right(Left("oh no)))是什麼意思呢?明顯是錯誤。我們必須用MonadTransformer的構建器(constructor)才能正確的對這些邊際值進行升格:

1  OptionT(none[Int].pure[Error])                   
2 //> res14: cats.data.OptionT[demo.ws.catsMTX.Error,Int] = OptionT(Right(None))
3  OptionT("oh no".left: Error[Option[Int]])
4 //> res15: cats.data.OptionT[demo.ws.catsMTX.Error,Int] = OptionT(Left(oh no))
5  XorT(none[Error[Int]])                           
6 //> res16: cats.data.XorT[Option,String,Int] = XorT(None)
7  XorT("oh no".left[Int].pure[Option])             
8 //> res17: cats.data.XorT[Option,String,Int] = XorT(Some(Left(oh no)))

下麵我們示範一下在for-comprehension中運算Xor[?Option[A]]這種堆疊類型:

 1  type Error[A] = Xor[String,A]
 2  type XResult[A] = OptionT[Error,A]
 3  type OResult[A] = XorT[Option,String,A]
 4  def getXor(s: String): Error[String] = s.right   //> getXor: (s: String)demo.ws.catsMTX.Error[String]
 5  def getOption(s: String): Option[String] = s.some
 6                                                   //> getOption: (s: String)Option[String]
 7   val composed: XResult[String] =
 8    for {
 9       s1 <- OptionT.liftF(getXor("Hello "))
10       s2 <- OptionT.liftF(getXor("World!"))
11       s3 <- OptionT(getOption("come to papa!").pure[Error])
12    } yield s1 + s2 + s3     //> composed  : demo.ws.catsMTX.XResult[String] = OptionT(Right(Some(Hello World!come to papa!)))
13    composed.value           //> res18: demo.ws.catsMTX.Error[Option[String]] = Right(Some(Hello World!come to papa!))

測試一下Xor,Option的left和none效果:

 1  val composed: XResult[String] =
 2    for {
 3       s1 <- OptionT.liftF(getXor("Hello "))
 4       s0 <- OptionT(none[String].pure[Error])
 5       s2 <- OptionT.liftF(getXor("World!"))
 6       s3 <- OptionT(getOption("come to papa!").pure[Error])
 7    } yield s1 + s2 + s3      //> composed  : demo.ws.catsMTX.XResult[String] = OptionT(Right(None))
 8    composed.value            //> res18: demo.ws.catsMTX.Error[Option[String]] = Right(None)
 9 
10   val composed: XResult[String] =
11    for {
12       s1 <- OptionT.liftF(getXor("Hello "))
13       s0 <- OptionT("oh no".left: Error[Option[Int]])
14       s2 <- OptionT.liftF(getXor("World!"))
15       s3 <- OptionT(getOption("come to papa!").pure[Error])
16    } yield s1 + s2 + s3       //> composed  : demo.ws.catsMTX.XResult[String] = OptionT(Left(oh no))
17    composed.value             //> res18: demo.ws.catsMTX.Error[Option[String]] = Left(oh no)

從運算結果我們看到在for-comprehension中這個堆疊類型的組成類型Xor和Option的效果可以得到體現。

在現實中三層以上的運算結果類型堆疊還是很普遍的,如:Future[Xor[?,Option[A]]]。要註意MonadTransformer類型堆疊的順序是重要的,而且是由內向外的,決定著最終運算結果的類型。如果增加一層Future類型,我們就需要把它放到堆疊結構的最內部:

 

1    type FError[A] = XorT[Future,String,A]
2    type FResult[A] = OptionT[FError,A]

 

現在我們需要考慮如何進行MonadTransformer類型的升格了。請相信我,這項工作絕對是一場噩夢。具體示範可以在我這篇博客scalaz-monadtransformer中找到。我的意思是如果沒有更好的辦法,這項工作基本是一項不可能的任務(mission impossible)。

對於上面提出的問題,freeK提供了很好的解決方法。freeK的Onion數據類型就是為簡化Monad堆疊操作而設計的。Onion表達形式如下:

 

1 type Stack[A] = F[G[H[I[A]]]]
2 type O = F :&: G :&: H :&: I :&: Bulb
3 type Stack[A] = O#Layers[A]

 

O就是Onion類型,代表了一個Monad堆疊。我們可以用O#Layers[A]返還原始的多層Monad,如下麵的示例:

 

1   import freek._
2    type O = Xor[String,?] :&: Option :&: Bulb
3    type MStack[A] = O#Layers[A]

 

我們用一個具體的Free程式來示範堆疊Monad運算結果的操作。假如例子的ADT是這樣的:

 1    sealed trait Foo[A]
 2    final case class Foo1(s: String) extends Foo[Option[Int]]
 3    final case class Foo2(i: Int) extends Foo[Xor[String, Int]]
 4    final case object Foo3 extends Foo[Unit]
 5    final case class Foo4(i: Int) extends Foo[Xor[String, Option[Int]]]
 6 
 7    sealed trait Bar[A]
 8    final case class Bar1(s: String) extends Bar[Option[String]]
 9    final case class Bar2(i: Int) extends Bar[Xor[String, String]]
10    
11    sealed trait Laa[A]
12    final case class Push(s: String) extends Laa[List[String]]

從模擬運算結果類型來看,我們將面對相當複雜的三層Monad堆疊。我們先用Foo,Bar來示範兩層堆疊的DSL。首先,我們希望使用DSL的語法如下:

 

1 for {
2   i   <- Foo1("5").freek[PRG] // 運算結果是: Option[String]
3   s   <- Bar2(i).freek[PRG]   // 運算結果是: Xor[String, String]
4   ...
5 } yield (())

 

我們希望對運算結果進行一種升格:把它們升格成一致堆疊類型,如下:

 

 1 Free[PRG.Cop, Option[A]]
 2 // 和這個類型
 3 Free[PRG.Cop, Xor[String, A]]
 4 
 5 // 一致升格成
 6 Free[PRG.Cop, Xor[String, Option[A]]]
 7 
 8 // 也就是這個
 9 type O = Xor[String, ?] :&: Option :&: Bulb
10 Free[PRG.Cop, O#Layers]

 

像cats的MonadTransformer,freeK也提供了個OnionT,OnionT代表Monad堆疊類型容器。我們希望實現以下升格(lifting)操作:

 

1 //
2 Free[PRG.Cop, Option[A] 
3 //
4 Xor[String, A]] 
5 //統統轉成
6 OnionT[Free, PRG.Cop, O, A]

 

我們可以用.onionT[O]來升格:

 

 1   type PRG = Foo :|: Bar :|: NilDSL
 2   val PRG = DSL.Make[PRG]
 3   type O = Xor[String,?] :&: Option :&: Bulb
 4   val prg: OnionT[Free,PRG.Cop,O,Int]= for {
 5     i  <- Foo1("5").freek[PRG].onionT[O]
 6     i2 <- Foo2(i).freek[PRG].onionT[O]
 7     _  <- Foo3.freek[PRG].onionT[O]
 8     s  <- Bar1(i2.toString).freek[PRG].onionT[O]
 9     i3 <- Foo4(i2).freek[PRG].onionT[O]
10   } yield (i3)

 

我們可以用比較簡單點的表達形式freeko來示範同樣效果:

 

1   val prg2: OnionT[Free,PRG.Cop,O,Int]= for {
2     i  <- Foo1("5").freeko[PRG,O]
3     i2 <- Foo2(i).freeko[PRG,O]
4     _  <- Foo3.freeko[PRG,O]
5     s  <- Bar1(i2.toString).freeko[PRG,O]
6     i3 <- Foo4(i2).freeko[PRG,O]
7   } yield (i3)

 

註意,現在程式prg的返回結果類型是OnionT。但我們的運算interpret函數是在Free上面的。OnionT.value可以返回Free類型:

1 pre.value
2 //res12: cats.free.Free[PRG.Cop,O#Layers[Int]] = Free(...)

所以運算程式方式要調整成:prg.value.interpret(interpreters)

如果我們再增加一層Monad堆疊呢?

 

 1   type PRG3 = Laa :|: Foo :|: Bar :|: NilDSL
 2   val PRG3 = DSL.Make[PRG3]
 3   type O3 = List :&: Xor[String,?] :&: Option :&: Bulb
 4   val prg3: OnionT[Free,PRG3.Cop,O3,Int]= for {
 5     i  <- Foo1("5").freeko[PRG3,O3]
 6     i2 <- Foo2(i).freeko[PRG3,O3]
 7     _  <- Foo3.freeko[PRG3,O3]
 8     s  <- Bar1(i2.toString).freeko[PRG3,O3]
 9     i3 <- Foo4(i2).freeko[PRG3,O3]
10     _ <- Push(s).freeko[PRG3,O3]
11   } yield (i3)

就是這麼簡單。

下麵我們把上篇討論的用戶驗證示範例子的運算結果類型調整成複雜類型,然後用freeK.Onion來完善程式。先調整ADT:

 

 1   object ADTs {
 2     sealed trait Interact[+A]
 3     object Interact {
 4       case class Ask(prompt: String) extends Interact[Xor[String,String]]
 5       case class Tell(msg: String) extends Interact[Unit]
 6     }
 7     sealed trait Login[+A]
 8     object Login {
 9       case class Authenticate(uid: String, pwd: String) extends Login[Option[Boolean]]
10     }
11     sealed trait Auth[+A]
12     object Auth {
13       case class Authorize(uid: String) extends Auth[Option[Boolean]]
14     }
15   }

 

我們把運算結果改成了Xor,Option。再看看DSL調整:

 

 1   object DSLs {
 2     import ADTs._
 3     import Interact._
 4     import Login._
 5     type PRG = Interact :|: Login :|: NilDSL
 6     val PRG = DSL.Make[PRG]
 7     type O =  Xor[String,?] :&: Option :&: Bulb
 8     val authenticDSL: OnionT[Free,PRG.Cop, O, Boolean] =
 9       for {
10         uid <- Ask("Enter your user id:").freeko[PRG,O]
11         pwd <- Ask("Enter password:").freeko[PRG,O]
12         auth <- Authenticate(uid,pwd).freeko[PRG,O]
13       } yield auth
14     type O2 =  Option :&: Xor[String,?] :&: Bulb
15     val authenticDSLX =
16       for {
17         uid <- Ask("Enter your user id:").freeko[PRG,O2].peelRight
18         pwd <- Ask("Enter password:").freeko[PRG,O2].peelRight
19         auth <- (uid,pwd) match {
20           case (Xor.Right(u),Xor.Right(p)) => Authenticate(u,p).freeko[PRG,O2].peelRight
21           case _ => Authenticate("","").freeko[PRG,O2].peelRight
22         }
23       } yield auth
24     val interactLoginDSL: OnionT[Free,PRG.Cop, O, Unit] =
25       for {
26         uid <- Ask("Enter your user id:").freeko[PRG,O]
27         pwd <- Ask("Enter password:").freeko[PRG,O]
28         auth <- Authenticate(uid,pwd).freeko[PRG,O]
29         _ <- if (auth) Tell(s"Hello $uid, welcome to the zoo!").freeko[PRG,O]
30         else Tell(s"Sorry, Who is $uid?").freeko[PRG,O]
31       } yield ()
32 
33     import Auth._
34     type PRG3 = Auth :|: PRG   //Interact :|: Login :|: NilDSL
35     val PRG3 = DSL.Make[PRG3]
36     val authorizeDSL: OnionT[Free,PRG3.Cop, O , Unit] =
37       for {
38         uid <- Ask("Enter your User ID:").freeko[PRG3,O]
39         pwd <- Ask("Enter your Password:").freeko[PRG3,O]
40         auth <- Authenticate(uid,pwd).freeko[PRG3,O]
41         perm <-  if (auth) Authorize(uid).freeko[PRG3,O]
42                  else OnionT.pure[Free,PRG3.Cop,O,Boolean](false)
43         _ <- if (perm)  Tell(s"Hello $uid, access granted!").freeko[PRG3,O]
44              else Tell(s"Sorry $uid, access denied!").freeko[PRG3,O]
45       } yield()
46   }

 

註意上面代碼中這個authenticDSLX:當我們需要對Option:&:Xor:&:Bulb中的整個Xor值而不是運算值A來操作時可以用peelRight來獲取這個Xor。如果有需要的話我們還可以用peelRight2,peelRight3來越過二、三層類型。具體實現interpreter部分也需要按照ADT的運算結果類型來調整:

 1 object IMPLs {
 2     import ADTs._
 3     import Interact._
 4     import Login._
 5     import Auth._
 6     val idInteract = new (Interact ~> Id) {
 7       def apply[A](ia: Interact[A]): Id[A] = ia match {
 8         case Ask(p) => {println(p); (scala.io.StdIn.readLine).right}
 9         case Tell(m) => println(m)
10       }
11     }
12     val idLogin = new (Login ~> Id) {
13       def apply[A](la: Login[A]): Id[A] = la match {
14         case Authenticate(u,p) => (u,p) match {
15           case ("Tiger","123") => true.some
16           case _ => false.some
17         }
18       }
19     }
20     val interactLogin = idInteract :&: idLogin
21     import Dependencies._
22     type ReaderContext[A] = Reader[Authenticator,A]
23     object readerInteract extends (Interact ~> ReaderContext) {
24       def apply[A](ia: Interact[A]): ReaderContext[A] = ia match {
25         case Ask(p) => Reader {pc => {println(p); (scala.io.StdIn.readLine).right}}
26         case Tell(m) => Reader {_ => println(m)}
27       }
28     }
29     object readerLogin extends (Login ~> ReaderContext) {
30       def apply[A](la: Login[A]): ReaderContext[A] = la match {
31         case Authenticate(u,p) => Reader {pc => pc.matchUserPassword(u,p).some}
32       }
33     }
34     val userInteractLogin = readerLogin :&: readerInteract
35 
36     val readerAuth = new (Auth ~> ReaderContext) {
37       def apply[A](aa: Auth[A]): ReaderContext[A] = aa match {
38         case Authorize(u) => Reader {ac => ac.grandAccess(u).some}
39       }
40     }
41     val userAuth = readerAuth :&: userInteractLogin
42   }

具體運行方式需要調整成:

 

1   authorizeDSL.value.interpret(userAuth).run(AuthControl)

 

測試運行與我們上篇示範相同。

完整的示範源代碼如下:

 

  1 import cats.instances.all._
  2 import cats.free.Free
  3 import cats.{Id, ~>}
  4 import cats.data.Reader
  5 import freek._
  6 import cats.data.Xor
  7 import cats.syntax.xor._
  8 import cats.syntax.option._
  9 object FreeKModules {
 10   object ADTs {
 11     sealed trait Interact[+A]
 12     object Interact {
 13       case class Ask(prompt: String) extends Interact[Xor[String,String]]
 14       case class Tell(msg: String) extends Interact[Unit]
 15     }
 16     sealed trait Login[+A]
 17     object Login {
 18       case class Authenticate(uid: String, pwd: String) extends Login[Option[Boolean]]
 19     }
 20     sealed trait Auth[+A]
 21     object Auth {
 22       case class Authorize(uid: String) extends Auth[Option[Boolean]]
 23     }
 24   }
 25   object DSLs {
 26     import ADTs._
 27     import Interact._
 28     import Login._
 29     type PRG = Interact :|: Login :|: NilDSL
 30     val PRG = DSL.Make[PRG]
 31     type O =  Xor[String,?] :&: Option :&: Bulb
 32     val authenticDSL: OnionT[Free,PRG.Cop, O, Boolean] =
 33       for {
 34         uid <- Ask("Enter your user id:").freeko[PRG,O]
 35         pwd <- Ask("Enter password:").freeko[PRG,O]
 36         auth <- Authenticate(uid,pwd).freeko[PRG,O]
 37       } yield auth
 38     type O2 =  Option :&: Xor[String,?] :&: Bulb
 39     val authenticDSLX =
 40       for {
 41         uid <- Ask("Enter your user id:").freeko[PRG,O2].peelRight
 42         pwd <- Ask("Enter password:").freeko[PRG,O2].peelRight
 43         auth <- (uid,pwd) match {
 44           case (Xor.Right(u),Xor.Right(p)) => Authenticate(u,p).freeko[PRG,O2].peelRight
 45           case _ => Authenticate("","").freeko[PRG,O2].peelRight
 46         }
 47       } yield auth
 48     val interactLoginDSL: OnionT[Free,PRG.Cop, O, Unit] =
 49       for {
 50         uid <- Ask("Enter your user id:").freeko[PRG,O]
 51         pwd <- Ask("Enter password:").freeko[PRG,O]
 52         auth <- Authenticate(uid,pwd).freeko[PRG,O]
 53         _ <- if (auth) Tell(s"Hello $uid, welcome to the zoo!").freeko[PRG,O]
 54         else Tell(s"Sorry, Who is $uid?").freeko[PRG,O]
 55       } yield ()
 56 
 57     import Auth._
 58     type PRG3 = Auth :|: PRG   //Interact :|: Login :|: NilDSL
 59     val PRG3 = DSL.Make[PRG3]
 60     val authorizeDSL: OnionT[Free,PRG3.Cop, O , Unit] =
 61       for {
 62         uid <- Ask("Enter your User ID:").freeko[PRG3,O]
 63         pwd <- Ask("Enter your Password:").freeko[PRG3,O]
 64         auth <- Authenticate(uid,pwd).freeko[PRG3,O]
 65         perm <-  if (auth) Authorize(uid).freeko[PRG3,O]
 66                  else OnionT.pure[Free,PRG3.Cop,O,Boolean](false)
 67         _ <- if (perm)  Tell(s"Hello $uid, access granted!").freeko[PRG3,O]
 68              else Tell(s"Sorry $uid, access denied!").freeko[PRG3,O]
 69       } yield()
 70 
 71 
 72   }
 73   object IMPLs {
 74     import ADTs._
 75     import Interact._
 76     import Login._
 77     import Auth._
 78     val idInteract = new (Interact ~> Id) {
 79       def apply[A](ia: Interact[A]): Id[A] = ia match {
 80         case Ask(p) => {println(p); (scala.io.StdIn.readLine).right}
 81         case Tell(m) => println(m)
 82       }
 83     }
 84     val idLogin = new (Login ~> Id) {
 85       def apply[A](la: Login[A]): Id[A] = la match {
 86         case Authenticate(u,p) => (u,p) match {
 87           case ("Tiger","123") => true.some
 88           case _ => false.some
 89         }
 90       }
 91     }
 92     val interactLogin = idInteract :&: idLogin
 93     import Dependencies._
 94     type ReaderContext[A] = Reader[Authenticator,A]
 95     object readerInteract extends (Interact ~> ReaderContext) {
 96       def apply[A](ia: Interact[A]): ReaderContext[A] = ia match {
 97         case Ask(p) => Reader {pc => {println(p); (scala.io.StdIn.readLine).right}}
 98         case Tell(m) => Reader {_ => println(m)}
 99       }
100     }
101     object readerLogin extends (Login ~

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 由於各Linux開發廠商的不同,因此不同開發廠商的Linux版本操作細節也不一樣,今天就來說一下CentOS下JDK的安裝: 方法一:手動解壓JDK的壓縮包,然後設置環境變數 1.在/usr/目錄下創建java目錄 2.下載jdk,然後解壓 3.設置環境變數 在profile中添加如下內容: 讓修改 ...
  • 系統 # uname -a # 查看內核/操作系統/CPU信息# head -n 1 /etc/issue # 查看操作系統版本# cat /proc/cpuinfo # 查看CPU信息# hostname # 查看電腦名# lspci -tv # 列出所有PCI設備# lsusb -tv # 列 ...
  • 1、ifconfig命令找不到 解決方法:安裝net-tools.x86_64工具包 yum install net-tools.x86_64 2、iptables無法使用 解決方法:yum install iptables-services vi /etc/sysconfig/iptables # ...
  • 一、安裝GCC編譯環境,如果有則不需要 1)安裝mpc庫 2)安裝gmp庫 3)安裝mpfr庫 4) 安裝GCC 以上GCC的安裝不綴述,可參考各種大神步驟; 二、安裝pcre庫 https://sourceforge.net/projects/pcre/files/pcre/ 下載後解壓併在解壓文 ...
  • 這種情況一般是由於系統防火牆設置問題導致的,這次遇到的系統是centos 7.2,防火牆由iptables改成了firewall,因此停止防火牆的命令應該是: 禁止防火牆啟動的命令應該是: 暫時只寫這麼多,關於防火牆的配置等以後有時間了再說. ...
  • 繼續解決mplayer安裝不上的問題: 多次嘗試後,把源換回官方然後 update&upgrade後安裝 問題解決 時區問題解決: 裡面的第五項 Internationalisation Options --> Change Timezone --> Asia --> Chongqing(找了半天沒 ...
  • 之前裝的是live版 就是沒有桌面的版本,想看能hdmi看電影,於是找了教程安裝omxplayer 用 命令 通過hdmi播放電影 具體安裝過程發在貼吧里了:http://tieba.baidu.com/p/4766986525?see_lz=1 但是依然不能掛字幕.... 無奈今天重裝rasbia ...
  • 近期項目查詢資料庫太慢,持久層也沒有開啟二級緩存,現希望採用Redis作為緩存。為了不改寫原來代碼,在此採用AOP+Redis實現。 目前由於項目需要,只需要做查詢部分: 數據查詢時每次都需要從資料庫查詢數據,資料庫壓力很大,查詢速度慢,因此設置緩存層,查詢數據時先從redis中查詢,如果查詢不到, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...