【问题标题】:Is there an analog in Scala for the Rails "returning" method?Rails“返回”方法在Scala中是否有类似物?
【发布时间】:2011-04-29 03:46:27
【问题描述】:

在 Rails 中,可以使用:

returning Person.create do |p|
  p.first_name = "Collin"
  p.last_name = "VanDyck"
end

避免这样做:

person = Person.create
person.first_name = "Collin"
person.last_name = "VanDyck"
person

我认为前一种方式更简洁,重复性更少。我发现自己在我的 Scala 项目中创建了这个方法:

def returning[T](value: T)(fn: (T) => Unit) : T = {
  fn(value)
  value
}

我知道它的实用性有限,因为对象倾向于不可变,但例如使用 Lift,在 Mapper 类上使用此方法效果很好。

是否有我不知道的用于“返回”的 Scala 模拟?或者,在 Scala 中是否有类似的方法可以更惯用?

【问题讨论】:

    标签: ruby-on-rails scala scala-2.8


    【解决方案1】:

    您的方法看起来不错,尽管我通常通过添加副作用方法来做到这一点,其中可能包括更改内部状态(或类似println):

    class SideEffector[A](a: A) {
      def effect(f: (A => Any)*) = { f.foreach(_(a)); a }
    }
    implicit def can_have_side_effects[A](a: A) = new SideEffector(a)
    
    scala> Array(1,2,3).effect(_(2) = 5 , _(0) = -1)
    res0: Array[Int] = Array(-1, 2, 5)
    

    编辑:以防万一不清楚这在原始示例中有何用处:

    Person.create.effect(
      _.first_name = "Collin",
      _.last_name = "VanDyck"
    )
    

    编辑:将方法名称更改为“效果”。我不知道为什么我以前不这样做--side effect,而不是命名的side效果。

    【讨论】:

    • 我不得不读了几遍才能完全理解它。很好的解决方案,我一定会使用它,谢谢。
    • 我主要是这样使用它:list.map(_._2 < 7).effect(println).filter(_._3 == "yes")。能够设置字段或进行其他更新只是一个快乐的意外。 (嗯,不完全是意外,但这不是我的正常用例。)
    • 我认为 Ruby 称它为 .tap - 不得不说我更喜欢你的名字!
    【解决方案2】:

    对你已经写的内容并没有太大的改进。正如您非常正确地指出的那样,惯用的 Scala 倾向于支持不可变对象,因此这种东西的用途有限。

    另外,作为一个单行者,如果你需要的话,自己实现真的没有那么痛苦!

    def returning[T](value: T)(fn: T => Unit) : T = { fn(value); value }
    

    【讨论】:

    • 感谢您的回复。我认为这可能是这种情况,但是在学习了如何将 Option/Box 与 map/flatMap/foreach 一起使用之后,我一直渴望找到更多 Scala 方式来解决问题。
    【解决方案3】:

    我愿意:

    scala> case class Person(var first_name: String = "", var last_name: String = "")
    defined class Person
    
    scala> Person(first_name="Collin", last_name="VanDyck")
    res1: Person = Person(Collin,VanDyck)
    

    【讨论】:

    • 感谢 pedrofula 的回复,但这实际上更多的是返回任意值的最佳方式,而是允许通过更高级别的函数对其进行一些操作而不会污染具有另一个变量引用的外部范围。
    • 我不明白你的意思。 Person(first_name="Collin", last_name="VanDyck") 如何污染外部范围?
    • 顺便说一句,这种方法不需要 - 但允许 - vars 而不是 vals。
    【解决方案4】:

    我不明白为什么Vasil 删除了他自己的答案,但我非常喜欢它(这正是我要建议的):

    val person = Person.create
    locally { import person._
      first_name = "Collin"
      last_name = "VanDyck"
    }
    person
    

    人们一直要求的功能之一是能够自动导入某些内容。如果可能,那么您可以这样做:

    def returning[T](import value: T)(fn: => Unit) : T = { fn; value }
    
    returning(Person.create) {
      first_name = "Collin"
      last_name = "VanDyck"
    }
    

    目前这是不可能的,在 Scala 的路线图中也不可能。但是有些人确实会时不时地要求类似的东西。

    【讨论】:

    • 本地不知道,谢谢。你给出的第一个例子是我试图避免的(val person..; ...; person)。我认为我一直在寻找这个的原因是因为我从 Ruby 来到 Scala,在那里我们经常接触到 Object#instance_eval :)
    【解决方案5】:

    可以避免像这样重复变量名:

    val person = Person.create
    locally { import person._
      first_name = "Collin"
      last_name = "VanDyck"
    }
    

    请注意,这只适用于vals。此外,locally 是一种 Predef 方法,它有助于创建块以限制变量范围,而不会违反 Scala 的分号推断。一旦你完成了这个人的初始化,这可以防止导入妨碍你。

    【讨论】:

      【解决方案6】:

      另一个建议是使用来自Scalazforward pipe operator

      val person = Person.create |> { p =>
        p.firstName = "Collin"
        p.lastName = "VanDyck"
        p // or p.saveMe
      }
      

      不同之处在于,如果要分配值,则必须自己返回值。如果您不需要返回值(如您最初的示例),事情就简单了:

      Person.create |> { p =>
        p.firstName = "Collin"
        p.lastName = "VanDyck"
        p.save
      }
      

      你去吧。

      我不愿意在我自己的代码中真正使用它(尽管我有点喜欢这种方式 - 但它只记录在 scalaz 中,可能很难让其他查看代码的人弄清楚),所以我希望这些示例确实有效。

      您当然可以使用|> 定义自己的“前进和返回管道”。

      class ReturningPipe[A](value: A) {
        import Scalaz._
        def |>>[B](f: A => B):A = value.|>(a => { f(a); value})
      }
      implicit def returningPipe[A](value: A) = new ReturningPipe(value)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-12-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-16
        • 1970-01-01
        • 2013-02-12
        相关资源
        最近更新 更多