scala 3 将元组映射到元组类型的 future 并返回

问题描述 投票:0回答:1

我正在尝试获取任意

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) => // 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)

但我收到错误:

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)带有警告:

  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) // doesn't compile without [(String, String)] 

斯卡斯蒂这里

scala pattern-matching matching scala-3 match-types
1个回答
2
投票

在匹配类型中,

case Tuple.Map[fh *: ft, Future]
是有意义的。但在模式匹配中,由于类型擦除,
case fs: Tuple.Map[fh *: ft, Future]
只是
case fs: Tuple.Map[_, _]

目前在价值层面匹配类型效果不太好(很多事情无法推断)。好的旧类型课程可以更好。

我猜你的意思是

Await.result
而不是不存在
Future.get

尝试使方法内联并在需要的地方添加隐式提示

summonFrom { _: some_evidence => ... }

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)
                    }
                }
            }
        }

测试:

getAll[(Int, String)](Future(1), Future("a"))(5000, MILLISECONDS) // (1,a)

也许用

getAll
来定义
Tuple.InverseMap
更好(根本不用
Tuple.Map

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)
            }
        }

测试:

getAll(Future(1), Future("a"))(5000, MILLISECONDS) // (1,a)

现在您不需要在调用站点指定

getAll
的类型参数。


更容易的是在类型级别(数学类型)和值级别(模式匹配)上递归地定义

getAll
。那么你就不需要隐式提示了

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)

请注意,如果将

GetAll
的递归定义替换为

type GetAll[T <: Tuple] = Tuple.InverseMap[T, Future]

代码停止编译。您必须再次添加隐式提示。


我提醒您比赛类型的规则

这种匹配表达式的特殊输入模式仅在满足以下条件时使用:

  1. 匹配表达式模式没有防护
  2. 匹配表达式受审者类型是匹配类型受审者类型的子类型
  3. 匹配表达式和匹配类型具有相同的事例数
  4. 匹配表达式模式都是 Typed Patterns,这些类型是
    =:=
    到它们在匹配中对应的类型模式 类型

如果我们专门化类型参数并引入类型别名,编译器似乎无法识别伴随模式匹配定义的匹配类型定义:

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

编译并

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

确实并且

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

但是

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

没有(Scala 3.2.2)。案例的顺序也很重要:

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

无法编译。

所以最简单的实现是

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

这就是

Tuple.InverseMap
定义中的情况顺序 https://github.com/lampepfl/dotty/blob/3.2.2/library/src/scala/Tuple.scala#L184-L187


另请参阅

Scala 3:类型化元组压缩

在 vanilla Scala 3 中表达任意数量的函数

© www.soinside.com 2019 - 2024. All rights reserved.