Big Data, iPaaS, SCALA

Advanced Type Classes and Implicits in Scala

This entry is part 5 of 9 in the series Scala Series

Originally posted November 15, 2018 by Kinshuk Dutta


In this blog, we’ll explore the powerful concepts of type classes and implicits in Scala. Type classes allow us to define functionality based on the type of an argument, without modifying existing code or relying on inheritance. Implicitsenable Scala to find the right implementations at runtime, making our code more flexible and concise. Together, they’re perfect for building modular applications.

To solidify these concepts, we’ll build a sample project: a Flexible Discount System for an e-commerce platform. This system uses type classes and implicits to calculate discounts based on customer type and purchase history.


Table of Contents

  1. Introduction to Type Classes
  2. Implicits in Scala
  3. Building the Flexible Discount System
  4. Conclusion and Next Steps

Introduction to Type Classes

In Scala, type classes provide a way to extend functionality without modifying existing code. Instead of using inheritance, type classes define behavior based on the types of arguments, making them versatile and reusable.

A type class is essentially a trait that describes a set of methods for a particular type. For example:

scala
trait Discountable[T] {
def discount(item: T): Double
}

To use this trait, we implement it for each type of item we want to be discountable.

Implicits in Scala

Implicits allow Scala to inject certain values, methods, or type class instances automatically. This is particularly useful with type classes, as Scala can find the right type class instance at runtime, based on context.

Examples of Implicits

  1. Implicit Values – Automatically passed as parameters to functions.
  2. Implicit Classes – Allow us to add methods to existing types.
  3. Implicit Parameters – Allow functions to receive parameters implicitly.

Here’s a simple implicit example:

scala
implicit val defaultDiscountRate: Double = 0.1

def calculateTotal(amount: Double)(implicit discountRate: Double): Double = {
amount * (1 - discountRate)
}

Scala will automatically inject defaultDiscountRate into calculateTotal.


Building the Flexible Discount System

Our Flexible Discount System will use type classes and implicits to apply different discount rates based on the customer’s profile. This system is designed for maximum flexibility, making it easy to add new discount criteria.

Project Structure

plaintext
flexible-discount-system

├── src
│ ├── main
│ │ ├── scala
│ │ │ ├── ecommerce
│ │ │ │ ├── models
│ │ │ │ │ ├── Customer.scala
│ │ │ │ │ ├── Order.scala
│ │ │ │ ├── services
│ │ │ │ │ ├── DiscountService.scala
│ │ │ │ │ ├── Discountable.scala
│ │ │ │ │ ├── Discounts.scala
│ │ │ │ ├── Main.scala

├── test
│ ├── scala
│ │ ├── ecommerce
│ │ │ ├── DiscountServiceTest.scala
└── build.sbt

Implementation Guide

Step 1: Define Models

In the models directory, define structures for Customer and Order.

Customer.scala

scala
package ecommerce.models

sealed trait CustomerType
case object Regular extends CustomerType
case object Premium extends CustomerType

case class Customer(id: String, name: String, customerType: CustomerType)

Order.scala

scala
package ecommerce.models

case class Order(id: String, customer: Customer, totalAmount: Double)

Step 2: Define Type Class and Implementations

In the services directory, create a type class called Discountable and define discount logic based on the CustomerType.

Discountable.scala

scala
package ecommerce.services

import ecommerce.models._

trait Discountable[T] {
def discount(item: T): Double
}

Discounts.scala

scala
package ecommerce.services

import ecommerce.models._

object Discounts {
implicit val regularCustomerDiscount: Discountable[Customer] = new Discountable[Customer] {
def discount(customer: Customer): Double = customer.customerType match {
case Regular => 0.05
case Premium => 0.15
}
}

implicit val orderDiscount: Discountable[Order] = new Discountable[Order] {
def discount(order: Order): Double = order.customer.customerType match {
case Regular => order.totalAmount * 0.05
case Premium => order.totalAmount * 0.15
}
}
}

Step 3: Create Discount Service with Implicit Parameter

The DiscountService will use implicits to apply discounts automatically based on the type.

DiscountService.scala

scala
package ecommerce.services

import ecommerce.models._

object DiscountService {
def applyDiscount[T](item: T)(implicit discountable: Discountable[T]): Double = {
discountable.discount(item)
}
}

Step 4: Main Application Logic

Main.scala

scala
package ecommerce

import ecommerce.models._
import ecommerce.services._
import ecommerce.services.Discounts._

object Main extends App {
val regularCustomer = Customer("C001", "John Doe", Regular)
val premiumCustomer = Customer("C002", "Jane Smith", Premium)

val order1 = Order("O001", regularCustomer, 100)
val order2 = Order("O002", premiumCustomer, 200)

println(s"Discount for Regular Customer: ${DiscountService.applyDiscount(regularCustomer)}")
println(s"Discount for Premium Customer Order: ${DiscountService.applyDiscount(order2)}")
}


Testing the Project

Step 1: Add ScalaTest Dependency

Add the ScalaTest library in build.sbt:

scala
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % Test

Step 2: Write Tests

DiscountServiceTest.scala

scala
package ecommerce.services

import org.scalatest.flatspec.AnyFlatSpec
import ecommerce.models._
import ecommerce.services.Discounts._

class DiscountServiceTest extends AnyFlatSpec {

"DiscountService" should "apply 5% discount for regular customers" in {
val customer = Customer("C001", "John Doe", Regular)
assert(DiscountService.applyDiscount(customer) == 0.05)
}

it should "apply 15% discount for premium customer orders" in {
val customer = Customer("C002", "Jane Smith", Premium)
val order = Order("O002", customer, 200)
assert(DiscountService.applyDiscount(order) == 30.0)
}
}

Step 3: Run Tests

Execute the tests with:

bash
sbt test

Conclusion and Next Steps

By leveraging type classes and implicits, we’ve developed a flexible discount system that applies discounts based on customer type with minimal boilerplate code. This project illustrates how Scala’s advanced functional programming features can streamline business logic, making code more modular and reusable.

Series Navigation<< Concurrency and Parallelism in ScalaConcurrency in Scala >>