Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

scala

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) ?

like image 437
John Threepwood Avatar asked Jul 09 '12 08:07

John Threepwood


3 Answers

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.

like image 55
Miles Sabin Avatar answered Sep 18 '22 13:09

Miles Sabin


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
like image 28
Luigi Plinge Avatar answered Sep 19 '22 13:09

Luigi Plinge


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
like image 26
Dan Burton Avatar answered Sep 20 '22 13:09

Dan Burton