8

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

4

6 回答 6

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 规范保证它的参数将从左到右进行评估,这就是你想要的。

于 2012-07-09T09:21:41.517 回答
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
于 2012-07-09T15:26:14.210 回答
5

也许您想使用红隼组合器?定义如下:

Kxy = x

因此,您使用要返回的值和要执行的一些副作用操作来调用它。

您可以按如下方式实现它:

def kestrel[A](x: A)(f: A => Unit): A = { f(x); x }

...并以这种方式使用它:

kestrel(result)(result => a.cleanUp)

更多信息可以在这里找到:debasish gosh 博客

[更新]正如 Yaroslav 正确指出的那样,这不是 kestrel 组合器的最佳应用。但是使用不带参数的函数定义类似的组合器应该没有问题,因此:

f: A => Unit

有人可以使用:

f: () => Unit
于 2012-07-09T09:16:11.070 回答
5

事实上,有一个 Haskell 运算符可以用于这种场合:

(<*) :: Applicative f => f a -> f b -> f a

例如:

ghci> getLine <* putStrLn "Thanks for the input!"
asdf
Thanks for the input!
"asdf"

剩下的就是在 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
于 2012-07-09T11:54:23.960 回答
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项目。

于 2012-07-09T09:19:05.367 回答
0

开始Scala 2.13,链接操作tap可用于对任何值应用副作用(在本例中为清除 A),同时返回原始值 untouched

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)) 的值来执行副作用 ( a.cleanUp)。

于 2019-03-17T16:32:59.740 回答