Frage

The Problem

I have two classes that look like the following:

class Now {
  def do[A](f: Int => A): Seq[A]
}

class Later {
  def do[A](f: Int => A): Future[Seq[A]]
}

The only difference between the two classes is that Now returns a Seq and Later returns a Future Seq. I would like these two classes to share the same interface

What I Have Tried

This seemed like a perfect fit for higher-kinded types, considering how both Seq and Future[Seq] should only need one type parameter.

trait Do[F[_]] {
  def do[A](f: Int => A): F[A]
}

// Compiles
class Now extends Do[Seq] {
  def do[A](f: Int => A): Seq[A]
}

// Does not compile. "type Seq takes type parameters" and
// "scala.concurrent.Future[<error>] takes no type parameters, expected: one"
class Later extends Do[Future[Seq]] {
  def do[A](f: Int => A): Future[Seq[A]]
}

Am I using higher-kinded types incorrectly? Am I supplying Future[Seq] incorrectly? Is there a way to allow Now and Later to share the same interface?

War es hilfreich?

Lösung

You need type composition:

trait Composition[F[_], G[_]] { type T[A] = F[G[A]] }

class Later extends Do[Composition[Future, Seq]#T] {
  def do[A](f: Int => A): Future[Seq[A]]
}

Or if you just need it in this one place

class Later extends Do[({ type T[A] = Future[Seq[A]] })#T] {
  def do[A](f: Int => A): Future[Seq[A]]
}

See scalaz (I could have sworn it included general type composition, but apparently not.)

Andere Tipps

I believe you want this:

import scala.language.higherKinds
import scala.concurrent.Future

object Main {
  type Id[A] = A

  trait Do[F[_]] {
    // Notice the return type now contains `Seq`.
    def `do`[A](f: Int => A): F[Seq[A]]
  }

  class Now extends Do[Id] {
    override def `do`[A](f: Int => A): Seq[A] = ???
  }

  class Later extends Do[Future] {
    override def `do`[A](f: Int => A): Future[Seq[A]] = ???
  }
}

But if you want something more general, where the abstract method is fully generic in its return type, then the type composition answer of @AlexeyRomanov is the one you're looking for.

Alexey's solution is very clever and answers the question you made. However, I think you made the from question.

You started with these two interfaces:

class Now {
  def do[A](f: Int => A): Seq[A]
}

class Later {
  def do[A](f: Int => A): Future[Seq[A]]
}

and want to modify Later so it implements this:

trait Do[F[_]] {
  def do[A](f: Int => A): F[A]
}

However, you are losing an opportunity here to abstract away whether something is now or later. You should instead change Do to be like this:

trait Do[F[_]] {
  def do[A](f: Int => A): F[Seq[A]]
}

And change Now to this:

class Now {
  def do[A](f: Int => A): Need[Seq[A]]
}

Here, Need is a Scalaz monad that basically acts like a lazy identity to the object it contains. There are other alternatives in the same vein, but the point is that the only thing you need to know about Future and Need is that they are monads. You treat them the same, and you make the decision to use one or the other elsewhere.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top