2

I have a Java interface like the following:

interface Bazz {
   void bar(String msg, Object args...);
}

I want to implement that interface (just happens to be in Scala) and use SLF4J logging to log the args parameter (for sake of this question, using another Scala logging library is not an option).

And the following (contrived) scala code:

object Foo extends Bazz {

  private val log = LoggerFactory.getLogger(Main.getClass)

  def bar(args: AnyRef*) {
    log.info("Processing params: {}, {}, {}", args: _*)
    // ... do stuff with args...
  }
}

object Main extends App {

  val arr: Seq[String] = Array("A","B","C")
  val anyRef: AnyRef = arr  

  Foo.bar(arr)

}

}

Running Main, I get the following output:

22:49:54.658 [run-main-0] INFO: sample.Main$ Processing params: WrappedArray(A, B, C), {}, {} 

That's no good because the :_* is exploding the args, but the first element is an array. For reasons I won't go into here, I actually need to pass the elements of that array as the single Object[] parameter to SLF4J's Logger.info(String, Object[]) method.

Here's my first attempt:

def bar(args: AnyRef*) {
  val flatArgs = args.flatMap {
    case s: Seq[_] => s
    case x         => Seq(x)
  }
  log.info("Processing params: {}, {}, {}", flatArgs: _*)
  // ... do stuff with args...
}

This does not compiles with the following error:

[error] Main.scala:18: overloaded method value info with alternatives:
[error]   (x$1: org.slf4j.Marker,x$2: String,x$3: <repeated...>[Object])Unit <and>
[error]   (x$1: org.slf4j.Marker,x$2: String)Unit <and>
[error]   (x$1: String,x$2: Throwable)Unit <and>
[error]   (x$1: String,x$2: <repeated...>[Object])Unit <and>
[error]   (x$1: String,x$2: Any)Unit
[error]  cannot be applied to (String, Any)
[error]     log.info("Processing params: {}, {}, {}", flatArgs: _*)
[error]         ^
[error] one error found

How can I change this code to make it compile and also print Processing params: A, B, C?

noahlz
  • 10,202
  • 7
  • 56
  • 75
  • 1
    Why do you use `AnyRef` ? – Ven Jun 11 '14 at 07:34
  • Modified my question to clarify this. It's about java interop. – noahlz Jun 11 '14 at 14:35
  • It might sound awful but you might be able to pattern match the arguments out? That is, `case x: WrappedArray[_] => stuff` – wheaties Jun 11 '14 at 15:14
  • I'd agree with @Ven. `anyRef: AnyRef` is the problem here. `args` is a one element array, where the first element is a `WrappedArray`. Use `foo("message",arr:_*)` to get the desired behaviour. – ggovan Jun 11 '14 at 15:30
  • I'm stuck with the AnyRef* because that's how you implement Java interfaces with VarArgs. – noahlz Jun 11 '14 at 15:41
  • See also: http://stackoverflow.com/questions/3313929/how-do-i-disambiguate-in-scala-between-methods-with-vararg-and-without – noahlz Jun 11 '14 at 15:43
  • All - sincere apologies - I significantly re-wrote my question, as I realized I didn't quite understand my own problem. Please have another look and if you wish, revise your own answers. – noahlz Jun 13 '14 at 03:08

3 Answers3

2

A problem here is that Int is an AnyVal which means that it is not an AnyRef/Object Also an Array is not a TraversableOnce.

Now in the foo method we pattern match on the varargs.

def foo(msg:String,varargs:AnyRef*) { varargs.toList match {
  case (h:TraversableOnce[_]) :: Nil => log.info(msg, h.toSeq.asInstanceOf[Seq[AnyRef]]:_*)
  case (h:Array[_]) :: Nil => log.info(msg, h.toSeq.asInstanceOf[Seq[AnyRef]]:_*)
  case _ => log.info(msg,varargs:_*)
}}

There are 3 cases we deal with:

case (h:TraversableOnce[_]) :: Nil => log.info(msg, h.toSeq.asInstanceOf[Seq[AnyRef]]:_*)

TraversableOnce is one of the Scala collections base traits; every collection I know of extends this (Array is not a collection, it doesn't extend it). It can contain either AnyVal or AnyRef but the actual items at runtime will be wrapped primitive (java.lang.Integer, etc.). So we can down cast from Seq[Any] to Seq[AnyRef] here and we should be okay.

case (h:Array[_]) :: Nil => log.info(msg, h.toSeq.asInstanceOf[Seq[AnyRef]]:_*)

Just like we did with the TraversableOnce, turn it into a Seq and then downcast. The transformation into Seq will wrap any primitives for us.

case _ => log.info(msg,varargs:_*)

The general case in which varargs could be empty, or contain more than one entry.

ggovan
  • 1,907
  • 18
  • 21
2

Here's the answer to my re-stated (now obvious) problem. Apologies for not having a clear question earlier:

def bar(args: AnyRef*) {
  val flatArgs = args.flatMap {
    case s: Seq[_] => s
    case x         => Seq(x)
  }.toArray.asInstanceOf[Array[Object]]

  log.info("Processing params: {}, {}, {}", flatArgs: _*)
}

Note: without the asInstanceOf[Array[Object]] the following occurs:

[error] type mismatch;
[error]  found   : Array[Any]
[error]  required: Array[Object]
[error] Note: Any >: Object, but class Array is invariant in type T.
[error] You may wish to investigate a wildcard type such as `_ >: Object`. (SLS 3.2.10)
[error]   }.toArray
[error]     ^
[error] one error found
noahlz
  • 10,202
  • 7
  • 56
  • 75
1

Simpler variation on ggovan's, doing the match before calling foo:

val arr: Seq[Int] = Array(1,2,3)
val anyRef: AnyRef = arr 

val msg = "Another message: {}, {}, {}"
  anyRef match {
    case seq: Seq[_] => foo(msg, seq.asInstanceOf[Seq[AnyRef]]: _*)
    case x => foo(msg, x)
  }    
sourcedelica
  • 23,940
  • 7
  • 66
  • 74