【问题标题】:How to write this three-liner as a one-liner?怎么把这三行写成单行?
【发布时间】:2012-07-09 08:53:40
【问题描述】:

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

【问题讨论】:

  • 如果'a'上的操作总是一样的,为什么不把它们包装成一个函数呢?
  • 除了 Vinc 的评论:单行代码不能很好地处理命令式代码
  • 你的介绍性例子是错误的:你不能做List(1, 2, 3).foreach(..).map(..),因为foreach返回Unit。另外我想补充一下 ziggystar 的评论:在编写命令式代码的同时寻找单行代码是错误的,而且会让你一事无成,因为它完全违反了命令式编程范式的本质。
  • 您需要一些库来扩展功能组合。就像 Python 的 PEAK 库一样。我已经搜索了一个,但没有找到。也许你会很幸运
  • 这个问题的厚颜无耻的答案是只使用分号将其塞进一行:def foo(a : A): Int = { val result = a.calculateImportantThings; a.cleanup; result }。我的意思是,这就是分号的用途,对吧?通过对副作用进行排序来组合副作用。

标签: scala


【解决方案1】:

这里显然有副作用(否则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 规范保证它的参数将从左到右进行计算,这正是您想要的。

【讨论】:

  • 这确实是一个班轮,而不是其他人建议的解决方法!
  • 我认为这是“相当神秘的代码”;依赖于 scala 规范中一些相当不重要(因此未知)的部分。
  • @ziggystar 你认为评估顺序是 Scala 规范中不重要的部分?!?
  • @ziggystar 单行将是def foo(a:A):Int = (a.calculateImportantThings, a.cleanup)._1。我根本不认为这很神秘:读者可以清楚地看到,由于 ._1 被立即调用,首先将两个值放在一个元组中的唯一原因是执行这两种效果。
  • @MilesSabin 在知道之前我写的所有代码都不依赖于评估顺序,而且我不打算在不久的将来编写这样的代码。所以对我来说,这个细节并不重要。事实上,我在写“不重要”之前停顿了一下,因为我的意思是对大多数编写的代码来说都不重要。
【解决方案2】:

这是try/finally 的完美用例:

try a.calculateImportantThings finally a.cleanUp

这是可行的,因为 try/catch/finally 是 scala 中的一个 表达式,这意味着它会返回一个值,而且更好的是,无论计算是否引发异常,您都可以获得清理。

例子:

scala> val x = try 42 finally println("complete")
complete
x: Int = 42

【讨论】:

  • 令人着迷!我至少不会认为这是一种滥用,特别是因为即使a.calculateImportantThings 抛出异常,您也可能想要a.cleanup。如果有的话,我想说这个用例正是 reason try/catch/finally 是 Scala 中的一个表达式。
  • Dan,你是对的,经过反思,这是 try/finally 的 完美 用例!措辞已调整。
【解决方案3】:

事实上,有一个 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 容器相互转换的魔法,所以你可以写:

def foo(a: A): Int = Identity(a.calculateImportantThings) <* a.cleanup()

确实需要向编译器提示以某种方式最左边的东西在 Identity monad 中。以上是我能想到的最短方法。另一种可能是使用Identity() *&gt; foo &lt;* 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

【讨论】:

  • 让我借此机会说我喜欢你的 Haskell+Scala 答案。
【解决方案4】:

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

Kxy = x

所以你用你想要返回的值和你想要执行的一些副作用来调用它。

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

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

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

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

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

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

f: A => Unit

有人可以使用:

f: () => Unit

【讨论】:

  • 好吧,它可以在这里使用,但它不是红隼组合器的最佳应用,因为它被设计为返回一个执行副作用的值,而它需要返回一个一些副作用的结果。
【解决方案5】:
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 项目以获取基于类型类的解决方案。

【讨论】:

  • 他要求的是单衬管,而不是自动清洁解决方案。
  • 他需要一个自动清理解决方案。
  • 他在哪里要求的?在示例中。这是什么意思?那不是cleanUp 可能是anyOtherMethod
【解决方案6】:

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

【讨论】:

    猜你喜欢
    • 2018-05-04
    • 1970-01-01
    • 1970-01-01
    • 2010-11-02
    • 2018-01-27
    • 1970-01-01
    • 2015-01-17
    • 1970-01-01
    • 2021-07-15
    相关资源
    最近更新 更多