Jake
Jake

Reputation: 757

constructing empty value of type that implements Seq

I have a trait like the following

private class SeqConverter[T](implicit val conv : Converter[T]) extends Converter[Seq[T]] {
    def toJs(x: Seq[T]): JsAny = {
      x.foldLeft(JsArray[JsAny]()) { (acc, next) =>
        acc.+:(conv.toJs(next)) 
      }
    }
    def toScala(x: JsAny): Seq[T] = {
      val arr = x.asInstanceOf[JsArray[JsObject]]
      var lst = List[T]()
      arr foreach { x =>
        lst = conv.toScala(x) :: lst 
      }
      lst
    }
  }

I want to have something more like this

private class SeqConverter[T, F <: Seq[T]](implicit val conv : Converter[T]) extends Converter[F] {
    def toJs(x: F): JsAny = {
      x.foldLeft(JsArray[JsAny]()) { (acc, next) =>
        acc.+:(conv.toJs(next)) 
      }
    }
    def toScala(x: JsAny): Seq[T] = {
      //need to construct empty F here to build it
    }
  }

But the problem is I have no way to get a member of F in order to start preforming construction of it. Is there any way I could get this to exist? It seems like there should be some kind of way to construct an empty member of F so so that I could use +: in order to convert from 1 kind of sequence to another. Does anything like that exist?

Upvotes: 2

Views: 205

Answers (1)

Erik Kaplun
Erik Kaplun

Reputation: 38217

UPDATE: if you want to avoid depending on Scalaz, you can define your own type class and instances thereof:

import scala.language.higherKinds

trait Coll[TS[_], T] {
  def zero: TS[T]
  def append(a: TS[T], b: TS[T]): TS[T]
  def point(x: T): TS[T]
}

object Coll {
  implicit def listOfTIsColl[T] = new Coll[List, T] {
    def zero = Nil
    def append(a: List[T], b: List[T]) = a ++ b
    def point(x: T) = List(x)
  }

  implicit def vectorOfTIsColl[T] = new Coll[Vector, T] {
    def zero = Vector.empty
    def append(a: Vector[T], b: Vector[T]) = a ++ b
    def point(x: T) = Vector(x)
  }
}

def foo[T, U, TS[_]](xs: TS[T], x: U)(implicit
  coll: Coll[TS, T],
  ev1:  TS[T] <:< Seq[T],
  ev2:  U =:= T
) = {
  (coll.zero, coll.append(coll.zero, coll.point(x)))
}

assert(foo(Vector(1, 2, 3), 4) == (Vector(), Vector(4)))
// foo(Vector(1, 2, 3), 4.4)     -- error: Cannot prove that Double =:= Int
// foo(Vector(1, 2, 3), "hello") -- error: Cannot prove that String =:= Int

Note that it's necessary for T and U to be separate type parameters; with def foo[T, TS[_]](xs: TS[T], x: T) ..., you'd be able to use foo as expected, but things like foo(Vector(1, 2, 3), "hello") would work and the type inferencer would infer a type like Vector[Any]. However, with the above, stricter definition of foo, this won't be allowed, which is, at least in idiomatic functional code, desirable.

Scalaz based solution: Scalaz Monoid and Applicative will help you:

import scalaz._
import Scalaz._

scala> Monoid[List[Int]].zero
res0: List[Int] = List()

scala> Monoid[Vector[Int]].zero
res1: Vector[Int] = Vector()

scala> Monoid[Vector[Int]].append(Vector(1, 2), Vector(3, 4))
res2: Vector[Int] = Vector(1, 2, 3, 4)

and

scala> Applicative[Vector].point(1)
res0: Vector[Int] = Vector(1)

Then, combining Monoid and Applicative will give you all of zero, append, and point/pure:

def foo[T, TS[_], U](xs: TS[T], x: U)(implicit
  monoid: Monoid[TS[T]],
  applic: Applicative[TS],
  ev1:    TS[T] <:< Seq[T],
  ev2:    U =:= T
) = {
  (monoid.zero, 
   monoid.append(monoid.zero, applic.point(x)))
}

then:

> foo(Vector(1, 2, 3), 4)
res1 = (Vector(),Vector(4))

I'm not confident there aren't any conciser solutions, e.g. one that relies on just one type class, but this one seems to work correctly.

Upvotes: 1

Related Questions