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

Joh*_*ood 8 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
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们必须返回一个结果,但是在计算完成后不能直接返回它,因为我们必须在返回之前做一些清理.

我总是要写一个三班轮.是否有可能编写一个单行代码来执行此操作(不更改类A,因为这可能是一个无法更改的外部库)?

Mil*_*bin 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
Run Code Online (Sandbox Code Playgroud)

元组值(a, b)等同于应用程序Tuple2(a, b),Scala规范保证其参数将从左到右进行计算,这就是您想要的.

  • 我把它投票为"相当神秘的代码"; 依赖于scala规范中一些非常不重要(因而未知)的部分. (2认同)
  • @ziggystar单行将是`def foo(a:A):Int =(a.calculateImportantThings,a.cleanup)._ 1`.我根本不认为这个含糊不清:读者可以清楚地看到,因为._1是立即调用的,所以将这两个值放在元组中的唯一原因就是执行这两种效果. (2认同)
  • @MilesSabin我写的所有代码,直到知道不依赖于评估顺序,我不打算编写在不久的将来做的代码.所以对我来说,这个细节并不重要.事实上,在写"不重要"之前我暂停了一下,因为我的意思是对大多数编写的代码不重要. (2认同)

Lui*_*hys 7

这是一个完美的用例为try/ finally:

try a.calculateImportantThings finally a.cleanUp
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为try/catch/finally是scala中的表达式,意味着它返回一个值,更好的是,无论计算是否抛出异常,都可以进行清理.

例:

scala> val x = try 42 finally println("complete")
complete
x: Int = 42
Run Code Online (Sandbox Code Playgroud)


Chr*_*ian 5

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

Kxy = x

因此,您可以使用要返回的值以及要执行的一些副作用操作来调用它.

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

def kestrel[A](x: A)(f: A => Unit): A = { f(x); x }
Run Code Online (Sandbox Code Playgroud)

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

kestrel(result)(result => a.cleanUp)
Run Code Online (Sandbox Code Playgroud)

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

[更新]正如雅罗斯拉夫正确指出的那样,这不是红隼组合器的最佳应用.但是使用没有参数的函数定义类似的组合器应该没有问题,所以相反:

f: A => Unit
Run Code Online (Sandbox Code Playgroud)

有人可以使用:

f: () => Unit
Run Code Online (Sandbox Code Playgroud)


Dan*_*ton 5

事实上,在这样的场合,有一个Haskell运算符:

(<*) :: Applicative f => f a -> f b -> f a
Run Code Online (Sandbox Code Playgroud)

例如:

ghci> getLine <* putStrLn "Thanks for the input!"
asdf
Thanks for the input!
"asdf"
Run Code Online (Sandbox Code Playgroud)

剩下的就是在scalaz中发现相同的运算符,因为scalaz通常会复制Haskell所拥有的所有内容.您可以包装值Identity,因为Scala不必IO对效果进行分类.结果看起来像这样:

import scalaz._
import Scalaz._

def foo(a: A): Int = 
  (a.calculateImportantThings.pure[Identity] <* a.cleanup.pure[Identity]).value
Run Code Online (Sandbox Code Playgroud)

然而,这是相当令人讨厌的,因为我们必须明确地将副作用计算包装在Identity中.事实上,scalaz做了一些隐式转换为Identity容器的魔法,所以你可以写:

def foo(a: A): Int = Identity(a.calculateImportantThings) <* a.cleanup()
Run Code Online (Sandbox Code Playgroud)

需要提示编译器不知何故最左边的是在身份单子.以上是我能想到的最短路.另一种可能性是使用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
Run Code Online (Sandbox Code Playgroud)

  • 让我借此机会说我喜欢你的Haskell + Scala答案. (3认同)