scalaz使用
在Scalaz上的迷你系列的这篇文章中,我们将介绍Scalaz中可用的几个附加的monad和模式。 再次,我们将研究实用的内容,并避免使用内部细节或Scalaz。 更准确地说,在本文中,我们将研究:
- Writer monad:在一组操作期间跟踪某种日志记录
- State monad:一种简单的方法来跟踪一组计算中的状态
- 镜头:轻松访问深层嵌套的属性,并使复制案例类更加方便
本系列当前提供以下文章:
- 日常使用的Scalaz功能第1部分:Typeclasses和Scala扩展
- 日常使用的Scalaz功能第2部分:Monad变形金刚和Reader Monad
- 日常使用的Scalaz功能第3部分:State Monad,Writer Monad和镜头
我们将从Scalaz提供的其他单子之一开始。
作家单子
基本上每个写程序都有一个日志和一个返回值。 这样,您就可以编写干净的代码,并在以后确定要执行的日志记录(例如,在测试中对其进行验证,将其输出到控制台或某些日志文件中)。 因此,例如,我们可以使用编写器来跟踪为达到某个特定值而执行的操作。
因此,让我们看一下代码,看看它是如何工作的:
import scalaz._
import Scalaz._
object WriterSample extends App {
// the left side can be any monoid. E.g something which support
// concatenation and has an empty function: e.g. String, List, Set etc.
type Result[T] = Writer[List[String], T]
def doSomeAction() : Result[Int] = {
// do the calculation to get a specific result
val res = 10
// create a writer by using set
res.set(List(s"Doing some action and returning res"))
}
def doingAnotherAction(b: Int) : Result[Int] = {
// do the calculation to get a specific result
val res = b * 2
// create a writer by using set
res.set(List(s"Doing another action and multiplying $b with 2"))
}
def andTheFinalAction(b: Int) : Result[String] = {
val res = s"bb:$b:bb"
// create a writer by using set
res.set(List(s"Final action is setting $b to a string"))
}
// returns a tuple (List, Int)
println(doSomeAction().run)
val combined = for {
a <- doSomeAction()
b <- doingAnotherAction(a)
c <- andTheFinalAction(b)
} yield c
// Returns a tuple: (List, String)
println(combined.run)
}
在此示例中,我们执行了三个操作。 在这种情况下,他们实际上并没有做那么多,但这没关系。 最主要的是,不是返回值,而是使用set函数返回Writer (请注意,我们也可以在for理解中创建writer)。 当我们在Writer上调用run时,我们不仅获得操作的结果,还获得Writer收集的合计值。 因此,当我们这样做时:
type Result[T] = Writer[List[String], T]
def doSomeAction() : Result[Int] = {
// do the calculation to get a specific result
val res = 10
// create a writer by using set
res.set(List(s"Doing some action and returning res"))
}
println(doSomeAction().run)
结果看起来像这样: (List(执行一些操作并返回res),10) 。 并不是那么令人兴奋,但是当我们开始理解时,它就会变得更加有趣。
val combined = for {
a <- doSomeAction()
b <- doingAnotherAction(a)
c <- andTheFinalAction(b)
} yield c
// Returns a tuple: (List, String)
println(combined.run)
当您查看此输出时,您将看到类似以下内容的内容:
(List(Doing some action and returning res,
Doing another action and multiplying 10 with 2,
Final action is setting 20 to a string)
,bb:20:bb)
如您所见,我们已经在List [String]中收集了所有不同的日志消息,并且生成的元组还包含最终的计算值。
当您不想在函数中添加Writer实例时,也可以像这样理解地创建writer:
val combined2 = for {
a <- doSomeAction1() set(" Executing Action 1 ") // A String is a monoid too
b <- doSomeAction2(a) set(" Executing Action 2 ")
c <- doSomeAction2(b) set(" Executing Action 3 ")
// c <- WriterT.writer("bla", doSomeAction2(b)) // alternative construction
} yield c
println(combined2.run)
该示例的结果是这样的:
( Executing Action 1 Executing Action 2 Executing Action 3 ,5)
酷吧? 在此示例中,我们仅显示了基本Writer内容,其中类型只是简单类型。 当然,您也可以从更复杂的类型创建Writer实例。 可以在这里找到一个示例: http : //stackoverflow.com/questions/35362240/creating-a-writertf-wa-from-a-writerw-a
州立单子
另一个有趣的单子是State单子。 state monad提供了一种方便的方式来处理需要通过一组函数传递的状态。 您可能需要跟踪结果,需要在一组函数周围传递一些上下文,或者由于其他原因需要一些(不可变的)上下文。 使用( Reader monad ),我们已经看到了如何将某些上下文注入到函数中。 但是,这种情况是不变的。 使用state monad时,我们可以得到一个不错的模式,我们可以使用该模式以安全纯净的方式传递可变的上下文。
让我们看一些例子:
case class LeftOver(size: Int)
/** A state transition, representing a function `S => (S, A)`. */
type Result[A] = State[LeftOver, A]
def getFromState(a: Int): Result[Int] = {
// do all kinds of computations
State[LeftOver, Int] {
// just return the amount of stuff we got from the state
// and return the new state
case x => (LeftOver(x.size - a), a)
}
}
def addToState(a: Int): Result[Int] = {
// do all kinds of computations
State[LeftOver, Int] {
// just return the amount of stuff we added to the state
// and return the new state
case x => (LeftOver(x.size + a), a)
}
}
val res: Result[Int] = for {
_ <- addToState(20)
_ <- getFromState(5)
_ <- getFromState(5)
a <- getFromState(5)
currentState <- get[LeftOver] // get the state at this moment
manualState <- put[LeftOver](LeftOver(9000)) // set the state to some new value
b <- getFromState(10) // and continue with the new state
} yield {
println(s"currenState: $currentState")
a
}
// we start with state 10, and after processing we're left with 5
// without having to pass state around using implicits or something else
println(res(LeftOver(10)))
如您所见,在每个函数中,我们都获得了当前上下文,对其进行了一些更改,并返回一个由新状态和函数值组成的元组。 这样,每个函数都可以访问状态,可以返回一个新状态,然后返回此新状态以及该函数的值作为元组。 当我们运行上面的代码时,我们看到以下内容:
currenState: LeftOver(15)
(LeftOver(8990),5)
如您所见,每个功能都对状态进行了处理。 使用get [S]函数,我们可以获取当前时刻的状态值,在本示例中,我们将其打印出来。 除了使用get函数外,我们还可以直接使用put函数设置状态。
如您所见,这是一种非常好用且简单易用的模式,但是当您需要在一组函数中传递一些状态时,则非常有用。
镜片
就现在的单子而言,已经足够了,让我们看看Lenses。 使用Lenses,可以轻松地(比仅手动复制案例类要容易得多)更改嵌套对象层次结构中的值。 镜头可以做很多事情,但是在本文中,我将仅介绍一些基本功能。 一,代码:
import scalaz._
import Scalaz._
object LensesSample extends App {
// crappy case model, lack of creativity
case class Account(userName: String, person: Person)
case class Person(firstName: String, lastName: String, address: List[Address], gender: Gender)
case class Gender(gender: String)
case class Address(street: String, number: Int, postalCode: PostalCode)
case class PostalCode(numberPart: Int, textPart: String)
val acc1 = Account("user123", Person("Jos", "Dirksen",
List(Address("Street", 1, PostalCode(12,"ABC")),
Address("Another", 2, PostalCode(21,"CDE"))),
Gender("male")))
val acc2 = Account("user345", Person("Brigitte", "Rampelt",
List(Address("Blaat", 31, PostalCode(67,"DEF")),
Address("Foo", 12, PostalCode(45,"GHI"))),
Gender("female")))
// when you now want to change something, say change the gender (just because we can) we need to start copying stuff
val acc1Copy = acc1.copy(
person = acc1.person.copy(
gender = Gender("something")
)
)
在此示例中,我们定义了几个案例类,并希望更改单个值。 对于案例类,这意味着我们必须开始嵌套一组复制操作才能正确更改其中一个嵌套值。 尽管可以为简单的层次结构完成此操作,但很快就会变得麻烦。 使用lensen,您可以通过一种可组合的方式来实现此目的:
val genderLens = Lens.lensu[Account, Gender](
(account, gender) => account.copy(person = account.person.copy(gender = gender)),
(account) => account.person.gender
)
// and with a lens we can now directly get the gender
val updated = genderLens.set(acc1, Gender("Blaat"))
println(updated)
#Output: Account(user123,Person(Jos,Dirksen,List(Address(Street,1,PostalCode(12,ABC)),
Address(Another,2,PostalCode(21,CDE))),Gender(Blaat)))
因此,我们定义了一个Lens,它可以更改层次结构中的特定值。 使用该镜头,我们现在可以直接在嵌套层次结构中获取或设置值。 我们还可以使用=> =运算符创建一个镜头,该镜头修改值并一次性返回修改后的对象。
// we can use our base lens to create a modify lens
val toBlaBlaLens = genderLens =>= (_ => Gender("blabla"))
println(toBlaBlaLens(acc1))
# Output: Account(user123,Person(Jos,Dirksen,List(Address(Street,1,PostalCode(12,ABC)),
Address(Another,2,PostalCode(21,CDE))),Gender(blabla)))
val existingGender = genderLens.get(acc1)
println(existingGender)
# Output: Gender(male)
我们可以使用> =>和<= <运算符将镜头组合在一起。 例如,在以下代码示例中,我们创建了单独的镜头,然后将其组合并执行:
// First create a lens that returns a person
val personLens = Lens.lensu[Account, Person](
(account, person) => account.copy(person = person),
(account) => account.person
)
// get the person lastname
val lastNameLens = Lens.lensu[Person, String](
(person, lastName) => person.copy(lastName = lastName),
(person) => person.lastName
)
// Get the person, then get the lastname, and then set the lastname to
// new lastname
val combined = (personLens >=> lastNameLens) =>= (_ => "New LastName")
println(combined(acc1))
# Output: Account(user123,Person(Jos,New LastName,List(Address(Street,1,PostalCode(12,ABC)),
Address(Another,2,PostalCode(21,CDE))),Gender(male)))
结论
我仍然要写两个主题,分别是Validation和Free monads。 在本系列的下一篇文章中,我将展示如何使用ValidationNEL进行验证。 但是,我认为免费的Monad并没有真正属于日常使用的范畴,因此将来我会在此基础上花费其他几篇文章。
scalaz使用