【问题标题】:.with alternative in scala.with scala 中的替代方案
【发布时间】:2011-12-16 17:37:33
【问题描述】:

我来自 Groovy,它在每个接受单参数闭包的类型上都有一个 .with 方法;参数是调用.with 方法的对象。这允许使用一种非常酷的技术来扩展函数链接功能,它使您无需引入临时变量、考虑代码、使其更易于阅读并进行其他细节处理。

我希望能够做这样的事情:

Seq(1, 2, 3, 4, 5)
  .filter(_ % 2 == 0)
  .with(it => if (!it.isEmpty) println(it))

代替

val yetAnotherMeaninglessNameForTemporaryVariable = 
  Seq(1, 2, 3, 4, 5).filter(_ % 2 == 0)
if (!yetAnotherMeaninglessNameForTemporaryVariable.isEmpty) 
  println(yetAnotherMeaninglessNameForTemporaryVariable)

换句话说,在第一个示例中,.with 有点类似于.foreach,但不是遍历对象的项目,而是在对象本身上调用一次。所以it 等于Seq(1, 2, 3, 4, 5).filter(_ % 2 == 0)

因为我很惊讶在 Scala 中没有找到类似的东西,所以我的问题是:

  • 我错过了什么吗?
  • Scala 是否有任何替代技术?
  • 如果没有,是否有任何正当理由说明该功能未在 Scala 中实现?

更新: Scala 问题跟踪器上发布了适当的功能请求:https://issues.scala-lang.org/browse/SI-5324。请投票并宣传

【问题讨论】:

  • 请注意:with 是 Scala 中的保留字,因此不能将方法命名为相同的东西。它仍然应该以其他名称存在;据我所知,这是 StackOverflow 上最常见的 Scala 问题和答案(“它不存在;像这样制作你自己的”)!
  • 我认为名称convert 最合适,因此表明此方法应该没有副作用,并且由于它将调用者作为参数并返回一些新内容,因此它必须是某种转换。从这个意义上说,这个函数在标准库中是不可替代的。同样正如stackoverflow.com/a/8538277/485115 中所建议的,还应该有一个名为tap 的副作用变体,它返回调用者对象。

标签: scala


【解决方案1】:

标准库中不存在这样的方法,但定义自己的方法并不难。

implicit def aW[A](a: A) = new AW(a)
class AW[A](a: A) {
  def tap[U](f: A => U): A = {
    f(a)
    a
  }
}

val seq = Seq(2, 3, 11).
          map(_ * 3).tap(x => println("After mapping: " + x)).
          filter(_ % 2 != 0).tap(x => println("After filtering: " + x))

编辑:(回应评论)

哦,我误会了。您需要的是 Scalaz 库中的内容。它的名称为|>(称为管道运算符)。这样,您的示例将如下所示:

Seq(1, 2, 3, 4, 5).filter(_ % 2 == 0) |> { it => if(!it.isEmpty) println(it) }

如果你不能使用Scalaz,你可以自己定义操作符:

implicit def aW[A](a: A) = new AW(a)
class AW[A](a: A) {
  def |>[B](f: A => B): B = f(a)
}

在现有类型上添加有用的方法并不是一个坏习惯。您应该谨慎使用隐式转换,但我认为这两个组合符很常见,以至于他们的皮条客是有道理的。

【讨论】:

  • 是的,我知道我可以使用隐式扩展标准库,但我只是发现远离标准是一种不好的做法。这就是为什么我的问题是为什么 Scala 的人不实现这个,因为这可能应该发布在他们的问题跟踪器上。除了你的tap 函数虽然非常有用,但它与with 有点不同,因为它返回对象本身而不是闭包的结果。在标准库 IMO 中同时拥有 .with.tap 会非常酷。
  • @NikitaVolkov Odersky 批评了一些 Scala 程序员不惜一切代价避免中间 val 声明的习惯,|> 在被要求使用管道运算符时肯定会有所贡献。所以,就目前而言,我不相信它会成为标准库的一部分。
  • @NikitaVolkov:Here。整个线程很有趣。
  • @NikitaVolkov:我同意你的观点,Martin 在该线程中提供的论点并不令人信服。我个人觉得|> 非常有用,而且我经常使用它。关于该操作的口头名称,pipepipeTo 怎么样?这就是 F# 的阅读方式(大量使用它的语言),而且它本质上与 UNIX 管道相同。
  • 发布了功能请求:issues.scala-lang.org/browse/SI-5324。请投票并宣传
【解决方案2】:

Scala 中包含这种模式的一些语法:

Seq(1, 2, 3, 4, 5).filter(_ % 2 == 0) match { case it => if (!it.isEmpty) println(it) }

但是,这不是公认的习语,因此您应该避免(ab)使用它。

如果您不喜欢为虚拟变量发明大量名称,请记住您可以使用范围大括号:

val importantResult = {
  val it = Seq(1,2,3).filter(_ % 2 == 0)
  if (!it.isEmpty) println(it)
  it
}

val otherImportantResultWithASpeakingVariableName = {
  val it = // ...
  /* ... */
  it
}

【讨论】:

  • 对比赛的好建议!虽然有点粗糙,但它确实回答了我的前 2 个问题。
【解决方案3】:

这样试试。

println(Seq(1, 2, 3, 4, 5).filter(_ % 2 == 0).ensuring(!_.isEmpty))

如果条件不满足,则抛出断言异常。

【讨论】:

  • +1,尽管它仅适用于单语句执行(例如此处引用的 println)。如果你想做不止一项操作,那就很棘手了。
  • 其他用途的好技术。对于这个抛出异常是矫枉过正
【解决方案4】:

还记得叫名字吗?也许它会给你你想要的能力:

object Test {
 def main(args: Array[String]) {
   delayed(time());
 }

 def time() = {
    println("Getting time in nano seconds")
    System.nanoTime
 }

 def delayed( t: => Long ) = {
   println("In delayed method")
   println("Param: " + t)
   t
 }
}

http://www.tutorialspoint.com/scala/functions_call_by_name.htm中所述

【讨论】:

    【解决方案5】:

    虽然我更喜欢其他解决方案(因为它们更本地化,因此更容易遵循),但不要忘记你可以

    { val x = Seq(1,2,3,4,5).filter(_ % 2 == 0); println(x); x }
    

    避免无意义变量的名称冲突,并将它们限制在适当的范围内。

    【讨论】:

      【解决方案6】:

      这只是函数应用程序 f(x) 颠倒过来:x.with(f)...如果您正在寻找一种在 Scala 中执行 with 的惯用方式,请取消翻转它:

      (it => if (!it.isEmpty) println(it)) (Seq(1, 2, 3, 4, 5).filter(_ % 2 == 0))
      

      同样,如果你想要x.with(f).with(g),只需使用g(f(x))...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-27
        • 1970-01-01
        • 2015-09-06
        • 2016-12-27
        • 2010-11-22
        • 2021-12-22
        • 2014-05-08
        • 1970-01-01
        相关资源
        最近更新 更多