A more idiomatic (monadic?) Way to express it is Scala

I have several blocks of code that follow this pattern:

// Dummy function defs. def result(i : Int, d : Double, b : Boolean) = { if (b) d else i } def fA(s : String) = {7} def fB(s : String, i : Int) = {1.0} def fC(s : String, i : Int, d : Double) = {true} // Actual code. def test(s : String) : Double = { try { val a = fA(s) try { val b = fB(s, a) try { val c = fC(s, a, b) result(a, b, c) } catch { case _ => result(a, b, false) } } catch { case _ => result(a, 0.0, false) } } catch { case _ => result(0, 0.0, false) } } 

Where a, b and c are in turn calculated by the corresponding functions, and then the values ​​are passed to the result function. If an exception occurs at any stage, the default value is used instead of the rest of the variables.

Is there a more idiomatic way of expressing this code. This reminds me of Monads in that it is a series of chain calculations that help out immediately if any calculations fail.

+6
source share
7 answers

These types of problems are what Try aims to solve in a slightly more monadic way (than nested try/catch blocks).

Try is a calculation that can either throw an exception or return a successfully calculated value. There are two subclasses for them - Success and Failure .

It's very funny that this question appeared when he did it - a few days ago I finished some additions and refactoring before scala.util.Try , for version 2.10, and this SO question helps illustrate an important precedent for the combinator, which we ultimately solved enable; transform .

(Starting from this, transform is currently at night and will be in Scala from 2.10-M5 onward, due today or tomorrow. More details on Try and usage examples can be found in the night docs )

With transform (by embedding them), this can be implemented using Try as follows:

 def test(s: String): Double = { Try(fA(s)).transform( ea => Success(result(0, 0.0, false)), a => Try(fB(s, a)).transform( eb => Success(result(a, 0.0, false)), b => Try(fC(s, a, b)).transform( ec => Success(result(a, b, false)), c => Try(result(a, b, c)) ) ) ).get } 
+5
source

I'm not sure that you can use monads, since at each step you have two alternatives (exception or result) and be true to your source code, in the exception that you do not want to call fB or fC .

I was not able to elegantly remove duplicate default values, so I left it, as I think, more understandable. Here is my non-monodic version based on either.fold and control.Exception :

 def test(s : String) = { import util.control.Exception._ val args = allCatch.either(fA(s)).fold(err => (0, 0.0, false), a => allCatch.either(fB(s, a)).fold(err => (a, 0.0, false), b => allCatch.either(fC(s, a, b)).fold(err => (a, b, false), c => (a, b, c)))) (result _).tupled(args) } 
+5
source

I changed the example of using monads:

 def fA(s: String) = Some(7) def fB(i: Option[Int]) = Some(1.0) def fC(d: Option[Double]) = true // might be false as well def result(i: Int, d: Double, b: Boolean) = { if (b) d else i } def test(s: String) = result(fA(s).getOrElse(0), fB(fA(s)).getOrElse(0.0), fC(fB(fA(s)))) 

Note. Understanding is understood as a chain of flatMap . Thus, the res type is Option[(Int, Double, Boolean)] . Therefore, there is no need to write map or flatMap yourself. The compiler does the work for you. :)

Edit

I edited my code to fit all the features. I will improve it if I find a better way. Thank you for all your comments.

+4
source

Defining these utility functions

 implicit def eitherOps[E, A](v: Either[E, A]) = new { def map[B](f: A => B) = v match { case Left(e) => Left(e) case Right(a) => Right(f(a)) } def flatMap[B](f: A => Either[E, B]) = v match { case Left(e) => Left(e) case Right(a) => f(a) } def or(a: A) = v match { case Left(_) => Right(a) case x => x } } def secure[A, B](f: A => B) = new { def run(a: A): Either[Trowable, B] = try { Right(f(a)) } catch { case e => Left(e) } } 

and simplifying your

 def fA(s : String) = 7 def fB(i : Int) = 1.0 def fC(d : Double) = true 

