链功能不同

lik*_*ern 6 methods scala implicit composition method-chaining

Scala函数具有以下链接方法:

 fn1.andThen(fn2)
 fn1.compose(fn2)
Run Code Online (Sandbox Code Playgroud)

但是如何写这种情况:

我有cleanUp()必须始终作为最后一步调用的函数。我还有很多其他功能,例如:

class Helper {
  private[this] val umsHelper = new UmsHelper()
  private[this] val user = umsHelper.createUser()
  def cleanUp = ... // delete user/ and other entities

  def prepareModel(model: TestModel) = {
    // create model on behalf of the user
  }

  def commitModel() = {
    // commit model on behalf of the user
  }
}
Run Code Online (Sandbox Code Playgroud)

而且一些外部代码可以使用如下代码:

val help = new Helper()
help.prepareModel()
help.commitModel()
// last step should be called implicitly cleanUp
Run Code Online (Sandbox Code Playgroud)

如何以一种功能性的方式编写代码,即链接将始终cleanUp隐式调用函数作为最后一步?

注意:我将其视为C ++中的析构函数的类似物。fn1 andLater fn2 andLater fn3必须在最后一步cleanUpfn1 andLater fn2 andLater fn3 andLater cleanUp)调用一些链接(无论该链接的完成方式如何)。直接写入cleanUp方法有误,有人很有可能会错过此步骤,并且用户将被泄漏(将留在数据库中)

maa*_*asg 6

这是一个更高级的替代方法:

当您听到“上下文”和“步骤”时,会直接想到一种功能模式:Monads。汇总您自己的monad实例可以简化将有效步骤放在一起的用户端,同时提供保证在执行这些步骤后将清除上下文。

在这里,我们将开发遵循该模式的“ CleanableContext”构造。

我们的构造基于最简单的monad,其唯一功能是保存值。我们将其称为Context

trait Context[A] { self => 
  def flatMap[B](f:A => Context[B]): Context[B] = f(value)
  def map[B](f:A => B): Context[B] = flatMap(f andThen ((b:B) => Context(b)))
  def value: A
}

object Context {
  def apply[T](x:T): Context[T] = new Context[T] { val value = x  }
}
Run Code Online (Sandbox Code Playgroud)

然后,我们提供了一个CleanableContext,它能够“自行清除”,并提供了一些“清除”功能:

trait CleanableContext[A] extends Context[A] {
  override def flatMap[B](f:A => Context[B]): Context[B] = {
    val res = super.flatMap(f)
    cleanup
    res
  }
  def cleanup: Unit
}
Run Code Online (Sandbox Code Playgroud)

现在,我们有了一个能够产生可清洁物体的对象,该物体UserContext将负责管理用户的创建和销毁。

object UserContext {
  def apply(x:UserManager): CleanableContext[User] = new CleanableContext[User] {
    val value = x.createUser
    def cleanup = x.deleteUser(value)
  }
}
Run Code Online (Sandbox Code Playgroud)

假设我们已经定义了模型和业务功能:

trait Model
trait TestModel extends Model
trait ValidatedModel extends Model
trait OpResult
object Ops {
  def prepareModel(user: User, model: TestModel): Model = new Model {}

  def validateModel(model: Model): ValidatedModel = new ValidatedModel {}

  def commitModel(user: User, vmodel: ValidatedModel): OpResult = new OpResult {}
}
Run Code Online (Sandbox Code Playgroud)

用法

有了这些可重复使用的机器,我们的用户可以简洁地表达我们的流程:

import Ops._
val ctxResult = for {
  user <- UserContext(new UserManager{})
  validatedModel <- Context(Ops.prepareModel(user, testModel)).map(Ops.validateModel)
  commitResult <- Context(commitModel(user, validatedModel))
} yield commitResult
Run Code Online (Sandbox Code Playgroud)

该过程的结果仍被封装,可以Context使用value方法从中“取出” :

val result = ctxResult.value
Run Code Online (Sandbox Code Playgroud)

注意,我们需要将业务操作封装到一个Context要在此monadic组合中使用的。还要注意,我们不需要手动创建或清理用于操作的用户。这已经为我们做好了。

此外,如果我们需要一种以上的托管资源,则可以通过组合不同的上下文来使用此方法来管理其他资源。

这样,我只想为这个问题提供另一个角度。管道更为复杂,但是它为用户通过组合创建安全的过程奠定了坚实的基础。