简体   繁体   中英

Scala generic wildcard and specific type

I have the mappings value:

val mappings: Map[Class[_] ,Iterable[AttributeKeyAndValue] => AnyRef]

is it possible to make it more typesafe like

val mappings: Map[Class[T], Iterable[AttributeKeyAndValue] => T]

where T plays the same role as underscore. I'd expect compiler to complain, if it meets this code:

val mappings: Map[Class[T], Iterable[AttributeKeyAndValue] => T] = Map(
  classOf[String], attrs => 1)

You can't parametrize val s so no, not like that.

Looking at your request, it doesn't make much sense. Lets say that this: val mappings: Map[Class[T], Iterable[AttributeKeyAndValue] => T] was valid and the compiler would complain.

You would either parametrize all the entries in the map with the same type, ie. T or have each entry with it's own parametrized type making it impossible to know which type it is when retrieving the entries with the apply or get methods.

I suggest you stick with the Class[_] because the only way to parametrize this is to force all the entries to have the same type. For example if you were able to parametrize it in Map[Class[String], ...] then you would only be able to put 1 entry in the map, the one where the key is classOf[String]

Wildcards in Scala are just a specific simple case of existential types, and what you want would be a more complex one because you want to use the same T in two places. Something like Seq[(Class[T], AttributeKeyAndValue => T) forSome { type T }] . But note where you need to put forSome : there is no equivalent place if you want Map ! Eg Map[Class[T], AttributeKeyAndValue => T] forSome { type T } would mean there is a single T for the entire map.

What I'd suggest is creating a type which presents a more type-safe interface, even if you need casts inside:

class Mappings private (contents: Map[Class[_], Iterable[AttributeKeyAndValue] => AnyRef]) {
  def get[T](clazz: Class[T]) = contents.get(clazz).asInstanceOf[Option[Iterable[AttributeKeyAndValue] => T]]

  def +[T](clazz: Class[T], value: Iterable[AttributeKeyAndValue] => T) = new Mappings(contents + (clazz, value))

  // any other methods you want
}

object Mappings {
  val empty = new Mappings(Map.empty)
}

// elsewhere
Mappings.empty + (classOf[String], attrs => "a") // type-checks
Mappings.empty + (classOf[String], attrs => 1) // doesn't type-check

You can actually improve the API to avoid manually passing classes, so you just write get[String] and +(attrs => 1) automatically infers it needs classOf[Int] , but I decided to show the simple idea here.

Well, compiler does not know what is T in example you've provided. So, as an option you can defined mappings function parametrised by T :

       def mappings[T](x: AttributeKeyAndValue => T): 
             Map[Class[T], AttributeKeyAndValue => T] = Map(classOf[T] -> x)

usage:

  val mappping = mappings(x => x.toString)

and the type that compiler will be able to infer is:

  mappping : Map[Class[String], AttributeKeyAndValue => String]

Wrap it around into a case class?

  type Attrs = Iterable[AttributeKeyAndValue]

  case class Mappings[T](m: Map[Class[T], Attrs => T]

  object Mappings {
    implicit def mk[T](m: Map[Class[T], Attrs => T): Mappings[T] = Mappings(m)
  } 

  val mappings: Mappings[_] = Map(classOf[String] -> { (_:Attrs) => "foo" })  // Works

  val badMappings: Mappings[_] =  Map(classOf[String] -> { (_:Attrs) => 1 })  // Fails

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