John Threepwood
John Threepwood

Reputation: 16143

How to write this three-liner as a one-liner?

I like the way, you can write one-liner-methods in Scala, e.g. with List(1, 2, 3).foreach(..).map(..).

But there is a certain situation, that sometimes comes up when writing Scala code, where things get a bit ugly. Example:

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
}

In this situation we have to return a result, but can not return it directly after the calculation is done, because we have to do some clean up before returning.

I always have to write a three-liner. Is there also a possibility to write a one-liner to do this (without changing the class of A, because this may be a external library which can not be changed) ?

Upvotes: 8

Views: 596

Answers (6)

Xavier Guihot
Xavier Guihot

Reputation: 61666

Starting Scala 2.13, the chaining operation tap can be used to apply a side effect (in this case the cleanup of A) on any value while returning the original value untouched:

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

In this case tap is a bit abused since we don't even use the value it's applied on (a.calculateImportantThings (23)) to perform the side effect (a.cleanUp).

Upvotes: 0

Luigi Plinge
Luigi Plinge

Reputation: 51109

This is a perfect use-case for try/finally:

try a.calculateImportantThings finally a.cleanUp

This works because try/catch/finally is an expression in scala, meaning it returns a value, and even better, you get the cleanup whether or not the calculation throws an exception.

Example:

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

Upvotes: 7

Christian
Christian

Reputation: 4593

Maybe you want to use a kestrel combinator? It is defined as follows:

Kxy = x

So you call it with the value you want to return and some side-effecting operation you want to execute.

You could implement it as follows:

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

... and use it in this way:

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

More information can be found here: debasish gosh blog.

[UPDATE] As Yaroslav correctly points out, this is not the best application of the kestrel combinator. But it should be no problem to define a similar combinator using a function without arguments, so instead:

f: A => Unit

someone could use:

f: () => Unit

Upvotes: 5

Dan Burton
Dan Burton

Reputation: 53675

There is, in fact, a Haskell operator for just such an occasion:

(<*) :: Applicative f => f a -> f b -> f a

For example:

ghci> getLine <* putStrLn "Thanks for the input!"
asdf
Thanks for the input!
"asdf"

All that remains then is to discover the same operator in scalaz, since scalaz usually replicates everything that Haskell has. You can wrap values in Identity, since Scala doesn't have IO to classify effects. The result would look something like this:

import scalaz._
import Scalaz._

def foo(a: A): Int = 
  (a.calculateImportantThings.pure[Identity] <* a.cleanup.pure[Identity]).value

This is rather obnoxious, though, since we have to explicitly wrap the side-effecting computations in Identity. Well the truth is, scalaz does some magic that implicitly converts to and from the Identity container, so you can just write:

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

You do need to hint to the compiler somehow that the leftmost thing is in the Identity monad. The above was the shortest way I could think of. Another possibility is to use Identity() *> foo <* bar, which will invoke the effects of foo and bar in that order, and then produce the value of foo.

To return to the ghci example:

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

Upvotes: 5

xiefei
xiefei

Reputation: 6599

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)

You can take a look at scala-arm project for type class based solution.

Upvotes: 2

Miles Sabin
Miles Sabin

Reputation: 23046

There are clearly side-effects involved here (otherwise the order of invocation of calculateImportantThings and cleanUp wouldn't matter) so you would be well advised to reconsider your design.

However, if that's not an option you could try something like,

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

The tuple value (a, b) is equivalent to the application Tuple2(a, b) and the Scala specification guarantees that its arguments will be evaluated left to right, which is what you want here.

Upvotes: 9

Related Questions