猫flatTap或scalatap
我很难理解猫 flatTap 的用途是什么?
def flatTap[B](f: A => F[B]): F[A] = typeClassInstance.flatTap[A, B](self)(f)
实际上,我只是想利用记录效果的价值,然后返回该效果。
似乎 scala 2.13 tap 是要走的路。然而,对于像Either这样的效果,我们需要测试一下:
(Right(2).withLeft[Throwable]).tap(....)
def tap[U](f: A => U): A = {
f(self)
self
}
关于如何在不复杂的情况下实现这一目标的任何建议,例如引入高级库等......
回答
想象一下你做了一些副作用:
def fetchUser(id: ID): F[User] // side effect - fetching from DB
def modifyUser(user: User): User // pure computation
def saveUser(user: User): F[Unit] // side effect - storing in DB
如果您提取,然后修改,然后尝试保存修改,会发生什么?
fetchUser(userID).map(modifyUser).flatMap(saveUser)
你会得到F[Unit]
. User
将被更新,但您将无法访问它。
因此,您可能认为可以执行以下操作:
fetchUser(userID).map(modifyUser).flatMap { modified =>
saveUser(modified).map(_ => modified)
}
除非每次要触发不应影响返回值的计算时都这样做会很烦人。这就是为什么我们有flatTap
fetchUser(userID).map(modifyUser).flatTap(saveUser) // F[User]
它与 有什么不同tap
?
假设您的F
= cats.effect.IO
。
两个都:
fetchUser(userID).map(modifyUser).flatTap(saveUser)
和
fetchUser(userID).map(modifyUser(_).tap(saveUser))
将具有相同的类型。
然而,最后一部分将是不同的值:
user =>
saveUser(u).map(_ => u)
user =>
saveUser(u) // lazy computation!!!
user // saveUser value is discarded!!!
所以flatTap
会确保saveUser
计算是程序蓝图的一部分,同时.tap
会创建一个计算配方并丢弃它而不将它组合到程序中。
典型的用例.tap
是您希望执行可以安全丢弃返回值的操作,而不会破坏您的逻辑。通常,它Unit
由立即产生副作用的操作返回:
// - we dont' want to return Unit
// - we can discard it and return original value
// - println is _eager_ so the side effects will happen immediately
// and so we won't skip them if we discard the value
calculateSomething().tap { value =>
println(value)
}
// each setX returns Unit so we couldn't chain them together
// (no .setA(...).setB(...)) but we can chain .taps
val value = new JavaObject()
.tap(_.setA("value"))
.tap(_.setB(1))
在日志记录的上下文中,您是否需要.tap
或.flatTap
取决于您使用的日志记录解决方案:一种立即记录(Log4j、Slf4J、Scala Logging 等)或一种F[Unit]
稍后返回以供评估的(Log4Cats)。类型应该引导你(你不能返回Unit
,.flatTap
也不能.tap
打开F[X]
并期望接收X
),但如果你有疑问,只需玩弄代码,看看不纯计算的行为与引用透明的计算行为有何不同。