techkuz
techkuz

Reputation: 3956

Extend case class with typeclass

I have this code

  case class Salary(employee: String, amount: Double){

  }

  trait XN[M] {
    def x2(m: M): M
    def x3(m: M): M
    def x4(m: M): M
  }

    

I want to extend Salary with XN trait in order for the following test to work:

test("Salary is extended with xN") {

    val bobSalary = Salary("Bob", 100.0)

    bobSalary.x2 shouldBe Salary("Bob", 200.0)
    bobSalary.x3 shouldBe Salary("Bob", 300.0)
    bobSalary.x4 shouldBe Salary("Bob", 400.0)

  }

My attempts:

#1

  implicit val SalaryXN: XN[Salary] = new XN[Salary] {
    override def x2(m: Salary): Salary = m.copy(amount = m.amount * 2)

    override def x3(m: Salary): Salary = m.copy(amount = m.amount * 3)

    override def x4(m: Salary): Salary = m.copy(amount = m.amount * 4)
  }

#2

  object Salary extends XN[Salary] {
    override def x2(m: Salary): Salary = new Salary(employee = m.employee, amount = m.amount * 2)

    override def x3(m: Salary): Salary = new Salary(employee = m.employee, amount = m.amount * 3)

    override def x4(m: Salary): Salary = new Salary(employee = m.employee, amount = m.amount * 4)
  }

How to do that?

Online code

Upvotes: 1

Views: 117

Answers (3)

Randomness Slayer
Randomness Slayer

Reputation: 724

[Use Accepted solution, implicits are tricky!]

greg made a great start, but as written it won't work in your solution.

Here are a few changes:

Still using:

case class Salary(employee: String, amount: Double) // As before

You can either call the method on a Salary or pass it a Salary, you're trying to do both.

The simplest would be to try:

trait XN[M] {
    def x2: M
    def x3: M
    def x4: M
  }

implicit def salary2XN(s: Salary): XN[Salary] = {
    new XN[Salary] {
      override def x2: Salary = s.copy(amount = 2 * s.amount)
      override def x3: Salary = s.copy(amount = 3 * s.amount)
      override def x4: Salary = s.copy(amount = 4 * s.amount)
    }
  }

And now you can call each method using your signature:

Salary("bob", 200).x2

EDIT:

A solution using plain old extension:

trait XN[M] {
    def x2: M
    def x3: M
    def x4: M
  }

  case class Salary(employee: String, amount: Double) extends XN[Salary] {
    override def x2: Salary = Salary(employee, 2 * amount)
    override def x3: Salary = Salary(employee, 3 * amount)
    override def x4: Salary = Salary(employee, 4 * amount)
  }

Which you can now call the exact same way:

Salary("bob", 233).x2

NOTE: In the former implementation, Salarys themselves do not have the methods of the XN, rather we apply them to each Salary using an implicit conversion. With the latter implementation, EVERY Salary has the methods for XN, we define it in the implementation of the case class itself.

Upvotes: 3

Since it seems that XN is a typeclass, it would be better to properly use that pattern instead of relying on a (discouraged) implicit conversion.

trait XN[M] {
  def x2(m: M): M
  def x3(m: M): M
  def x4(m: M): M
}

object XN {
  object syntax {
    implicit class XNOp[M](private val m: M) extends AnyVal {
      @inline final def x2(implicit ev: XN[M]): M = ev.x2(m)
      @inline final def x3(implicit ev: XN[M]): M = ev.x3(m)
      @inline final def x4(implicit ev: XN[M]): M = ev.x4(m)
    }
  }
}

final case class Salary(employee: String, amount: Double)
object Salary {
  implicit final val SalaryXN: XN[Salary] =
    new XN[Salary] {
      override def x2(s: Salary): Salary = s.copy(amount = s.amount * 2)
      override def x3(s: Salary): Salary = s.copy(amount = s.amount * 3)
      override def x4(s: Salary): Salary = s.copy(amount = s.amount * 4)
    }
}

Which can be used like this:

import XN.syntax._

val bobSalary = Salary("Bob", 100.0)

bobSalary.x2
// res: Salary = Salary("Bob", 200.0)

You can see the code running here.

Upvotes: 2

greg
greg

Reputation: 1160

Creating implicit conversion from Salary to XN could be a solution. This is how scala add new functionality to existing classes without extending them.

implicit def salary2XN(s: Salary): XN[Salary] = {
  new XN[Salary] {
    override def x2: Salary = Salary(s.employee, 2*s.amount)
    override def x3: Salary = Salary(s.employee, 3*s.amount)
    override def x4: Salary = Salary(s.employee, 4*s.amount)
  }
}

Upvotes: 2

Related Questions