Cats(4)- 叠加Free程序运算结果,Stacking monadic result types
在前面的几篇关于Free编程的讨论示范中我们均使用了基础类型的运算结果。但在实际应用中因为需要考虑运算中出现异常的情况,常常会需要到更高阶复杂的运算结果类型如Option、Xor等。因为Monad无法实现组合(monad do not compose),我们如何在for-comprehension中组合这些运算呢?假如在我们上一篇讨论里的示范DSL是这样的:
trait Login[+A]
case class Authenticate(uid: String, pwd: String) extends Login[Option[Boolean]] trait Auth[+A]
case class Authorize(uid: String) extends Auth[Xor[String,Boolean]]
这两个ADT在for-comprehension里如果我们勉强将Option和Xor叠加在一起就会产生所谓下台阶式运算(stair-stepping),因为monad do not compose! 我们可以看看下面的示范:
type Result[A] = Xor[String,Option[A]]
def getResult: Result[Int] = .some.right //> getResult: => demo.ws.catsMTX.Result[Int]
for {
optValue <- getResult
} yield {
for {
valueA <- optValue
} yield valueA + //> res0: cats.data.Xor[String,Option[Int]] = Right(Some(80))
}
我们必须用两层for-comprehension来组合最终结果。这就是所谓的下台阶运算了。如果遇到三层叠加类型,那么整个程序会变得更加复杂了。其实不单是程序结构复杂问题,更重要的是运算效果(effect)无法正确体现:出现None和Left值时并不能立即终止for-comprehension、再就是如果第一层是有副作用(side-effect)运算时,由于我们必须先得出第一层的运算结果才能进行下一层运算,所以这个for-comprehension产生了不纯代码(impure-code),如下:
for {
optionData <- IO {readDB()}
} yield {
for {
data <- optionData
} yield Process(data)
}
我们必须先运算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来把一个值升格成堆叠类型:
import cats._,cats.instances.all._
import cats.data.{Xor,XorT}
import cats.syntax.xor._
import cats.data.OptionT
import cats.syntax.option._
import cats.syntax.applicative._ type Error[A] = Xor[String,A]
type XResult[A] = OptionT[Error,A]
type OResult[A] = XorT[Option,String,A]
Applicative[XResult].pure() //> res0: demo.ws.catsMTX.XResult[Int] = OptionT(Right(Some(62)))
.pure[XResult] //> res1: demo.ws.catsMTX.XResult[Int] = OptionT(Right(Some(62)))
Applicative[OResult].pure() //> res2: demo.ws.catsMTX.OResult[Int] = XorT(Some(Right(62)))
.pure[OResult] //> res3: demo.ws.catsMTX.OResult[Int] = XorT(Some(Right(62)))
注意,用Applicative.pure来升格None或者Left会产生错误结果:
Applicative[XResult].pure(none[Int])
//> res4: demo.ws.catsMTX.XResult[Option[Int]] = OptionT(Right(Some(None)))
(None: Option[Int]).pure[XResult]
//> res5: demo.ws.catsMTX.XResult[Option[Int]] = OptionT(Right(Some(None)))
Applicative[XResult].pure("oh no".left[Int])
//> res6: demo.ws.catsMTX.XResult[cats.data.Xor[String,Int]] = OptionT(Right(Some(Left(oh no))))
(Left[String,Int]("oh no")).pure[XResult]
//> res7: demo.ws.catsMTX.XResult[scala.util.Left[String,Int]] = OptionT(Right(Some(Left(oh no))))
Applicative[OResult].pure(Left[String,Int]("oh no"))
//> res8: demo.ws.catsMTX.OResult[scala.util.Left[String,Int]] = XorT(Some(Right(Left(oh no))))
"oh no".left[Int].pure[OResult]
//> res9: demo.ws.catsMTX.OResult[cats.data.Xor[String,Int]] = XorT(Some(Right(Left(oh no))))
Applicative[OResult].pure(none[Int])
//> res10: demo.ws.catsMTX.OResult[Option[Int]] = XorT(Some(Right(None)))
(None: Option[Int]).pure[OResult]
//> res11: demo.ws.catsMTX.OResult[Option[Int]] = XorT(Some(Right(None)))
Applicative[OResult].pure("oh no".left[Int])
//> res12: demo.ws.catsMTX.OResult[cats.data.Xor[String,Int]] = XorT(Some(Right(Left(oh no))))
(Left[String,Int]("oh no")).pure[OResult]
//> res13: demo.ws.catsMTX.OResult[scala.util.Left[String,Int]] = XorT(Some(Right(Left(oh no))))
Some(None),Right(Left("oh no)))是什么意思呢?明显是错误。我们必须用MonadTransformer的构建器(constructor)才能正确的对这些边际值进行升格:
OptionT(none[Int].pure[Error])
//> res14: cats.data.OptionT[demo.ws.catsMTX.Error,Int] = OptionT(Right(None))
OptionT("oh no".left: Error[Option[Int]])
//> res15: cats.data.OptionT[demo.ws.catsMTX.Error,Int] = OptionT(Left(oh no))
XorT(none[Error[Int]])
//> res16: cats.data.XorT[Option,String,Int] = XorT(None)
XorT("oh no".left[Int].pure[Option])
//> res17: cats.data.XorT[Option,String,Int] = XorT(Some(Left(oh no)))
下面我们示范一下在for-comprehension中运算Xor[?Option[A]]这种堆叠类型:
type Error[A] = Xor[String,A]
type XResult[A] = OptionT[Error,A]
type OResult[A] = XorT[Option,String,A]
def getXor(s: String): Error[String] = s.right //> getXor: (s: String)demo.ws.catsMTX.Error[String]
def getOption(s: String): Option[String] = s.some
//> getOption: (s: String)Option[String]
val composed: XResult[String] =
for {
s1 <- OptionT.liftF(getXor("Hello "))
s2 <- OptionT.liftF(getXor("World!"))
s3 <- OptionT(getOption("come to papa!").pure[Error])
} yield s1 + s2 + s3 //> composed : demo.ws.catsMTX.XResult[String] = OptionT(Right(Some(Hello World!come to papa!)))
composed.value //> res18: demo.ws.catsMTX.Error[Option[String]] = Right(Some(Hello World!come to papa!))
测试一下Xor,Option的left和none效果:
val composed: XResult[String] =
for {
s1 <- OptionT.liftF(getXor("Hello "))
s0 <- OptionT(none[String].pure[Error])
s2 <- OptionT.liftF(getXor("World!"))
s3 <- OptionT(getOption("come to papa!").pure[Error])
} yield s1 + s2 + s3 //> composed : demo.ws.catsMTX.XResult[String] = OptionT(Right(None))
composed.value //> res18: demo.ws.catsMTX.Error[Option[String]] = Right(None) val composed: XResult[String] =
for {
s1 <- OptionT.liftF(getXor("Hello "))
s0 <- OptionT("oh no".left: Error[Option[Int]])
s2 <- OptionT.liftF(getXor("World!"))
s3 <- OptionT(getOption("come to papa!").pure[Error])
} yield s1 + s2 + s3 //> composed : demo.ws.catsMTX.XResult[String] = OptionT(Left(oh no))
composed.value //> res18: demo.ws.catsMTX.Error[Option[String]] = Left(oh no)
从运算结果我们看到在for-comprehension中这个堆叠类型的组成类型Xor和Option的效果可以得到体现。
在现实中三层以上的运算结果类型堆叠还是很普遍的,如:Future[Xor[?,Option[A]]]。要注意MonadTransformer类型堆叠的顺序是重要的,而且是由内向外的,决定着最终运算结果的类型。如果增加一层Future类型,我们就需要把它放到堆叠结构的最内部:
type FError[A] = XorT[Future,String,A]
type FResult[A] = OptionT[FError,A]
现在我们需要考虑如何进行MonadTransformer类型的升格了。请相信我,这项工作绝对是一场噩梦。具体示范可以在我这篇博客scalaz-monadtransformer中找到。我的意思是如果没有更好的办法,这项工作基本是一项不可能的任务(mission impossible)。
对于上面提出的问题,freeK提供了很好的解决方法。freeK的Onion数据类型就是为简化Monad堆叠操作而设计的。Onion表达形式如下:
type Stack[A] = F[G[H[I[A]]]]
type O = F :&: G :&: H :&: I :&: Bulb
type Stack[A] = O#Layers[A]
O就是Onion类型,代表了一个Monad堆叠。我们可以用O#Layers[A]返还原始的多层Monad,如下面的示例:
import freek._
type O = Xor[String,?] :&: Option :&: Bulb
type MStack[A] = O#Layers[A]
我们用一个具体的Free程序来示范堆叠Monad运算结果的操作。假如例子的ADT是这样的:
sealed trait Foo[A]
final case class Foo1(s: String) extends Foo[Option[Int]]
final case class Foo2(i: Int) extends Foo[Xor[String, Int]]
final case object Foo3 extends Foo[Unit]
final case class Foo4(i: Int) extends Foo[Xor[String, Option[Int]]] sealed trait Bar[A]
final case class Bar1(s: String) extends Bar[Option[String]]
final case class Bar2(i: Int) extends Bar[Xor[String, String]] sealed trait Laa[A]
final case class Push(s: String) extends Laa[List[String]]
从模拟运算结果类型来看,我们将面对相当复杂的三层Monad堆叠。我们先用Foo,Bar来示范两层堆叠的DSL。首先,我们希望使用DSL的语法如下:
for {
i <- Foo1("").freek[PRG] // 运算结果是: Option[String]
s <- Bar2(i).freek[PRG] // 运算结果是: Xor[String, String]
...
} yield (())
我们希望对运算结果进行一种升格:把它们升格成一致堆叠类型,如下:
Free[PRG.Cop, Option[A]]
// 和这个类型
Free[PRG.Cop, Xor[String, A]] // 一致升格成
Free[PRG.Cop, Xor[String, Option[A]]] // 也就是这个
type O = Xor[String, ?] :&: Option :&: Bulb
Free[PRG.Cop, O#Layers]
像cats的MonadTransformer,freeK也提供了个OnionT,OnionT代表Monad堆叠类型容器。我们希望实现以下升格(lifting)操作:
//把
Free[PRG.Cop, Option[A]
//或
Xor[String, A]]
//统统转成
OnionT[Free, PRG.Cop, O, A]
我们可以用.onionT[O]来升格:
type PRG = Foo :|: Bar :|: NilDSL
val PRG = DSL.Make[PRG]
type O = Xor[String,?] :&: Option :&: Bulb
val prg: OnionT[Free,PRG.Cop,O,Int]= for {
i <- Foo1("").freek[PRG].onionT[O]
i2 <- Foo2(i).freek[PRG].onionT[O]
_ <- Foo3.freek[PRG].onionT[O]
s <- Bar1(i2.toString).freek[PRG].onionT[O]
i3 <- Foo4(i2).freek[PRG].onionT[O]
} yield (i3)
我们可以用比较简单点的表达形式freeko来示范同样效果:
val prg2: OnionT[Free,PRG.Cop,O,Int]= for {
i <- Foo1("").freeko[PRG,O]
i2 <- Foo2(i).freeko[PRG,O]
_ <- Foo3.freeko[PRG,O]
s <- Bar1(i2.toString).freeko[PRG,O]
i3 <- Foo4(i2).freeko[PRG,O]
} yield (i3)
注意,现在程序prg的返回结果类型是OnionT。但我们的运算interpret函数是在Free上面的。OnionT.value可以返回Free类型:
pre.value
//res12: cats.free.Free[PRG.Cop,O#Layers[Int]] = Free(...)
所以运算程序方式要调整成:prg.value.interpret(interpreters)
如果我们再增加一层Monad堆叠呢?
type PRG3 = Laa :|: Foo :|: Bar :|: NilDSL
val PRG3 = DSL.Make[PRG3]
type O3 = List :&: Xor[String,?] :&: Option :&: Bulb
val prg3: OnionT[Free,PRG3.Cop,O3,Int]= for {
i <- Foo1("").freeko[PRG3,O3]
i2 <- Foo2(i).freeko[PRG3,O3]
_ <- Foo3.freeko[PRG3,O3]
s <- Bar1(i2.toString).freeko[PRG3,O3]
i3 <- Foo4(i2).freeko[PRG3,O3]
_ <- Push(s).freeko[PRG3,O3]
} yield (i3)
就是这么简单。
下面我们把上篇讨论的用户验证示范例子的运算结果类型调整成复杂类型,然后用freeK.Onion来完善程序。先调整ADT:
object ADTs {
sealed trait Interact[+A]
object Interact {
case class Ask(prompt: String) extends Interact[Xor[String,String]]
case class Tell(msg: String) extends Interact[Unit]
}
sealed trait Login[+A]
object Login {
case class Authenticate(uid: String, pwd: String) extends Login[Option[Boolean]]
}
sealed trait Auth[+A]
object Auth {
case class Authorize(uid: String) extends Auth[Option[Boolean]]
}
}
我们把运算结果改成了Xor,Option。再看看DSL调整:
object DSLs {
import ADTs._
import Interact._
import Login._
type PRG = Interact :|: Login :|: NilDSL
val PRG = DSL.Make[PRG]
type O = Xor[String,?] :&: Option :&: Bulb
val authenticDSL: OnionT[Free,PRG.Cop, O, Boolean] =
for {
uid <- Ask("Enter your user id:").freeko[PRG,O]
pwd <- Ask("Enter password:").freeko[PRG,O]
auth <- Authenticate(uid,pwd).freeko[PRG,O]
} yield auth
type O2 = Option :&: Xor[String,?] :&: Bulb
val authenticDSLX =
for {
uid <- Ask("Enter your user id:").freeko[PRG,O2].peelRight
pwd <- Ask("Enter password:").freeko[PRG,O2].peelRight
auth <- (uid,pwd) match {
case (Xor.Right(u),Xor.Right(p)) => Authenticate(u,p).freeko[PRG,O2].peelRight
case _ => Authenticate("","").freeko[PRG,O2].peelRight
}
} yield auth
val interactLoginDSL: OnionT[Free,PRG.Cop, O, Unit] =
for {
uid <- Ask("Enter your user id:").freeko[PRG,O]
pwd <- Ask("Enter password:").freeko[PRG,O]
auth <- Authenticate(uid,pwd).freeko[PRG,O]
_ <- if (auth) Tell(s"Hello $uid, welcome to the zoo!").freeko[PRG,O]
else Tell(s"Sorry, Who is $uid?").freeko[PRG,O]
} yield () import Auth._
type PRG3 = Auth :|: PRG //Interact :|: Login :|: NilDSL
val PRG3 = DSL.Make[PRG3]
val authorizeDSL: OnionT[Free,PRG3.Cop, O , Unit] =
for {
uid <- Ask("Enter your User ID:").freeko[PRG3,O]
pwd <- Ask("Enter your Password:").freeko[PRG3,O]
auth <- Authenticate(uid,pwd).freeko[PRG3,O]
perm <- if (auth) Authorize(uid).freeko[PRG3,O]
else OnionT.pure[Free,PRG3.Cop,O,Boolean](false)
_ <- if (perm) Tell(s"Hello $uid, access granted!").freeko[PRG3,O]
else Tell(s"Sorry $uid, access denied!").freeko[PRG3,O]
} yield()
}
注意上面代码中这个authenticDSLX:当我们需要对Option:&:Xor:&:Bulb中的整个Xor值而不是运算值A来操作时可以用peelRight来获取这个Xor。如果有需要的话我们还可以用peelRight2,peelRight3来越过二、三层类型。具体实现interpreter部分也需要按照ADT的运算结果类型来调整:
object IMPLs {
import ADTs._
import Interact._
import Login._
import Auth._
val idInteract = new (Interact ~> Id) {
def apply[A](ia: Interact[A]): Id[A] = ia match {
case Ask(p) => {println(p); (scala.io.StdIn.readLine).right}
case Tell(m) => println(m)
}
}
val idLogin = new (Login ~> Id) {
def apply[A](la: Login[A]): Id[A] = la match {
case Authenticate(u,p) => (u,p) match {
case ("Tiger","") => true.some
case _ => false.some
}
}
}
val interactLogin = idInteract :&: idLogin
import Dependencies._
type ReaderContext[A] = Reader[Authenticator,A]
object readerInteract extends (Interact ~> ReaderContext) {
def apply[A](ia: Interact[A]): ReaderContext[A] = ia match {
case Ask(p) => Reader {pc => {println(p); (scala.io.StdIn.readLine).right}}
case Tell(m) => Reader {_ => println(m)}
}
}
object readerLogin extends (Login ~> ReaderContext) {
def apply[A](la: Login[A]): ReaderContext[A] = la match {
case Authenticate(u,p) => Reader {pc => pc.matchUserPassword(u,p).some}
}
}
val userInteractLogin = readerLogin :&: readerInteract val readerAuth = new (Auth ~> ReaderContext) {
def apply[A](aa: Auth[A]): ReaderContext[A] = aa match {
case Authorize(u) => Reader {ac => ac.grandAccess(u).some}
}
}
val userAuth = readerAuth :&: userInteractLogin
}
具体运行方式需要调整成:
authorizeDSL.value.interpret(userAuth).run(AuthControl)
测试运行与我们上篇示范相同。
完整的示范源代码如下:
import cats.instances.all._
import cats.free.Free
import cats.{Id, ~>}
import cats.data.Reader
import freek._
import cats.data.Xor
import cats.syntax.xor._
import cats.syntax.option._
object FreeKModules {
object ADTs {
sealed trait Interact[+A]
object Interact {
case class Ask(prompt: String) extends Interact[Xor[String,String]]
case class Tell(msg: String) extends Interact[Unit]
}
sealed trait Login[+A]
object Login {
case class Authenticate(uid: String, pwd: String) extends Login[Option[Boolean]]
}
sealed trait Auth[+A]
object Auth {
case class Authorize(uid: String) extends Auth[Option[Boolean]]
}
}
object DSLs {
import ADTs._
import Interact._
import Login._
type PRG = Interact :|: Login :|: NilDSL
val PRG = DSL.Make[PRG]
type O = Xor[String,?] :&: Option :&: Bulb
val authenticDSL: OnionT[Free,PRG.Cop, O, Boolean] =
for {
uid <- Ask("Enter your user id:").freeko[PRG,O]
pwd <- Ask("Enter password:").freeko[PRG,O]
auth <- Authenticate(uid,pwd).freeko[PRG,O]
} yield auth
type O2 = Option :&: Xor[String,?] :&: Bulb
val authenticDSLX =
for {
uid <- Ask("Enter your user id:").freeko[PRG,O2].peelRight
pwd <- Ask("Enter password:").freeko[PRG,O2].peelRight
auth <- (uid,pwd) match {
case (Xor.Right(u),Xor.Right(p)) => Authenticate(u,p).freeko[PRG,O2].peelRight
case _ => Authenticate("","").freeko[PRG,O2].peelRight
}
} yield auth
val interactLoginDSL: OnionT[Free,PRG.Cop, O, Unit] =
for {
uid <- Ask("Enter your user id:").freeko[PRG,O]
pwd <- Ask("Enter password:").freeko[PRG,O]
auth <- Authenticate(uid,pwd).freeko[PRG,O]
_ <- if (auth) Tell(s"Hello $uid, welcome to the zoo!").freeko[PRG,O]
else Tell(s"Sorry, Who is $uid?").freeko[PRG,O]
} yield () import Auth._
type PRG3 = Auth :|: PRG //Interact :|: Login :|: NilDSL
val PRG3 = DSL.Make[PRG3]
val authorizeDSL: OnionT[Free,PRG3.Cop, O , Unit] =
for {
uid <- Ask("Enter your User ID:").freeko[PRG3,O]
pwd <- Ask("Enter your Password:").freeko[PRG3,O]
auth <- Authenticate(uid,pwd).freeko[PRG3,O]
perm <- if (auth) Authorize(uid).freeko[PRG3,O]
else OnionT.pure[Free,PRG3.Cop,O,Boolean](false)
_ <- if (perm) Tell(s"Hello $uid, access granted!").freeko[PRG3,O]
else Tell(s"Sorry $uid, access denied!").freeko[PRG3,O]
} yield() }
object IMPLs {
import ADTs._
import Interact._
import Login._
import Auth._
val idInteract = new (Interact ~> Id) {
def apply[A](ia: Interact[A]): Id[A] = ia match {
case Ask(p) => {println(p); (scala.io.StdIn.readLine).right}
case Tell(m) => println(m)
}
}
val idLogin = new (Login ~> Id) {
def apply[A](la: Login[A]): Id[A] = la match {
case Authenticate(u,p) => (u,p) match {
case ("Tiger","") => true.some
case _ => false.some
}
}
}
val interactLogin = idInteract :&: idLogin
import Dependencies._
type ReaderContext[A] = Reader[Authenticator,A]
object readerInteract extends (Interact ~> ReaderContext) {
def apply[A](ia: Interact[A]): ReaderContext[A] = ia match {
case Ask(p) => Reader {pc => {println(p); (scala.io.StdIn.readLine).right}}
case Tell(m) => Reader {_ => println(m)}
}
}
object readerLogin extends (Login ~> ReaderContext) {
def apply[A](la: Login[A]): ReaderContext[A] = la match {
case Authenticate(u,p) => Reader {pc => pc.matchUserPassword(u,p).some}
}
}
val userInteractLogin = readerLogin :&: readerInteract val readerAuth = new (Auth ~> ReaderContext) {
def apply[A](aa: Auth[A]): ReaderContext[A] = aa match {
case Authorize(u) => Reader {ac => ac.grandAccess(u).some}
}
}
val userAuth = readerAuth :&: userInteractLogin
} }
object Dependencies {
trait PasswordControl {
val mapPasswords: Map[String,String]
def matchUserPassword(uid: String, pswd: String): Boolean
}
trait AccessControl {
val mapAccesses: Map[String, Boolean]
def grandAccess(uid: String): Boolean
}
trait Authenticator extends PasswordControl with AccessControl
} object freeKDemo extends App {
import FreeKModules._
import DSLs._
import IMPLs._
// val r0 = authenticDSL.foldMap(interactLogin.nat)
// val r = authenticDSL.interpret(interactLogin)
import Dependencies._
object AuthControl extends Authenticator {
override val mapPasswords = Map(
"Tiger" -> "",
"John" -> ""
)
override def matchUserPassword(uid: String, pswd: String) =
mapPasswords.getOrElse(uid, pswd+"!") == pswd override val mapAccesses = Map (
"Tiger" -> true,
"John" -> false
)
override def grandAccess(uid: String) =
mapAccesses.getOrElse(uid, false)
} // interactLoginDSL.value.interpret(userInteractLogin).run(AuthControl)
authorizeDSL.value.interpret(userAuth).run(AuthControl)
}
Cats(4)- 叠加Free程序运算结果,Stacking monadic result types的更多相关文章
- 笔记-python-实用-程序运算时间计算
方法1 import datetime starttime = datetime.datetime.now() #long running endtime = datetime.datetime.no ...
- Cats(3)- freeK-Free编程更轻松,Free programming with freeK
在上一节我们讨论了通过Coproduct来实现DSL组合:用一些功能简单的基础DSL组合成符合大型多复杂功能应用的DSL.但是我们发现:cats在处理多层递归Coproduct结构时会出现编译问题.再 ...
- 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)
编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ...
- 编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议16~20)
建议16:易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP,Ruby,Groovy.Javascript等,这些入侵者都有一个共同特征:全是同一类语言-----脚本语言,它 ...
- Scalaz(53)- scalaz-stream: 程序运算器-application scenario
从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流 ...
- C语言学习-01第一个C语言程序
一 C语言的历史 C语言是一门通用计算机编程语言,应用广泛.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言. 尽管C语言提供 ...
- 支持向量机(SVM)理论总结系列.线性可分(附带R程序案例:用体重和心脏重量来预测一只猫的性别)
附注:不要问我为什么写这么快,是16年写的. 1.名词解释 支持向量机中的机:在机器学习领域,常把一些算法看做一个机器,如分类机(也叫作分类器) 2.问题描述 空间中有很多已知类别的点,现在想用一个面 ...
- Akka(8): 分布式运算:Remoting-远程查找式
Akka是一种消息驱动运算模式,它实现跨JVM程序运算的方式是通过能跨JVM的消息系统来调动分布在不同JVM上ActorSystem中的Actor进行运算,前题是Akka的地址系统可以支持跨JVM定位 ...
- Python 的第一个小程序
F盘 新建文本文档 hello.txt 内容为: print("hello world! hello 2018!"); 打开CMD cd c:\ ...
随机推荐
- 文件上传之Apache commons fileupload使用
后台servlet代码: File file1 = null,file2=null; String description1 = null,description2 = ...
- 我也来说说DDD~大话目录
回到占占推荐博客索引 DDD之前没有接触过,但一但有了接触就一发不可收拾,他会带去进入一个全新的世界! DDD不是新技术,而是新思想,新模式,是软件开发领域的一次突破,它更接近于业务,对于业务的改动它 ...
- slave IO流程之一:mysql登陆过程(mysql_real_connect)
最近看了slave IO的源码,发现slave IO的写relay log貌似是单线程单连接的,这让我有点小失望. slave IO的主函数是handle_slave_io,处理流程如下: 图1 ha ...
- Leetcode-24 Swap Nodes in Pairs
#24. Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. For ...
- 如何在多线程leader-follower模式下正确的使用boost::asio。
#include <assert.h> #include <signal.h> #include <unistd.h> #include <iostream& ...
- LINQ系列:Linq to Object联接操作符
联接是指将一个数据源对象与另一个数据源对象进行关联或联合的操作.这两个数据源对象通过一个共同的值或属性进行关联. LINQ的联接操作符将包含可匹配(或相同)关键字的两个或多个数据源中的值进行匹配. L ...
- 使用jQuery的animate方法制作滑动菜单
周末看Ziv小威的博客<制作滑动条菜单,如何延时处理滑动效果,避免动画卡顿>,参见地址:http://www.cnblogs.com/zivxiaowei/p/3462964.html.是 ...
- js 把数字转成2 ,8,16进制的方法
直接上代码 <!DOCTYPE html> <html> <body> <script> var myNumber = 128; document.wr ...
- spring整合hibernate的详细步骤
Spring整合hibernate需要整合些什么? 由IOC容器来生成hibernate的sessionFactory. 让hibernate使用spring的声明式事务 整合步骤: 加入hibern ...
- JavaScript 获取HTML中的CSS样式的属性以及值的的方法。
<body> <div id="wow" style="font-size:10px; float:left"></div> ...