简体   繁体   中英

Scala case class with multiple-type argument

I need to check integrity of nested schemas and hence am writing case classes to do so. The main hurdle I am facing is the schema may have a field (say, name ) either of a String or a Utf8 type and I want to accept both the instances. Is it possible to avoid having two case classes as

 case class NameValueString(name: String, value: Double)
 case class NameValueUtf8(name: Utf8, value: Double)

and something like

 case class NameValue(name @(_:String | _:Utf8), value: Double)

The above expression certainly fails compilation.

Nikhil

One approach is so-called type classes:

trait StringLike[A] // sealed if you don't want anybody to implement it elsewhere
object StringLike {
  implicit object StringEv extends StringLike[String] {}
  implicit object Utf8Ev extends StringLike[Utf8] {}
}

case class NameValue[A](name: A, value: Double)(implicit val stringLike: StringLike[A])

Of course, StringLike will normally not be empty, but describe whatever common functionality you need from both String and Utf8 .

You can match on the evidence:

def nameLength[A](nameValue: NameValue[A]) = nameValue.stringLike match {
  case StringLike.StringEv => 
    nameValue.name.length // calls String#length
  case StringLike.Utf8Ev =>
    nameValue.name.length // calls Utf8#length (assuming Utf8 has such method)
}

In this case the compiler will even know that A (and so the type of nameValue.name ) is String in the first branch and Utf8 in the second.

Another pattern (doesn't require implicit arguments):

import scala.language.implicitConversions

class StringLike[A](name: A) {
  override def toString = {
    name match {
      case s: String => s"String: $s"
      case i: Int => s"Int: $i"
    }
  }
}
implicit def string2StringLike(s: String) = new StringLike(s)
implicit def int2StringLike(i: Int) = new StringLike(i)

case class NameValue[A](name: StringLike[A], value: String) {
  override def toString = name.toString
}

NameValue("123", "123")
//> NameValue[String] = String: 123
NameValue(13, "123")
//> NameValue[Int] = Int: 13
NameValue(13.9, "123")
// error: type mismatch;
//  found   : Double(13.9)
//  required: StringLike[?]
//      NameValue(13.9, "123")
//                ^

UPDATE

Here's how I see completed type class approach based on Alexey's answer:

trait StringLike[A] {
  def toString(x: A): String
}

object StringLike {
  implicit object StringStringLike extends StringLike[String] {
    def toString(s: String) = s"String: $s"
  }
  implicit object IntStringLike extends StringLike[Int] {
    def toString(i: Int) = s"Int: $i"
  } 
}

import StringLike._

case class NameValue[A](name: A, value: Double)(implicit ev: StringLike[A]) {
  override def toString = ev.toString(name)
}

NameValue(1, 2.0)
//> NameValue[Int] = Int: 1

NameValue("123", 2.0)
//> NameValue[String] = String: 123

NameValue(2.0, 2.0)
// error: could not find implicit value for parameter ev: 
// StringLike[Double]
//       NameValue(2.0, 2.0)
//                ^

UPDATE2

One more (using union type for type safety):

type ¬[A] = A => Nothing
type ¬¬[A] = ¬[¬[A]]
type ∨[T, U] = ¬[¬[T] with ¬[U]]
type |∨|[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) }

def nameLength[A: ClassTag: (Int |∨| String)#λ](nameValue: NameValue[A]) =
  nameValue.name match {
    case s:String => s.length
    case i:Int => i + 1
  } 

As you are using case class already, if you just need different ways to create it, and you are ok on keeping your data represented in only one way, you can add your own apply method to enable the creation using different parameters.

 case class NameValue(name: String, value: Double)
 object NameValue{
     def apply(name: Utf8, value: Double): NameValue = {
         new NameValue( name.toString, value )
     }
 }

Alternatively, if you would like to pattern match and extract NameValue from different options, you may need to check Extractors, which is basically create your own unapply methods... check http://danielwestheide.com/blog/2012/11/21/the-neophytes-guide-to-scala-part-1-extractors.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM