Eduardo
Eduardo

Reputation: 8412

How to give the Scala compiler evidence that a collection has elements of the correct types?

I would like to do something like the following:

val foo = List[B <% JValue] = 42 :: "hello" : Nil

for the compiler to know that the members of my list can be converted to JValues.

However, this does not compile. I cannot settle for a List[Any] because I have to use its members where values that can be converted into JValues are expected, say in:

def fun[A <% JValue](x: List[A]) = ...

Is there any way to solve this?

Upvotes: 1

Views: 152

Answers (3)

senia
senia

Reputation: 38045

A little improvement of |: method from Luigi Plinge's answer:

You could write

val foo: List[JValue] = 42 :: "hello" :: HNil

with an appropriate implicit conversion (using shapeless):

import shapeless._

trait HListTConv[H <: HList, T] {
  def apply(l: List[T], hl: H): List[T]
}

object HListTConv {
  implicit def apply0[T] = new HListTConv[HNil, T] {
    def apply(l: List[T], hl: HNil): List[T] = l
  }

  implicit def applyN[Head, Tail <: HList, T](implicit c: HListTConv[Tail, T], conv: Head => T) =
    new HListTConv[Head :: Tail, T] {
      def apply(l: List[T], hl: Head :: Tail): List[T] = (hl.head: T) :: c(l, hl.tail)
    }
}

implicit def hListToJValueList[H <: HList](hl: H)(implicit c: HListTConv[H, JValue]): List[JValue] = c(Nil, hl)

Test:

case class Test(s: String)
implicit def intToTest(i: Int): Test = Test(i.toString)
implicit def strToTest(s: String): Test = Test(s)

implicit def hListToListTest[H <: HList](hl: H)(implicit c: HListTConv[H, Test]): List[Test] = c(Nil, hl)

scala> val foo: List[Test] = 42 :: "hello" :: HNil
foo: List[Test] = List(Test(42), Test(hello))

Upvotes: 2

Luigi Plinge
Luigi Plinge

Reputation: 51109

You can write

val foo: List[JValue] = List(42, "hello")

If there is no implicit conversion from these types to JValue then it won't type check.

Unfortunately you can't write it as 42 :: "hello" :: Nil because the compiler isn't smart enough to know that you want to apply the conversions on each term (you could add a type annotation on each term, but that's messy). Each :: method would have to somehow look ahead all the way to the end of the expression to check if some later method made it fit the type parameter.

However, if you want to add your own funky operator, you can constrain the types allowed by :::

implicit class pimp(xs: List[JValue]) {
  def |: (x: JValue) = x :: xs
}

after which you can write stuff like this:

val foo = 42 |: "hello" |: Nil
  // type is List[JValue]

(I tried parameterizing this so that it would infer the most specific common type, as :: does, but with an upper bound the compiler didn't want to play ball - see here. Maybe someone with more Scala-fu can fix it, if it's possible.)

Upvotes: 5

jroesch
jroesch

Reputation: 1260

You can enable the conversions with a simple set of implicits.

class JValue
implicit intToJValue(x: Int) = new JValue
implicit stringToJValue(x: String) = new JValue

val xs: List[JValue] = List(1, "hello")

For your second question you can enable wholesale list conversion with:

implicit def listToJList[A <% JValue](xs: List[A]): List[JValue] = xs
def foo[A <% JValue](x: List[A]): List[JValue] = x

This above example only works if you have a uniform type, otherwise you will need to employ a more sophisticated means, a list of heterogeneous types will unify to List[Any] in most cases.

You could come up more elegant/complicated solutions using shapeless, most employing shapless.Poly and HList's.

Upvotes: 0

Related Questions