Is there an analog in Scala for the Rails "returning" method?
Asked Answered
T

6

5

In Rails, one could use:

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

Avoiding having to do this:

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

I think the former way is cleaner and less repetitive. I find myself creating this method in my Scala projects:

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

I know that it is of somewhat limited utility due to the tendency of objects to be immutable, but for example working with Lift, using this method on Mapper classes works quite well.

Is there a Scala analog for "returning" that I'm not aware of? Or, is there a similar way to do this in Scala that's more idiomatic?

Touching answered 22/10, 2010 at 12:51 Comment(0)
P
5

Can't really improve much on what you've already written. As you quite correctly pointed out, idiomatic Scala tends to favour immutable objects, so this kind of thing is of limited use.

Plus, as a one-liner it's really not that painful to implement yourself if you need it!

def returning[T](value: T)(fn: T => Unit) : T = { fn(value); value }
Prisca answered 22/10, 2010 at 13:32 Comment(1)
Thanks for the reply. I figured this was probably the case but after learning how to use Option/Box with map/flatMap/foreach I've been eager to find the more Scala-way to approach problems.Touching
D
6

Your method looks fine, though I normally do this by adding a method for side-effects, which can include changing internal state (or also stuff like 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)

Edit: just in case it's not clear how this would be useful in the original example:

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

Edit: changed the name of the method to "effect". I don't know why I didn't go that way before--side effect, not side effect for the naming.

Disadvantage answered 22/10, 2010 at 16:1 Comment(3)
I had to read this one a couple of times to fully grok it. Great solution, I'll definitely be using it, thanks.Touching
I mostly use it like so: list.map(_._2 < 7).effect(println).filter(_._3 == "yes"). Being able to set fields or do other updates is just a happy accident. (Well, not entirely an accident, but that's not my normal use case.)Disadvantage
I think Ruby calls this .tap - gotta say I like your name better!Stalemate
P
5

Can't really improve much on what you've already written. As you quite correctly pointed out, idiomatic Scala tends to favour immutable objects, so this kind of thing is of limited use.

Plus, as a one-liner it's really not that painful to implement yourself if you need it!

def returning[T](value: T)(fn: T => Unit) : T = { fn(value); value }
Prisca answered 22/10, 2010 at 13:32 Comment(1)
Thanks for the reply. I figured this was probably the case but after learning how to use Option/Box with map/flatMap/foreach I've been eager to find the more Scala-way to approach problems.Touching
G
3

I would do:

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)
Greenlee answered 22/10, 2010 at 13:31 Comment(3)
Thanks for the reply pedrofula, but this is really more of a matter of the best way to return an arbitrary value, but to allow some operations to be done on it via a higher level function without polluting the outer scope with another variable reference.Touching
I fail to see your point. How can a Person(first_name="Collin", last_name="VanDyck") pollute de outer scope?Greenlee
BTW this approach doesn't require - but allows - vars instead of vals.Greenlee
T
2

I don't understand why Vasil deleted his own answer, but I like it a lot (it was precisely what I was going to suggest):

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

One of the features people have been asking for is the ability to auto-import something. If it were possible, then you could do this:

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

returning(Person.create) {
  first_name = "Collin"
  last_name = "VanDyck"
}

That is not possible at the moment, nor is it in Scala's roadmap. But some people do ask for something like that now and again.

Thier answered 22/10, 2010 at 17:34 Comment(1)
Did not know about locally, thanks. The first example you gave is what I'm trying to avoid (val person..; ...; person). I think the reason that I've been searching for this is because I came to Scala from Ruby, where we get to Object#instance_eval quite a lot :)Touching
M
1

Another suggestion would be using the forward pipe operator from Scalaz.

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

The difference is that you would have to return the value yourself, if you want to assign it. If you do not need the return value (as in your initial example), things are easier:

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

And there you go.

I was reluctant to really use it in my own code (even though I kind of favour this way of doing it – but it is only documented in scalaz and maybe hard to figure out for other people looking at the code), so I hope these examples do work.

You could of course define your own ‘forward and returning pipe’ using |>.

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)
Markman answered 22/10, 2010 at 17:34 Comment(0)
O
1

It is possible to avoid repeating the variable name like so:

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

Note that this only works for vals. Also, locally is a Predef method that helps to create blocks just to limit variable scope, without running afoul of Scala's semicolon inference. This keeps the import from getting in your way once you have finished initializing the person.

Outpatient answered 22/10, 2010 at 17:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.