Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversions in the context of a (case) class constructor

I would like to have the automatic companion class apply constructors of a case class to perform implicit conversions for me, but cannot figure out how to do so. I've searched all over and the closest answer I could find was for this question (I'll explain why it isn't what I'm looking for below).

I have a case class that looks something like this:

case class Container(a: Long, b: Long, c: Long)

I'm using the container to count instances where certain conditions apply, so I'd like to be able to have the constructor automatically convert boolean parameters to longs (if (boolean) 1L else 0L).

The real case class, of course, has many parameters, so it would be tedious and terribly repetitive to make my own companion object and overload apply to accept Boolean parameters. Additionally, something like the code below isn't ideal (if it were implemented correctly somehow) as it only accepts boolean arguments:

object Container {
  def apply(args: Boolean*) = {
    // doesn't REALLY work since number of arguments not enforced
    Container(args map { if (_) 1L else 0L } toArray: _*)
  }
}
val c1 = Container(1, 0, 1) // works
val c2 = Container(true, false, true) // might be workable if done correctly
val c3 = Container(true, 0, 1) // won't work

I tried adding an implicit conversion in the companion object (below), hoping that it would automatically be used in Container.apply, but it appears that this does not actually put the implicit conversion into the namespace of the code that calls apply.

object Container {
  implicit def booleanToLong(x: Boolean): Long = if (x) 1L else 0L
}

I'm able to get things working using this hackish workaround:

{
  import Container.booleanToLong
  // all of these now work
  val c1 = Container(1, 0, 1)
  val c2 = Container(true, false, true)
  val c3 = Container(true, 0, 1) // works!!!
}

The biggest problem is that I have to import booleanToLong into the code that wants to create a Container and thus must put it in its own block for safety (booleanToLong is generally undesirable).

Finally, the solution of using an implicit parameter that itself includes an implicit conversion doesn't work because it would require an explicit overriding of apply, defeating the goal of not repeating a long parameter list and marshaling types.

Is there a way to do this such that I get implicit conversions for free every time I make a Container, but not otherwise? Or is this impossible due to some sort of technical constraint?

like image 768
Ben Sidhom Avatar asked Sep 26 '13 00:09

Ben Sidhom


People also ask

What are implicit conversions?

An implicit conversion sequence is the sequence of conversions required to convert an argument in a function call to the type of the corresponding parameter in a function declaration. The compiler tries to determine an implicit conversion sequence for each argument.

How do you use implicit conversions?

An implicit conversion from type S to type T is defined by an implicit value which has function type S => T , or by an implicit method convertible to a value of that type. Implicit conversions are applied in two situations: If an expression e is of type S , and S does not conform to the expression's expected type T .

How do you avoid implicit conversions in C++?

Keyword explicit tells compiler to not use the constructor for implicit conversion. For example declaring Bar's constructor explicit as - explicit Bar(int i); - would prevent us from calling ProcessBar as - ProcessBar(10); .

What is implicit conversion in Scala?

Implicit conversions in Scala are the set of methods that are apply when an object of wrong type is used. It allows the compiler to automatically convert of one type to another. Implicit conversions are applied in two conditions: First, if an expression of type A and S does not match to the expected expression type B.


1 Answers

You can use a kind of variant of the magnet pattern to make this a little safer. First for a type class:

trait ToLong[A] {
  def apply(a: A): Long
}

implicit object longToLong extends ToLong[Long] {
  def apply(l: Long) = l
}

implicit object booleanToLong extends ToLong[Boolean] {
  def apply(b: Boolean) = if (b) 1L else 0L
}

And now we just need one extra constructor:

case class Container(a: Long, b: Long, c: Long)

object Container {
  def apply[A: ToLong, B: ToLong, C: ToLong](a: A, b: B, c: C) = new Container(
    implicitly[ToLong[A]].apply(a),
    implicitly[ToLong[B]].apply(b),
    implicitly[ToLong[C]].apply(c)
  )
}

And we can write the following:

val c1 = Container(1, 0, 1)
val c2 = Container(true, false, true)
val c3 = Container(true, 0L, 1L)

Without having to introduce the rather scary general conversion from Boolean to Long.

like image 117
Travis Brown Avatar answered Jan 02 '23 12:01

Travis Brown