英文:
scala 3 map tuple to futures of tuple types and back
问题
以下是代码的中文翻译部分:
我正在尝试获取一个任意的Future
元组,并返回已完成的Future
值的元组,同时为Future
的完成提供一个时间限制。我试图使用Tuple
提供的Map
匹配类型:
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) => // 在这里,我认为`fh`应该是已知的`Future`,特别是`Head[Map[T, Future]]`,`ft`应该是`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)
但是我遇到了一个错误:
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)
似乎编译器无法确定fh
是Future
。我试图遵循我从以前的一个问题中得到的关于匹配类型的指导,特别是尝试将值模式与匹配类型模式相匹配,但我认为还是漏了点什么。
有什么想法吗?
编辑:
我到了这个至少能够编译并且似乎可以正常运行的版本:
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]
但是:
a) 带有警告:
the type test for (java.util.concurrent.Future[fh] *:
scala.Tuple.Map[ft, java.util.concurrent.Future]
) @ft @fh cannot be checked at runtime
case fs: Tuple.Map[fh *: ft, Future] =>
这是有道理的,我只是不知道如何修复它。我猜如果我能以某种方式产生一个ClassTag[ft]
,但这似乎是不可能的...
b) 使用需要一个类型说明,这使得它不太可用,例如:
getAll[(String, String)]((f1, f2))(to, TimeUnit.SECONDS) // 如果没有[(String, String)],则不会编译
希望这对你有帮助。如果你有其他问题,可以提出。
英文:
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
答案1
得分: 2
In match types case Tuple.Map[fh *: ft, Future]
can make sense. But in pattern matching case fs: Tuple.Map[fh *: ft, Future]
is just case 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.
Try to make the method inline and add implicit hints summonFrom { _: some_evidence => ... }
where needed
import scala.compiletime.summonFrom
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.{*, given}
import scala.concurrent.ExecutionContext.Implicits.given
inline def getAll[T <: Tuple](futures: Tuple.Map[T, Future])(
timeout: Long,
units: TimeUnit
): T = inline futures match
case _: EmptyTuple =>
summonFrom {
case _: (EmptyTuple =:= T) => EmptyTuple
}
case vfs: (fh *: ft) =>
vfs match
case vfh *: vft =>
val start = System.currentTimeMillis()
summonFrom {
case _: (`fh` <:< Future[h]) =>
val vh: h = Await.result(vfh, Duration(timeout, units))
val elapsed = System.currentTimeMillis() - start
val remaining = MILLISECONDS.convert(timeout, units) - elapsed
summonFrom {
case _: (Tuple.InverseMap[`ft`, Future] =:= t) =>
summonFrom {
case _: (`ft` =:= Tuple.Map[`t` & Tuple, Future]) =>
summonFrom {
case _: ((`h` *: `t`) =:= T) =>
vh *: getAll[t & Tuple](vft)(remaining, units)
}
}
}
}
Testing:
getAll[(Int, String)](Future(1), Future("a"))(5000, MILLISECONDS) // (1,a)
Maybe it's better to define getAll
with Tuple.InverseMap
(without Tuple.Map
at all)
inline def getAll[T <: Tuple](futures: T)(
timeout: Long,
units: TimeUnit
): Tuple.InverseMap[T, Future] = inline futures match
case _: EmptyTuple =>
summonFrom {
case _: (EmptyTuple =:= Tuple.InverseMap[T, Future]) => EmptyTuple
}
case vfs: (fh *: ft) =>
vfs match
case vfh *: vft =>
val start = System.currentTimeMillis()
summonFrom {
case _: (`fh` <:< Future[h]) =>
val vh: h = Await.result(vfh, Duration(timeout, units))
val elapsed = System.currentTimeMillis() - start
val remaining = MILLISECONDS.convert(timeout, units) - elapsed
summonFrom {
case _: ((`h` *: Tuple.InverseMap[`ft`, Future]) =:= (Tuple.InverseMap[T, Future])) =>
vh *: getAll[ft](vft)(remaining, units)
}
}
Testing:
getAll(Future(1), Future("a"))(5000, MILLISECONDS) // (1,a)
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 (match types) and value level (pattern matching). Then you don't need implicit hints
type GetAll[T <: Tuple] <: Tuple = T match
case EmptyTuple => EmptyTuple
case Future[h] *: ft => h *: GetAll[ft]
inline def getAll[T <: Tuple](futures: T)(
timeout: Long,
units: TimeUnit
): GetAll[T] = inline futures match
case _: EmptyTuple => EmptyTuple
case vfs: (Future[_] *: ft) =>
vfs match
case vfh *: vft =>
val start = System.currentTimeMillis()
val vh = Await.result(vfh, Duration(timeout, units))
val elapsed = System.currentTimeMillis() - start
val remaining = MILLISECONDS.convert(timeout, units) - elapsed
vh *: getAll[ft](vft)(remaining, units)
Please notice that if you replace the recursive definition of GetAll
with just
type GetAll[T <: Tuple] = Tuple.InverseMap[T, Future]
the code stops to compile. You'll have to add implicit hints again.
I'm reminding you the rules of match types:
This special mode of typing for match expressions is only used when the following conditions are met:
- The match expression patterns do not have guards
- The match expression scrutinee's type is a subtype of the match type scrutinee's type
- The match expression and the match type have the same number of cases
- The match expression patterns are all Typed Patterns, and these types are
=:=
to their corresponding type patterns in the match type
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:
type A[T] = T match
case Int => Double
case String => Boolean
def foo[T](t: T): A[T] = t match
case _: Int => 1.0
case _: String => true
compiles and
type A[T] = T match
case Int => Double
case String => Boolean
type B[T] = A[T]
def foo[T](t: T): B[T] = t match
case _: Int => 1.0
case _: String => true
does and
type A[T, F[_]] = T match
case Int => Double
case String => Boolean
def foo[T](t: T): A[T, Option] = t match
case _: Int => 1.0
case _: String => true
does but
type A[T, F[_]] = T match
case Int => Double
case String => Boolean
type B[T] = A[T, Option]
def foo[T](t: T): B[T] = t match
case _: Int => 1.0
case _: String => true
doesn't (Scala 3.2.2). Also the order of cases is significant:
type A[T] = T match
case Int => Double
case String => Boolean
def foo[T](t: T): A[T] = t match
case _: String => true
case _: Int => 1.0
doesn't
英文:
In match types case Tuple.Map[fh *: ft, Future]
can make sense. But in pattern matching case fs: Tuple.Map[fh *: ft, Future]
is just case 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.
<strike>I guess you meant Await.result
instead of not existing Future.get
.</strike>
Try to make the method inline and add implicit hints summonFrom { _: some_evidence => ... }
where needed
import scala.compiletime.summonFrom
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.{*, given}
import scala.concurrent.ExecutionContext.Implicits.given
inline def getAll[T <: Tuple](futures: Tuple.Map[T, Future])(
timeout: Long,
units: TimeUnit
): T = inline futures match
case _: EmptyTuple =>
summonFrom {
case _: (EmptyTuple =:= T) => EmptyTuple
}
case vfs: (fh *: ft) =>
vfs match
case vfh *: vft =>
val start = System.currentTimeMillis()
summonFrom {
case _: (`fh` <:< Future[h]) =>
val vh: h = Await.result(vfh, Duration(timeout, units))
val elapsed = System.currentTimeMillis() - start
val remaining = MILLISECONDS.convert(timeout, units) - elapsed
summonFrom {
case _: (Tuple.InverseMap[`ft`, Future] =:= t) =>
summonFrom {
case _: (`ft` =:= Tuple.Map[`t` & Tuple, Future]) =>
summonFrom {
case _: ((`h` *: `t`) =:= T) =>
vh *: getAll[t & Tuple](vft)(remaining, units)
}
}
}
}
Testing:
getAll[(Int, String)](Future(1), Future("a"))(5000, MILLISECONDS) // (1,a)
Maybe it's better to define getAll
with Tuple.InverseMap
(without Tuple.Map
at all)
inline def getAll[T <: Tuple](futures: T)(
timeout: Long,
units: TimeUnit
): Tuple.InverseMap[T, Future] = inline futures match
case _: EmptyTuple =>
summonFrom {
case _: (EmptyTuple =:= Tuple.InverseMap[T, Future]) => EmptyTuple
}
case vfs: (fh *: ft) =>
vfs match
case vfh *: vft =>
val start = System.currentTimeMillis()
summonFrom {
case _: (`fh` <:< Future[h]) =>
val vh: h = Await.result(vfh, Duration(timeout, units))
val elapsed = System.currentTimeMillis() - start
val remaining = MILLISECONDS.convert(timeout, units) - elapsed
summonFrom {
case _: ((`h` *: Tuple.InverseMap[`ft`, Future]) =:= (Tuple.InverseMap[T, Future])) =>
vh *: getAll[ft](vft)(remaining, units)
}
}
Testing:
getAll(Future(1), Future("a"))(5000, MILLISECONDS) // (1,a)
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 hints
type GetAll[T <: Tuple] <: Tuple = T match
case EmptyTuple => EmptyTuple
case Future[h] *: ft => h *: GetAll[ft]
inline def getAll[T <: Tuple](futures: T)(
timeout: Long,
units: TimeUnit
): GetAll[T] = inline futures match
case _: EmptyTuple => EmptyTuple
case vfs: (Future[_] *: ft) =>
vfs match
case vfh *: vft =>
val start = System.currentTimeMillis()
val vh = Await.result(vfh, Duration(timeout, units))
val elapsed = System.currentTimeMillis() - start
val remaining = MILLISECONDS.convert(timeout, units) - elapsed
vh *: getAll[ft](vft)(remaining, units)
Please notice that if you replace the recursive definition of GetAll
with just
type GetAll[T <: Tuple] = Tuple.InverseMap[T, Future]
the code stops to compile. You'll have to add implicit hints again.
I'm reminding you the rules of match types:
> This special mode of typing for match expressions is only used when the following conditions are met:
>
> 1. The match expression patterns do not have guards
> 2. The match expression scrutinee's type is a subtype of the match type scrutinee's type
> 3. The match expression and the match type have the same number of cases
> 4. The match expression patterns are all Typed Patterns, and these types are =:=
to their corresponding type patterns in the match
> type
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:
type A[T] = T match
case Int => Double
case String => Boolean
def foo[T](t: T): A[T] = t match
case _: Int => 1.0
case _: String => true
compiles and
type A[T] = T match
case Int => Double
case String => Boolean
type B[T] = A[T]
def foo[T](t: T): B[T] = t match
case _: Int => 1.0
case _: String => true
does and
type A[T, F[_]] = T match
case Int => Double
case String => Boolean
def foo[T](t: T): A[T, Option] = t match
case _: Int => 1.0
case _: String => true
does but
type A[T, F[_]] = T match
case Int => Double
case String => Boolean
type B[T] = A[T, Option]
def foo[T](t: T): B[T] = t match
case _: Int => 1.0
case _: String => true
doesn't (Scala 3.2.2). Also the order of cases is significant:
type A[T] = T match
case Int => Double
case String => Boolean
def foo[T](t: T): A[T] = t match
case _: String => true
case _: Int => 1.0
doesn't compile.
So the easiest implementation is
inline def getAll[T <: Tuple](futures: T)(
timeout: Long,
units: TimeUnit
): Tuple.InverseMap[T, Future] = inline futures match
case vfs: (Future[_] *: ft) =>
vfs match
case vfh *: vft =>
val start = System.currentTimeMillis()
val vh = Await.result(vfh, Duration(timeout, units))
val elapsed = System.currentTimeMillis() - start
val remaining = MILLISECONDS.convert(timeout, units) - elapsed
vh *: getAll[ft](vft)(remaining, units)
case _: EmptyTuple => EmptyTuple
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-L187
See also
https://stackoverflow.com/questions/75355407/scala-3-typed-tuple-zipping
https://stackoverflow.com/questions/75418623/express-function-of-arbitrary-arity-in-vanilla-scala-3
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论