Software DevelopmentLearn How Errors Are Handled Without Use Of Exceptions In Scala

Learn How Errors Are Handled Without Use Of Exceptions In Scala

Exceptions
Errors in code development cannot be completely eliminated. It is therefore necessary for a mature programming language to avail mechanisms for dealing with errors when they arise. One way of handling errors is to anticipate where they are likely to occur then use a throw and catch block to handle them. Throwing exceptions involves side effects which go against the concepts of functional programming. Consider the function defined below where a try and catch block is used.

def sampleError(results: Int):  Int = {
	val err: Int = throw new Exception (“fail”!)
	try {
		val sum = 68 + 4
		sum + err
	}
	catch { case e: Exception => 70 }
}

The expression that is assigned to sum will give an expected result of 72. However the expression throw new Exception (“fail”!) causes program flow to move to catch statement without returning any value. By looking at the type signature we observe that the function should return an Int type. However this is not the case because the function can return an exception. When it happens that a function does not at all times return the value it says should be returned in that case function purity has been broken.
Avoiding exceptions in our code has its advantages. First avoiding exceptions enables us to use pure functions. Secondly we are able to use monadic composition. In the following sections we are going to look at available alternatives for dealing with errors.
To further demonstrate exceptions and their alternatives consider the program shown below. The program describes a hypothetical situation of preparing fruit juice. In this program we first buy fruits then use a blender to make juice. In the program we first check there is sufficient money to purchase fruits, if not we throw an exception. We also check to make sure the juice blender is functioning properly, if not we throw an exception.

class Juice
class Fruits
object JuiceService {
  val price = 85
  def purchaseJuice(money: Int): Juice =
    blendJuice(buyFruits(money))
  def buyFruits(money: Int): Fruits = {
    if (money < price)
      throw new Exception(s"Not enough money to buy Fruits, need $price")
    else
      new Fruits
  }
  def blendJuice(fruits: Fruits): Juice = {
    // simulate a faulty blender that only works 20 days in a month
    if (Math.random < 0.30)
      throw new Exception("Faulty juice blender!")
    else
      new Juice
  }
}

If you look at the program above the functions blendJuice and buyFruits can return values not defined in their parameter signature. This is another instance of impure functions.

One way of handling errors without exceptions is to use Option. If we rewrite our juice preparation program using option it would be as shown below

class Juice
class Fruits
object JuiceServiceOption {
  val price = 85
  def purchaseJuice(money: Int): Option[Juice] =
    for {
      fruits <- buyFruits(money)
      juice <- blendJuice(fruits)
    } yield juice
  def buyFruits(money: Int): Option[Fruits] = {
    if (money < price)
      None
    else
      Some(new Fruits)
  }
  def blendJuice(fruits: Fruits): Option[Juice] = {
    // simulate a faulty blenderthat works only 20 days in a month
    if (Math.random < 0.30)
      None
    else
      Some(new Juice)
  }
}

In our rewritten program we use a for comprehension to chain buying fruits and blending juice. If it happens that the buyFruits function returns a None program flow will terminate. The function purchaseJuice will return a None and the blendJuice function will not execute.
Another way of dealing with errors without resorting to exceptions is to use a Try. We place code that we anticipate to throw exception in a Try block. To understand how Try works look at it as container that holds a success if a value is returned or holds a failure if an exception happens. If we rewrite the program that uses exceptions it would be as shown below. In our rewritten program we place the code that checks if there is sufficient money and if the blender is working in a Try block.

import scala.util.Try
class Juice
class Fruits
object juiceBlendTry {
  val price = 85
  def purchaseJuice(money: Int): Try[Juice] =
    for {
      beans <- buyFruits(money)
      juice <- blendJuice(fruits)
    } yield juice
  def buyFruits(money: Int): Try[Fruits] = {
    Try {
      if (money < price)
        throw new Exception(s"Not enough money to buy fruits, need $price")
      else
        new Fruits
    }
  }
  def blendJuice(fruits: Fruits): Try[Juice] = {
    Try {
      // simulate a faulty blennder that works only 20 days in a month
      if (Math.random < 0.30)
        throw new Exception("Juice blender not working!")
      else
        new Juice
    }
  }
}

The two approaches discussed above for handling errors without exceptions have their limitations. Try uses exceptions which we would like to avoid. When we use option we lack complete information on why there was failure. To overcome these two limitations we can use Either. It is important to note that Either lacks the methods map and flatMap and also does not have biasing. The value returned is stored in Right while Left stores the error. Our rewritten program using Either is shown below.

import scala.util.{ Either, Left, Right }
class Juice
class Fruits
case class FailureReason(reason: String)
object juiceBlendEither {
  val price = 85
  def purchaseJuice(money: Int): Either[FailureReason, Juice] =
    for {
      fruits <- buyFruits(money).right
      juice <- blendJuice(fruits).right
    } yield juice
  def buyFruits(money: Int): Either[FailureReason, Fruits] = {
    if (money < price)
      Left(FailureReason(s"Not enough money to buy fruits, you need $price"))
    else
      Right(new Fruits)
  }
  def blendJuice(fruits: Fruits): Either[FailureReason, Juice] = {
    // simulate a faulty blender that works only 20 days in a month
    if (Math.random < 0.30)
      Left(FailureReason("The blender is not working!"))
    else
      Right(new Juice)
  }
}

In this tutorial we looked at how errors are handled by use of exceptions. We noted that exceptions lead to side effects which we would like to avoid in functional programming. We looked at three alternatives of handling errors without exceptions. These were Option, Try and Either. We looked at how a program that used exceptions could be rewritten. The three options exist within the Scala standard library. There are other alternatives available outside the standard library but we did not look at them.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Exclusive content

- Advertisement -

Latest article

21,501FansLike
4,106FollowersFollow
106,000SubscribersSubscribe

More article

- Advertisement -