开发者

scala: use implicits to support two different types for each argument to a method?

开发者 https://www.devze.com 2023-03-22 03:28 出处:网络
suppose i have a method that takes a large number of arguments, each of which could be a String or a Seq[String]. inside the method, i want the value of each argument to be just the value if it was a

suppose i have a method that takes a large number of arguments, each of which could be a String or a Seq[String]. inside the method, i want the value of each argument to be just the value if it was a String, and mkString " " on the Seq if it was a Seq[String]. i realize that Left/Right could possibly be used here but i don't want the caller to have to wrap any args.

i've used implicits to force the arg to be either a String or a Seq[String] but i can't figure out how to get the arg to reduce itself to a string, either as the identity or as mkString, depending on the case. the ideal code might look like this:

trait StrOrSeqStr[A]

implicit object Str extends StrOrSeqStr[String]
implicit object SeqStr extends StrOrSeqStr[Seq[String]]

def f[A, B](a: A, b: B)(implicit a: StrOrSeqStr[A], implicit b: StrOrSeqStr[B]) {
  val aIn: String = a
  val bIn: String = b // converts strings to themselves, seq strings as _ mkString " "
}

// both开发者_StackOverflow should work
f(Seq("1"), "2")
f("1", Seq("2", "3"))

any suggestions? thanks!


Please correct me if I'm wrong, but as far as I understand you just need to convert String to Seq[String] implicitly. This simple implicit conversion should do it:

implicit def stringToSeqString(s: String) = Seq(s)

def f(a: Seq[String], b: Seq[String]) {
  val (aIn, bIn) = (a mkString " ", b mkString " ")
  // ...
}

f(Seq("1"), "2")
f("1", Seq("2", "3"))


implicit def stringsToString(s: Seq[String]) = s mkString " "

def f(s: String *) {
   println(s mkString " ")
}

scala> f("1")
1

scala> f("1", "2")
1 2

scala> f(Seq("1", "2"))
1 2

scala> f(Seq("1"), Seq("2"))
1 2

scala> f("1", Seq("2"))
1 2

scala> f(Seq("1"), "2")
1 2


As far as I can see you tried to use context bounds in order to give function f something that can be treated as String or can be converted to it. I addition to my other answer I want to add other solution that uses type classes. It's more complicated, so I can advise you to use simple implicit conversion, that I described in another answer, if it solves your problem.

For demonstration purposes I renamed StrOrSeqStr to Displayable and made it more generic, so that it works not only for String and Seq[String], but also for any other types and Seq[T] if type parameter T is also has Displayable for it (it actually can work with any class you want - you just need to provide suitable implicit).

At first you need to define Displayable:

trait Displayable[T] {
  def getString(target: T): String
}

object Displayable {
  implicit object StringDisplayable extends Displayable[String] {
    def getString(str: String) = str
  }

  implicit object DateDisplayable extends Displayable[Date] {
    val dateFormat = new SimpleDateFormat("dd.MM.yyyy")
    def getString(date: Date) = dateFormat format date
  }

  implicit def seqDisplayable[T: Displayable] = new Displayable[Seq[T]] {
    def getString(seq: Seq[T]) = seq map implicitly[Displayable[T]].getString mkString " "
  }
}

As you can see I collected all related implicits in companion object, so that they would be always available and do not require explicit import (good for future usage).

Interesting method here is seqDisplayable - it requires T to also have Displayable (this also makes it recursive - so that Seq[Seq[Date]] for example would also have Displayable). I also created special rules for String and Date. So as you can see, it's pretty restrictive and allows String, Date, Seq[String], Seq[Date], Seq[Seq[String]], Seq[Seq[String]], etc. If you want to allow all other objects you can add something like this:

implicit def anythingDisplayable[T] = new Displayable[T] {
  def getString(anything: T) = anything toString
}

Now comes f function:

def f[A : Displayable, B : Displayable](a: A, b: B) {
  val aIn = implicitly[Displayable[A]].getString(a)
  val bIn = implicitly[Displayable[B]].getString(b)
  // ...
}

So I require evidence: there should exist some Displayable for A and B and then I just convert them to String using these Displayables. If you want to simplify it further, you can create implicit conversion from some type that has Dislplayable to String:

implicit def displayableToString[T : Displayable](target: T) =
      implicitly[Displayable[T]].getString(target)

def f[A : Displayable, B : Displayable](a: A, b: B) {
  val aIn: String = a
  val bIn: String = b
  //...
}

Here is example of usage:

f(Seq("1"), "2")
f("1", Seq("2", "3"))
f(new Date, Seq("10", "3"))
f(new Date, Seq(Seq("10", "11"), Seq("3", "4")))
f(Seq(new Date, new Date(12345)), "1")

Hope this helps.


Close, very close...

trait StrOrSeqStr[A] {
  def mkString(x: A): String
}

implicit object Str extends StrOrSeqStr[String] { def mkString(s: String) = s }
implicit object SeqStr extends StrOrSeqStr[Seq[String]] {
    def mkString(s: Seq[String]) = s mkString " "
}

def f[A, B](a: A, b: B)(implicit evA: StrOrSeqStr[A], evB: StrOrSeqStr[B]) {
  implicit def fromSeqString(s: Seq[String]): String = s mkString ""
  val aIn: String = evA mkString a
  val bIn: String = evB mkString b
}

// both should work
f(Seq("1"), "2")
f("1", Seq("2", "3"))
0

精彩评论

暂无评论...
验证码 换一张
取 消