0
votes

Suppose I am writing library code that should be easy to extend and to use without verbose syntax. It seems like implicit conversions can be used to avoid verbosity, as in the Scala Collections library, but I am struggling at applying it to traversables as follows.

I have a trait:

trait Wrapped[T]
{
  def value : T
}

Then I have the class Foo, a key class in the library. Foos are constructed with a list of anything that is Wrapped.

case class Foo[T <: Wrapped[_]](lst : Traversable[T]) {
  override def toString = lst mkString " " 
}

A common use case would be wrapping an Int so I provide a WrappedInt class:

case class WrappedInt(value : Int) extends Wrapped[Int]

With this code, I can make a Foo like this:

val wrappedInts = Seq(1, 2, 3, 4) map { new WrappedInt(_) }
val someFoo = Foo(wrappedInts)

I do not like the extra code to wrap here. I would like the following code to be equivalent:

val foo = Foo(Seq(1, 2, 3, 4)) //should be a Foo[WrappedInt] - gives error

I define the following implicit:

object Conversions {
  implicit def intsToWrapped(lst : Traversable[Int]) = lst map { new WrappedInt(_) }
}

However, it still doesn't work, my val foo has a compilation error after changing the Foo constructor parameter to implicit lst. The Scala book says that an implicit conversion essentially allows x + y to be changed to convert(x) + y, where convert is an implicit conversion. It seems to me like I have the same exact case, one conversion of one parameter here is enough. I verify by doing this:

val foo = Foo(Conversions.intsToWrapped(Seq(1, 2, 3, 4)))

Why is my implicit not being applied? And is there a different, more idiomatic way in current Scala to let Foo be constructed with less code?

EDIT: Adding import Conversions._ does not help and should, if I understand correctly, not be necessary because this example is in one file.

Specific compiler errors I get are these:

val foo = Foo(Seq(1, 2, 3, 4))


inferred type arguments [Int] do not conform to method apply's type parameter bounds [T <: Wrapped[_]]
type mismatch; found : Seq[Int] required: Traversable[T]

Specifying the type to help with type inference, like this:

val foo = Foo[WrappedInt](Seq(1, 2, 3, 4))

gives a message for each int like

type mismatch; found : Int(1) required: WrappedInt
1
import Conversions._ Also, the parameter on Foos constructor does not need to be implicit, but that does not matter.Dima
@Dima This is an example contained entirely within one file, so I do not think I need the import. Adding it does not help.DUman

1 Answers

2
votes

You can specify an implicit conversion in foo's constructor (what once was a view bound). You have to specify that the collection elements are "viewable" as their wrapped versions, not the collection itself.

trait Wrapped[T] {
  def value : T
}

// you can specify bounds on W if you want to be able to do something specific on the values, e.g. W <: MyClass
case class Foo[T, W](lst : Traversable[T])(implicit asWrapped: T => Wrapped[W]) {
  override def toString = lst.map(_.value) mkString " "
}

case class WrappedInt(value : Int) extends Wrapped[Int]

// This is your implicit conversion from Int to WrappedInt
implicit def intToWrapped(x : Int): WrappedInt = WrappedInt(x)

val wrappedInts = Seq(1, 2, 3, 4) map { new WrappedInt(_) }
val someFoo = Foo(wrappedInts)
val foo = Foo(Traversable(1, 2, 3, 4))