Scala - Control Abstractions



This chapter takes you through the concept of control abstractions in Scala programming. You can define custom control structures that encapsulate patterns of control flow. It enhances code readability and reusability.

Control Abstractions

Control abstractions are used to create custom control structures that abstract avoid repetitive control flow patterns. So, your code will be more concise and expressive. These abstractions use object-oriented principles to encapsulate and control logic effectively.

Control abstractions are high-level constructs that abstract control flow patterns into reusable and expressive custom control structures with object-oriented design principles.

Syntax

The syntax of control abstraction in Scala is -

def controlAbstraction(params: Type)(body: => ReturnType): ReturnType = {
   // control flow logic
   body
}

Example of Control Abstractions

The following example shows defining and using a simple control abstraction in Scala programming −

object Demo {
   def repeat(n: Int)(body: => Unit): Unit = {
      for (_ <- 1 to n) body
   }

   def main(args: Array[String]): Unit = {
      repeat(3) {
         println("Hello, Scala!")
      }
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

Hello, Scala!
Hello, Scala!
Hello, Scala!

In the example, the repeat function is a control abstraction that repeats a block of code given number of times.

Types of Control Abstractions

There are different types of control abstraction in Scala which are as follows −

1. Looping Abstractions

You can abstract repetitive code patterns using looping abstractions, so loops will be more expressive.

Syntax

The syntax of looping abstractions is -

def loop(n: Int)(body: => Unit): Unit = {
   for (_ <- 1 to n) body
}

Example

object Demo {
   def loop(n: Int)(body: => Unit): Unit = {
      for (_ <- 1 to n) body
   }

   def main(args: Array[String]): Unit = {
      loop(5) {
         println("Looping")
      }
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

Looping
Looping
Looping
Looping
Looping

2. Conditional Abstractions

You can manage conditional logic using conditional abstractions in a more concise manner.

Syntax

The syntax of conditional abstractions is -

def unless(condition: Boolean)(body: => Unit): Unit = {
   if (!condition) body
}

Example

object Demo {
   def unless(condition: Boolean)(body: => Unit): Unit = {
      if (!condition) body
   }

   def main(args: Array[String]): Unit = {
      unless(2 > 3) {
         println("2 is not greater than 3")
      }
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

2 is not greater than 3

3. Resource Management Abstractions

You can also manage resources using abstractions properly managed, like, closing files, database connections, etc.

Syntax

The syntax of resource management abstractions is -

def using[A <: { def close(): Unit }, B](resource: A)(body: A => B): B = {
   try {
      body(resource)
   } finally {
      resource.close()
   }
}

Example

import java.io._
import scala.language.reflectiveCalls

object Demo {
   def using[A <: { def close(): Unit }, B](resource: A)(body: A => B): B = {
      try {
         body(resource)
      } finally {
         resource.close()
      }
   }
   def main(args: Array[String]): Unit = {
      using(new PrintWriter("output.txt")) { writer =>
         writer.write("Hello, Scala!")
      }
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

Check the output.txt file for the written content -

Hello, Scala!

4. Retry Abstractions

You can use retry abstractions to encapsulate retry logic. You can perform operations multiple times.

Syntax

The syntax of retry abstractions is -

def retry[T](n: Int)(block: => T): T = {
   var lastException: Option[Throwable] = None
   for (_ <- 1 to n) {
      try {
         return block
      } catch {
         case e: Throwable => lastException = Some(e)
      }
   }
   throw lastException.getOrElse(new RuntimeException("Unknown error"))
}

Example

object Demo {
   def retry[T](n: Int)(block: => T): T = {
      var lastException: Option[Throwable] = None
      for (_ <- 1 to n) {
         try {
            return block
         } catch {
            case e: Throwable => lastException = Some(e)
         }
      }
      throw lastException.getOrElse(new RuntimeException("Unknown error"))
   }

   def main(args: Array[String]): Unit = {
      var attempts = 0
      val result = retry(3) {
         attempts += 1
         if (attempts < 3) throw new RuntimeException("Failed attempt")
         "Success"
      }
      println(result) 
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

Success

5. Custom Control Abstractions

You can define custom control abstractions for your specific use cases. Spo enhances code modularity and readability.

Syntax

The syntax of custom control abstractions is as -

def customControl[A, B](params: Type)(body: => A): B = {
   // custom control flow logic
   body
}

Example

Consider a custom control abstraction for timing the execution of a block of code -

object Demo {
   def time[A](body: => A): A = {
      val start = System.nanoTime()
      val result = body
      val end = System.nanoTime()
      println(s"Execution time: ${(end - start) / 1e6} ms")
      result
   }

   def main(args: Array[String]): Unit = {
      time {
         Thread.sleep(500)
         println("Slept for 500 ms")
      }
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

Slept for 500 ms
Execution time: 500.x ms

Control Abstractions for Collections

You can use control abstractions to simplify operations on collections, like, mapping, filtering, folding, etc.

Syntax

The syntax of control abstractions for collections is -

def map[A, B](collection: Seq[A])(f: A => B): Seq[B] = {
   collection.map(f)
}

def filter[A](collection: Seq[A])(predicate: A => Boolean): Seq[A] = {
   collection.filter(predicate)
}

Example

object Demo {
   def map[A, B](collection: Seq[A])(f: A => B): Seq[B] = {
      collection.map(f)
   }

   def filter[A](collection: Seq[A])(predicate: A => Boolean): Seq[A] = {
      collection.filter(predicate)
   }

   def main(args: Array[String]): Unit = {
      val nums = Seq(1, 2, 3, 4, 5)
      val doubled = map(nums)(_ * 2)
      val evens = filter(nums)(_ % 2 == 0)
      println(doubled) 
      println(evens) 
   }
}

Save the above program in Demo.scala. Use the following commands to compile and execute this program.

Command

> scalac Demo.scala
> scala Demo

Output

List(2, 4, 6, 8, 10)
List(2, 4)

Control Abstractions Summary

  • Control abstractions in Scala encapsulate control flow patterns in higher-order functions.
  • Control abstractions simplify complex control flows, enhance code readability, and promote reuse.
  • You can manage looping abstractions for repetitive code patterns.
  • You can simply conditional logic using conditional abstractions.
  • You can manage resources using abstractions.
  • Retry abstractions encapsulate retry logic.
  • Custom control abstractions can be defined for your specific use cases.
  • You can simplify operations on collections using control abstractions for collections.
Advertisements