我正在寻找这样一种类型,它允许我表示一个上下文,其中运行一段代码。例如:
def withinContext[R]: ((=> R) => R) =
(inner) => {
initializeSomeResource()
try {
inner
} finally {
releaseTheResource()
}
}
然后我可以简单地用作
withinContext {
...
}
或者,如果内部代码块需要上下文中的一些信息,则将其概括为
def withinContext[R]: ((Ctx => R) => R) = ...
它们的用例大致对应于Haskell的bracket_
和bracket
。
我可以直接使用类型(=> R) => R
和(A => R) => R
,但我没有用于组合此类上下文包装器的实用函数,所以我想知道,Scala生态系统中是否已经存在类似的功能
我所知道的最后一件事是scala.util.control.Exception.Catch
,它为构建和组合Catch
实例提供了很好的功能,但似乎没有办法在执行内部块之前运行任何初始化。此外(这对我的用例来说并不那么重要),它不允许为内部计算提供参数,就像(A => R) => R
的情况一样。
类型(A => R) => R
是延续monad,对应于Haskell的ContT r IO a
,但我在任何标准Scala库中都找不到延续monad的实现(也许它隐藏在Scalaz深处,我错过了它)。
为了与Specs2测试代码一起使用,我经常做这种事情。从本质上讲,我们希望设置一些上下文来围绕代码块。我使用的习语是这样的:
def contextName[TYPE,RSRC](doit: (RSRC) => TYPE) : TYPE = {
val thing : RSRC = acquireResource(args)
try doit(thing)
finally releaseResource(thing)
}
我意识到你试图让一个延续成为一个延续,但我不得不问:为什么?我刚才给出的习语是类型(RSRC=>type)=>type的延续monad,它可以像您在withContext
中建议的那样使用。例如,我写的一个ReactiveMongo上下文对象的真实用例:
def withDatabase[T](dbName: String)(doit: (DefaultDB) => T) : T = {
val db = connection.db(dbName)
try doit(db)
finally db.drop()
}
这会获取一个数据库,将其传递给continuation,然后在完成后丢弃数据库,即使continuation引发异常。它是这样使用的:
val numCollections = withDatabase("foo") { db =>
db.collectionNames.map { list => list.size }
}
它只是使用数据库来获取数据库中(未来)的集合数。
希望这能有所帮助。