2

Problem: I have some case classes and for every case class I have to run one simple function that return same instance of case class. so for every case class I have to implement same function each time even though, function has same functionality. Is there a SCALA way of iterating over case class type.

object SampleCaseClasses {

  case class a(elem1: String, elem2: Int)

  case class b(elem3: String, elem4: Int)

  case class c(elem5: String, elem6: Int)

  case class d(elem7: String, elem8: Int)

  case class e(elem9: String, elem10: Int)

  case class f(elem10: String, elem12: Int)

  case class g(elem11: String, elem14: Int)

}

import SampleCaseClasses.{a, b}

def getProperAddress[a](host:String,port:Int): SampleCaseClasses.a ={
  a(host.toUpperCase,port+1)
}

def getProperAddress[b](host:String,port:Int): SampleCaseClasses.b ={
  b(host.toUpperCase,port+1)
}
1
  • 3
    You can check out Shapeless to write a generic function. However, are you sure that you need all those case classes? BTW this def getProperAddress[a] is unnecessary, just remove the [a] it is not doing what you think is doing. Commented Oct 14, 2020 at 15:53

3 Answers 3

5

You can pass the constructor itself as an argument to getProperAddress

def getProperAddress[T](ctor: (String, Int) => T)(host: String, port: Int): T =
  ctor(host, port)

Since they're all case classes, you can just do this, which is only a couple characters more than your original solution of making multiple methods (Scastie):

getProperAddress(a)("foo", 0)

For non-case classes, you would need something like this:

getProperAddress(new NotCaseClass(_, _))("foo", 1)
Sign up to request clarification or add additional context in comments.

Comments

2

You can use Shapeless

import shapeless.{Generic, HNil, ::}

def getProperAddress[A <: Product](host: String, port:Int)(implicit 
  generic: Generic.Aux[A, String :: Int :: HNil]
): A = generic.from(host.toUpperCase :: (port + 1) :: HNil)

getProperAddress[a]("http://...", 80)
getProperAddress[b]("http://...", 80)

or define a macro annotation

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro annotations")
class properAddress extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro ProperAddressMacro.impl
}

object ProperAddressMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    annottees match {
      case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        val companionObjects = body.collect {
          case q"case class $tpname[..$_] $_(...$_) extends { ..$_ } with ..$_ { $_ => ..$_ }" =>
            q"""object ${tpname.toTermName} {
              def getProperAddress(host: String, port: Int): $tpname = new $tpname(host.toUpperCase, port + 1)
            }"""
        }

        q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
          ..$body
          ..$companionObjects
        }"""
    }
  }
}

@properAddress
object SampleCaseClasses {
  case class a(elem1: String, elem2: Int)
  case class b(elem3: String, elem4: Int)
  //...
}

a.getProperAddress("http://...", 80)
b.getProperAddress("http://...", 80)

1 Comment

Thanks again for help.
0

You could try using Scala's Reflections, but this would be very unconventional and not nice. The simpler way: define a class e.g. Parent and inherit from it:

class Parent(val e:String, val i:Int)

case class a(elem1: String, elem2: Int) extends Parent(elem1, elem2)
case class b(elem3: String, elem4: Int) extends Parent(elem3, elem4)
case class c(elem5: String, elem6: Int) extends Parent(elem5, elem6)
case class d(elem7: String, elem8: Int) extends Parent(elem7, elem8)

  //...

def getProperAddress(host:String,port:Int):Parent = new Parent(host.toUpperCase,port+1)

I hope this solves your problem :)

EDIT:

If you really want to have the type as a result use scala's implicit conversions by providing a casting method for every case class (i replaced Parent with p):

//...
class p(val e:String, val i:Int)
case class a(elem1: String, elem2: Int) extends p(elem1, elem2)
case class b(elem3: String, elem4: Int) extends p(elem3, elem4)
//...
object p{
   implicit def pToA(v:p):a = a(v.e, v.i)
   implicit def pToB(v:p):b = b(v.e, v.i)
}
//...

def getProperAddress(host:String,port:Int):p = new p(host.toUpperCase,port+1)
//...
//using it:
val foo1:a = getProperAddress("google.com", 8080)
val foo2:b = getProperAddress("stackoverflow.com", 8080)

This way scala knows how to make an "a" from a "p" Unfortunately scala does not implement downward casting in type hierarchies, so either you use reflections (which should be more work than the casting thing), you provide the constructor as a parameter or you use implicit casts

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.