链式函数以不同的方式
Chain functions in different way
Scala 函数具有以下链接方法:
fn1.andThen(fn2)
fn1.compose(fn2)
但是这个case怎么写:
我有函数 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
}
}
一些外部代码可以使用类似这样的代码:
val help = new Helper()
help.prepareModel()
help.commitModel()
// last step should be called implicitly cleanUp
如何以函数式方式编写,链接总是
最后一步隐式调用 cleanUp
函数?
注:我将其视为 C++ 中析构函数的类比。一些链接(不管这个链是如何完成的)fn1 andLater fn2 andLater fn3
必须作为最后一步调用 cleanUp
(fn1 andLater fn2 andLater fn3 andLater cleanUp
)。直接写 cleanUp
方法是错误的,很可能有人会错过这一步并且 user 将被泄露(将保留在数据库中)
使用autoClean
这会在最后自动调用cleanUp
。
创建一个包含所有必要功能的 HelperStuff 特征。
在 Helper 对象中创建 HelperStuff 的私有实现,然后有一个名为 autoClean 的方法方法,它可以使 Helper 实例保持私有且安全,远离恶意用户。
Helper.autoClean { helperStuff =>
//write all your code here. clean up will happen automatically
helper.foo()
helper.commitModel()
}
这是为您准备的自动清洁功能
trait HelperStuff {
def foo(): Unit
def commitModel: Unit
def cleanUp(): Unit
}
object Helper {
private class Helper extends HelperStuff {
def foo(): Unit = println("foo")
def cleanUp(): Unit = println("cleaning done")
}
private val helper = new Helper()
def autoClean[T](code: HelperStuff => T): T = {
val result = code(helper)
helper.cleanUp()
result
}
}
我认为问题的核心是"how to keep a resource within a managed context"。即为用户提供一种使用资源的方法,并防止它'leak'超出其上下文。
一种可能的方法是提供对托管资源的功能访问,其中 API 需要对相关资源进行操作的功能。让我用一个例子来说明这一点:
首先,我们定义模型的域:(我添加了模型的一些子类型以使示例更清楚)
trait User
trait Model
trait TestModel extends Model
trait ValidatedModel extends Model
trait OpResult
// Some external resource provider
trait Ums {
def createUser: User
def deleteUser(user: User)
}
然后我们创建一个 class 来保存我们的特定上下文。
class Context {
private val ums = new Ums{
def createUser = new User{}
def deleteUser(user: User) = ???
}
def withUserDo[T](ops: User => T):T = {
val user = ums.createUser
val result = ops(user)
ums.deleteUser(user)
result
}
}
伴随对象提供对托管资源的(一些)操作。用户也可以提供自己的功能。
object Context {
def prepareModel(model: TestModel): User => Model = ???
val validateModel: Model => ValidatedModel = ???
val commitModel: ValidatedModel => OpResult = ???
}
我们可以使用 classic 声明实例化我们的上下文并在其上声明操作,例如:
val ctx = new Context
val testModel = new TestModel{}
val result = ctx.withUserDo{ user =>
val preparedModel = prepareModel(testModel)(user)
val validatedModel = validateModel(preparedModel)
commitModel(validatedModel)
}
或者,鉴于问题中希望使用功能组合,我们可以将其重写为:
val result = ctx.withUserDo{
prepareModel(testModel) andThen validateModel andThen commitModel
}
这是一个更高级的选择:
当您听到 "context" 和 "steps" 时,您会直接想到一种功能模式:Monad。汇总你自己的 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 }
}
然后我们有一个 CleanableContext
,它能够 "cleaning up after itself" 提供一些 'cleanup' 功能:
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
}
现在,我们有一个能够生成可清理的对象 UserContext
,它将负责管理用户的创建和销毁。
object UserContext {
def apply(x:UserManager): CleanableContext[User] = new CleanableContext[User] {
val value = x.createUser
def cleanup = x.deleteUser(value)
}
}
假设我们还定义了模型和业务功能:
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 {}
}
用法
有了可重复使用的机器,我们的用户可以用简洁的方式表达我们的过程:
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
过程的结果仍然是封装的,可以用value
方法从Context
中取出"out":
val result = ctxResult.value
请注意,我们需要将业务操作封装到一个 Context
中,以便在这个 单子组合 中使用。另请注意,我们不需要手动创建或清理用于操作的用户。这是为我们照顾的。
此外,如果我们需要一种以上的托管资源,可以使用此方法通过将不同的上下文组合在一起来管理其他资源。
至此,我只是想提供另一个角度来看待这个问题。管道更复杂,但它为用户通过组合创建安全流程奠定了坚实的基础。
Scala 函数具有以下链接方法:
fn1.andThen(fn2)
fn1.compose(fn2)
但是这个case怎么写:
我有函数 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
}
}
一些外部代码可以使用类似这样的代码:
val help = new Helper()
help.prepareModel()
help.commitModel()
// last step should be called implicitly cleanUp
如何以函数式方式编写,链接总是
最后一步隐式调用 cleanUp
函数?
注:我将其视为 C++ 中析构函数的类比。一些链接(不管这个链是如何完成的)fn1 andLater fn2 andLater fn3
必须作为最后一步调用 cleanUp
(fn1 andLater fn2 andLater fn3 andLater cleanUp
)。直接写 cleanUp
方法是错误的,很可能有人会错过这一步并且 user 将被泄露(将保留在数据库中)
使用autoClean
这会在最后自动调用cleanUp
。
创建一个包含所有必要功能的 HelperStuff 特征。
在 Helper 对象中创建 HelperStuff 的私有实现,然后有一个名为 autoClean 的方法方法,它可以使 Helper 实例保持私有且安全,远离恶意用户。
Helper.autoClean { helperStuff =>
//write all your code here. clean up will happen automatically
helper.foo()
helper.commitModel()
}
这是为您准备的自动清洁功能
trait HelperStuff {
def foo(): Unit
def commitModel: Unit
def cleanUp(): Unit
}
object Helper {
private class Helper extends HelperStuff {
def foo(): Unit = println("foo")
def cleanUp(): Unit = println("cleaning done")
}
private val helper = new Helper()
def autoClean[T](code: HelperStuff => T): T = {
val result = code(helper)
helper.cleanUp()
result
}
}
我认为问题的核心是"how to keep a resource within a managed context"。即为用户提供一种使用资源的方法,并防止它'leak'超出其上下文。
一种可能的方法是提供对托管资源的功能访问,其中 API 需要对相关资源进行操作的功能。让我用一个例子来说明这一点:
首先,我们定义模型的域:(我添加了模型的一些子类型以使示例更清楚)
trait User
trait Model
trait TestModel extends Model
trait ValidatedModel extends Model
trait OpResult
// Some external resource provider
trait Ums {
def createUser: User
def deleteUser(user: User)
}
然后我们创建一个 class 来保存我们的特定上下文。
class Context {
private val ums = new Ums{
def createUser = new User{}
def deleteUser(user: User) = ???
}
def withUserDo[T](ops: User => T):T = {
val user = ums.createUser
val result = ops(user)
ums.deleteUser(user)
result
}
}
伴随对象提供对托管资源的(一些)操作。用户也可以提供自己的功能。
object Context {
def prepareModel(model: TestModel): User => Model = ???
val validateModel: Model => ValidatedModel = ???
val commitModel: ValidatedModel => OpResult = ???
}
我们可以使用 classic 声明实例化我们的上下文并在其上声明操作,例如:
val ctx = new Context
val testModel = new TestModel{}
val result = ctx.withUserDo{ user =>
val preparedModel = prepareModel(testModel)(user)
val validatedModel = validateModel(preparedModel)
commitModel(validatedModel)
}
或者,鉴于问题中希望使用功能组合,我们可以将其重写为:
val result = ctx.withUserDo{
prepareModel(testModel) andThen validateModel andThen commitModel
}
这是一个更高级的选择:
当您听到 "context" 和 "steps" 时,您会直接想到一种功能模式:Monad。汇总你自己的 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 }
}
然后我们有一个 CleanableContext
,它能够 "cleaning up after itself" 提供一些 'cleanup' 功能:
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
}
现在,我们有一个能够生成可清理的对象 UserContext
,它将负责管理用户的创建和销毁。
object UserContext {
def apply(x:UserManager): CleanableContext[User] = new CleanableContext[User] {
val value = x.createUser
def cleanup = x.deleteUser(value)
}
}
假设我们还定义了模型和业务功能:
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 {}
}
用法
有了可重复使用的机器,我们的用户可以用简洁的方式表达我们的过程:
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
过程的结果仍然是封装的,可以用value
方法从Context
中取出"out":
val result = ctxResult.value
请注意,我们需要将业务操作封装到一个 Context
中,以便在这个 单子组合 中使用。另请注意,我们不需要手动创建或清理用于操作的用户。这是为我们照顾的。
此外,如果我们需要一种以上的托管资源,可以使用此方法通过将不同的上下文组合在一起来管理其他资源。
至此,我只是想提供另一个角度来看待这个问题。管道更复杂,但它为用户通过组合创建安全流程奠定了坚实的基础。