We'll have:

 def test(s: String): Either[Throwable, Double] = for { a <- secure(fA).run(s).or(0) b <- secure(fB).run(a).or(0.0) c <- secure(fC).run(b).or(false) } yield result(a, b, c) 

Edit

Here is the executable, but unfortunately a more detailed piece of code

 object Example { trait EitherOps[E, A] { def self: Either[E, A] def map[B](f: A => B) = self match { case Left(e) => Left(e) case Right(a) => Right(f(a)) } def flatMap[B](f: A => Either[E, B]) = self match { case Left(e) => Left(e) case Right(a) => f(a) } def or(a: A) = self match { case Left(_) => Right(a) case x => x } } trait SecuredFunction[A, B] { def self: A => B def secured(a: A): Either[Throwable, B] = try { Right(self(a)) } catch { case e => Left(e) } } implicit def eitherOps[E, A](v: Either[E, A]) = new EitherOps[E, A] { def self = v } implicit def opsToEither[E, A](v: EitherOps[E, A]) = v.self implicit def secure[A, B](f: A => B) = new SecuredFunction[A, B]{ def self = f } def fA(s : String) = 7 def fB(i : Int) = 1.0 def fC(d : Double) = true def result(i : Int, d : Double, b : Boolean) = { if (b) d else i } def test(s: String): Either[Throwable, Double] = for { a <- (fA _).secured(s) or 0 b <- (fB _).secured(a) or 0.0 c <- (fC _).secured(b) or false } yield result(a, b, c) } 
+2
source

You can use the catching idiom as follows:

 import scala.util.control.Exception._ def test(s : String) : Double = result( catching(classOf[Exception]).opt( fA(s) ).getOrElse(0), catching(classOf[Exception]).opt( fB(s, a) ).getOrElse(0.0), catching(classOf[Exception]).opt( fC(s, a, b) ).getOrElse(false) ) 

However, like other solutions, this makes a small change in execution in that fB and fC will always be evaluated, while your source code only evaluates them if previous calls succeeded.

+1
source

Tried to make it more functional. Not sure if this solution is clearer than yours, but I think it will be better if you have more calculation steps.

 def result(i : Int, d : Double, b : Boolean) = { if (b) d else i } def fA(s : String) = {7} def fB(s : String, i : Int) = {1.0} def fC(s : String, i : Int, d : Double) = {true} type Data = (Int, Double, Boolean) def test(s : String) : Double = { val steps = Seq[Data => Data]( {case (_, b, c) => (fA(s), b, c)}, {case (a, _, c) => (a, fB(s, a), c)}, {case (a, b, _) => (a, b, fC(s, a, b))} ) val defaults: Either[Data, Data] = Right((0, 0.0, false)) val resultData = steps.foldLeft { defaults } { (eith, func) => eith match { case left: Left[_,_] => left case Right(data) => try { Right(func(data)) } catch { case _ => Left(data) } } } fold (identity, identity) (result _) tupled (resultData) } 
0
source

The previous answers do not seem to coincide with the fact that you want to get the default result at each level. No need to come up with an expression here, you just need a helper function:

 def optTry[T]( f: => T) : Option[T] = try { Some(f) } catch { case e:Exception => None } 

OK, optTry is a bad name (I am not good at this game), but then you can simply:

 def test(s : String) : Double = { val a = optTry(fA(s)) getOrElse 0 val b = optTry(fB(s,a)) getOrElse 0.0 val c = optTry(fC(s,a,b)) getOrElse false result(a,b,c) } 

Note that Scala 2.10 will have a Try data structure that basically does the same with pimped Either instead of Option , see http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs /library/index.html#scala.util.Try

Also note that try { ... } catch { case _ => ... } is a bad idea, you certainly don't want to catch some Exception system like OutOfMemory, etc.

EDIT: see also the Scalaz Validation data structure for a world of fear of all these issues. See: http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html

0
source

Source: https://habr.com/ru/post/919093/


All Articles