将Scala 3中的元组映射到元组类型的Future,然后还原。

huangapple go评论55阅读模式
英文:

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)

似乎编译器无法确定fhFuture。我试图遵循我从以前的一个问题中得到的关于匹配类型的指导,特别是尝试将值模式与匹配类型模式相匹配,但我认为还是漏了点什么。

有什么想法吗?

编辑:
我到了这个至少能够编译并且似乎可以正常运行的版本:

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 Futures 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 &lt;: Tuple](futures: Tuple.Map[T, Future])(timeout: Long, units: TimeUnit): T = futures match
        case e: EmptyTuple =&gt; EmptyTuple.asInstanceOf[T]
        case fs: (fh *: ft) =&gt; // here, I&#39;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 &lt;: Tuple](futures: Tuple.Map[T, Future])(timeout: Long, units: TimeUnit): T = futures match
        case _: EmptyTuple =&gt; EmptyTuple.asInstanceOf[T]
        case fs: Tuple.Map[fh *: ft, Future] =&gt;
            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] =&gt;

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&#39;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:

  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

英文:

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 =&gt; ... } 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 &lt;: Tuple](futures: Tuple.Map[T, Future])(
timeout: Long,
units: TimeUnit
): T = inline futures match
case _: EmptyTuple =&gt;
summonFrom {
case _: (EmptyTuple =:= T) =&gt; EmptyTuple
}
case vfs: (fh *: ft) =&gt;
vfs match
case vfh *: vft =&gt;
val start = System.currentTimeMillis()
summonFrom {
case _: (`fh` &lt;:&lt; Future[h]) =&gt;
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) =&gt;
summonFrom {
case _: (`ft` =:= Tuple.Map[`t` &amp; Tuple, Future]) =&gt;
summonFrom {
case _: ((`h` *: `t`) =:= T) =&gt;
vh *: getAll[t &amp; Tuple](vft)(remaining, units)
}
}
}
}

Testing:

getAll[(Int, String)](Future(1), Future(&quot;a&quot;))(5000, MILLISECONDS) // (1,a)

Maybe it's better to define getAll with Tuple.InverseMap (without Tuple.Map at all)

inline def getAll[T &lt;: Tuple](futures: T)(
timeout: Long,
units: TimeUnit
): Tuple.InverseMap[T, Future] = inline futures match
case _: EmptyTuple =&gt;
summonFrom {
case _: (EmptyTuple =:= Tuple.InverseMap[T, Future]) =&gt; EmptyTuple
}
case vfs: (fh *: ft) =&gt;
vfs match
case vfh *: vft =&gt;
val start = System.currentTimeMillis()
summonFrom {
case _: (`fh` &lt;:&lt; Future[h]) =&gt;
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])) =&gt;
vh *: getAll[ft](vft)(remaining, units)
}
}

Testing:

getAll(Future(1), Future(&quot;a&quot;))(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 &lt;: Tuple] &lt;: Tuple = T match
case EmptyTuple =&gt; EmptyTuple
case Future[h] *: ft =&gt; h *: GetAll[ft]
inline def getAll[T &lt;: Tuple](futures: T)(
timeout: Long,
units: TimeUnit
): GetAll[T] = inline futures match
case _: EmptyTuple =&gt; EmptyTuple
case vfs: (Future[_] *: ft) =&gt;
vfs match
case vfh *: vft =&gt;
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 &lt;: 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    =&gt; Double
case String =&gt; Boolean
def foo[T](t: T): A[T] = t match
case _: Int    =&gt; 1.0
case _: String =&gt; true

compiles and

type A[T] = T match
case Int    =&gt; Double
case String =&gt; Boolean
type B[T] = A[T]
def foo[T](t: T): B[T] = t match
case _: Int    =&gt; 1.0
case _: String =&gt; true

does and

type A[T, F[_]] = T match
case Int    =&gt; Double
case String =&gt; Boolean
def foo[T](t: T): A[T, Option] = t match
case _: Int    =&gt; 1.0
case _: String =&gt; true

does but

type A[T, F[_]] = T match
case Int    =&gt; Double
case String =&gt; Boolean
type B[T] = A[T, Option]
def foo[T](t: T): B[T] = t match
case _: Int    =&gt; 1.0
case _: String =&gt; true

doesn't (Scala 3.2.2). Also the order of cases is significant:

type A[T] = T match
case Int    =&gt; Double
case String =&gt; Boolean
def foo[T](t: T): A[T] = t match
case _: String =&gt; true
case _: Int    =&gt; 1.0

doesn't compile.

So the easiest implementation is

inline def getAll[T &lt;: Tuple](futures: T)(
timeout: Long,
units: TimeUnit
): Tuple.InverseMap[T, Future] = inline futures match
case vfs: (Future[_] *: ft) =&gt;
vfs match
case vfh *: vft =&gt;
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 =&gt; 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

huangapple
  • 本文由 发表于 2023年2月16日 12:36:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/75467905.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定