我喜欢这种方式,你可以在Scala中编写单行方法,例如:与List(1, 2, 3).foreach(..).map(..)
。
但是有一种情况,有时在编写Scala代码时出现,事情变得有点难看。例如:
def foo(a: A): Int = {
// do something with 'a' which results in an integer
// e.g. 'val result = a.calculateImportantThings
// clean up object 'a'
// e.g. 'a.cleanUp'
// Return the result of the previous calculation
return result
}
在这种情况下,我们必须返回一个结果,但是在计算结束后不能直接返回它,因为我们必须在返回之前进行一些清理。
我总是要写一个三线。是否有可能写一个单行来执行此操作(不更改A
的类,因为这可能是一个无法更改的外部库)?
答案 0 :(得分:9)
这里涉及明显的副作用(否则调用calculateImportantThings
和cleanUp
的顺序无关紧要)所以建议您重新考虑您的设计。
但是,如果这不是一个选项,你可以试试像,
scala> class A { def cleanUp {} ; def calculateImportantThings = 23 }
defined class A
scala> val a = new A
a: A = A@927eadd
scala> (a.calculateImportantThings, a.cleanUp)._1
res2: Int = 23
元组值(a, b)
等同于应用程序Tuple2(a, b)
,Scala规范保证其参数将从左到右进行计算,这就是你想要的。
答案 1 :(得分:7)
这是try
/ finally
try a.calculateImportantThings finally a.cleanUp
这是因为try / catch / finally是scala中的表达式,这意味着它返回一个值,更好的是,无论计算是否抛出异常,都可以进行清理。
示例:
scala> val x = try 42 finally println("complete")
complete
x: Int = 42
答案 2 :(得分:5)
也许你想使用红隼组合器?它的定义如下:
Kxy = x
所以你用你想要返回的值和你想要执行的一些副作用操作来调用它。
您可以按如下方式实施:
def kestrel[A](x: A)(f: A => Unit): A = { f(x); x }
...并以这种方式使用它:
kestrel(result)(result => a.cleanUp)
可在此处找到更多信息:debasish gosh blog。
[更新] 正如雅罗斯拉夫正确指出的那样,这不是红隼组合器的最佳应用。但是使用没有参数的函数定义类似的组合器应该没有问题,所以相反:
f: A => Unit
有人可以使用:
f: () => Unit
答案 3 :(得分:5)
(<*) :: Applicative f => f a -> f b -> f a
例如:
ghci> getLine <* putStrLn "Thanks for the input!"
asdf
Thanks for the input!
"asdf"
然后剩下的就是发现the same operator in scalaz,因为scalaz通常会复制Haskell所拥有的一切。您可以在Identity
中包装值,因为Scala没有IO
来对效果进行分类。结果看起来像这样:
import scalaz._
import Scalaz._
def foo(a: A): Int =
(a.calculateImportantThings.pure[Identity] <* a.cleanup.pure[Identity]).value
但这是相当令人讨厌的,因为我们必须在Identity中明确地包含副作用计算。事实上,scalaz做了一些隐式转换为Identity容器和从Identity容器转换的魔法,所以你可以写:
def foo(a: A): Int = Identity(a.calculateImportantThings) <* a.cleanup()
你做需要提示编译器以某种方式最左边的东西在Identity monad中。以上是我能想到的最短路。另一种可能性是使用Identity() *> foo <* bar
,它会按顺序调用foo
和bar
的效果,然后生成foo
的值。
返回ghci示例:
scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._
scala> val x : String = Identity(readLine) <* println("Thanks for the input!")
<< input asdf and press enter >>
Thanks for the input!
x: String = asdf
答案 4 :(得分:2)
class Test {
def cleanUp() {}
def getResult = 1
}
def autoCleanup[A <: Test, T](a: A)(x: => T) = {
try { x } finally { a.cleanUp }
}
def foo[A <: Test](a:A): Int = autoCleanup(a) { a.getResult }
foo(new Test)
您可以查看基于类型类的解决方案的scala-arm项目。
答案 5 :(得分:0)
从Scala 2.13
开始,链接操作tap
可用于在返回任何值的同时对任何值施加副作用(在这种情况下为清除A)原始值保持不变:
def tap [U](f:(A)=> U):A
import util.chaining._
// class A { def cleanUp { println("clean up") } ; def calculateImportantThings = 23 }
// val a = new A
val x = a.calculateImportantThings.tap(_ => a.cleanUp)
// clean up
// x: Int = 23
在这种情况下,tap
有点滥用,因为我们甚至不使用它施加的值(a.calculateImportantThings
(23
)来产生副作用({{1 }}。