如何将这三个班轮写成一个班轮?

时间:2012-07-09 08:53:40

标签: scala code-design

我喜欢这种方式,你可以在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的类,因为这可能是一个无法更改的外部库)?

6 个答案:

答案 0 :(得分:9)

这里涉及明显的副作用(否则调用calculateImportantThingscleanUp的顺序无关紧要)所以建议您重新考虑您的设计。

但是,如果这不是一个选项,你可以试试像,

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)

实际上,有一个Haskell运算符就是这样一个场合:

(<*) :: 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,它会按顺序调用foobar的效果,然后生成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.calculateImportantThings23)来产生副作用({{1 }}。