I'm trying to take an arbitrary tuple of Future
s and return a tuple of the completed future's values, while providing a time limit for the completion of the futures. I'm trying to use Tuple
's provided Map
match type:
def getAll[T <: Tuple](futures: Tuple.Map[T, Future])(timeout: Long, units: TimeUnit): T = futures match
case e: EmptyTuple => EmptyTuple.asInstanceOf[T]
case fs: (fh *: ft) => // here, I'd think `fh` would be known to be a Future, specifically Head[Map[T, Future]], and `ft` a Map[Tail[T], Future]
val start = System.currentTimeMillis()
val vh = fs.head.asInstanceOf[fh].get(timeout, units)
val elapsed = System.currentTimeMillis() - start
val remaining = TimeUnit.MILLISECONDS.convert(timeout, units) - elapsed
vh *: getAll(fs.tail)(remaining)
But I'm getting an error:
value get is not a member of fh
where: fh is a type in method getAll with bounds
val vh = fs.head.asInstanceOf[fh].get(timeout, units)
It seems like the compiler can't tell that fh
is a Future
. I'm trying to follow guidance on match types I got from a previous question of mine, in particular trying to match the value patterns with the match type patterns, but I guess am still missing something.
Any ideas?
EDIT: I got to this version that at least compiles and seems to run properly:
def getAll[T <: Tuple](futures: Tuple.Map[T, Future])(timeout: Long, units: TimeUnit): T = futures match
case _: EmptyTuple => EmptyTuple.asInstanceOf[T]
case fs: Tuple.Map[fh *: ft, Future] =>
val start = System.nanoTime()
val vh = fs.head.asInstanceOf[Future[fh]].get(timeout, units)
val elapsed = System.nanoTime() - start
val remaining = TimeUnit.NANOSECONDS.convert(timeout, units) - elapsed
(vh *: getAll(fs.tail)(remaining, TimeUnit.NANOSECONDS)).asInstanceOf[T]
but a) with the warning:
scala.Tuple.Map[ft, java.util.concurrent.Future]
) @ft @fh cannot be checked at runtime
case fs: Tuple.Map[fh *: ft, Future] =>
which makes sense, I just don't know how to fix it. I'm guessing if I could somehow conjure a ClassTag[ft]
, but that doesn't seem possible...
and b) the usage needs a type ascription, which makes it much less useable, for example:
getAll[(String, String)]((f1, f2))(to, TimeUnit.SECONDS) // doesn't compile without [(String, String)]
Scastie here
In match types
case Tuple.Map[fh *: ft, Future]
can make sense. But in pattern matchingcase fs: Tuple.Map[fh *: ft, Future]
is justcase fs: Tuple.Map[_, _]
because of type erasure.Currently on value level match types work not so well (many things can't be inferred). Good old type classes can be better.
I guess you meantAwait.result
instead of not existingFuture.get
.Try to make the method inline and add implicit hints
summonFrom { _: some_evidence => ... }
where neededTesting:
Maybe it's better to define
getAll
withTuple.InverseMap
(withoutTuple.Map
at all)Testing:
Now you don't need to specify the type parameter of
getAll
at the call site.Easier would be to define
getAll
recursively both on type level (math types) and value level (pattern matching). Then you don't need implicit hintsPlease notice that if you replace the recursive definition of
GetAll
with justthe code stops to compile. You'll have to add implicit hints again.
I'm reminding you the rules of match types:
The compiler seems not to recognize the match-type definition accompanying a pattern-match definition if we specialize a type parameter along with introducing a type alias:
compiles and
does and
does but
doesn't (Scala 3.2.2). Also the order of cases is significant:
doesn't compile.
So the easiest implementation is
That's the order of cases as in the definition of
Tuple.InverseMap
https://github.com/lampepfl/dotty/blob/3.2.2/library/src/scala/Tuple.scala#L184-L187See also
Scala 3: typed tuple zipping
Express function of arbitrary arity in vanilla Scala 3