Scala 3: 类型化元组合并

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

Scala 3: typed tuple zipping

问题

我正在尝试将元组进行压缩,并使用匹配类型获取压缩后的确切类型。我有一个匹配类型和一个函数:

  type Z[A <: Tuple, B <: Tuple] <: Tuple = (A, B) match
    case (EmptyTuple, EmptyTuple) => EmptyTuple
    case (a *: as, b *: bs) => (a, b) *: Z[as, bs]

  def z[A <: Tuple, B <: Tuple](a: A, b: B): Z[A, B] = (a, b) match
    case (EmptyTuple, EmptyTuple) => EmptyTuple
    case (ah *: at, bh *: bt) => (ah, bh) *: z(at, bt)

然而,在z()中的两种情况都会导致错误:
分别是Found: EmptyTuple.type Required: test.Tuples.Z[A, B]Found: (Any, Any) *: test.Tuples.Z[Tuple, Tuple] Required: test.Tuples.Z[A, B]。我本以为这应该是匹配类型的一个相当直接的应用,但显然我错了。我在这里缺少什么?

我也想将匹配类型和函数z()限制为具有相同长度的元组(类似于scala 2中可以使用shapeless的Length),但也许这是一个单独的问题。

编辑:

我设法通过显式转换使函数z()正常工作,但我仍然认为应该有一种方法可以避免这样做:

  def z[A <: Tuple, B <: Tuple, M <: Int](a: A, b: B): Z[A, B] = (a, b) match
    case (ah *: at, bh *: bt) => ((ah, bh) *: z(at, bt)).asInstanceOf[Z[A, B]]
    case (EmptyTuple, EmptyTuple) => EmptyTuple.asInstanceOf[Z[A, B]]

此外,我也成功地让函数z()实现了长度方面的限制,但我很想知道是否有a) 一种更清晰/更简洁的方法来实现这一点(也许不需要定义L)和b) 是否有一种方法来限制类型参数Z为具有相同长度的元组:

  type L[T <: Tuple] <: Int = T match
    case EmptyTuple => 0
    case _ *: t => 1 + L[t]

  type Z[A <: Tuple, B <: Tuple] <: Tuple = (A, B) match
    case (EmptyTuple, EmptyTuple) => EmptyTuple
    case (a *: as, b *: bs) => (a, b) *: Z[as, bs]

  def z[A <: Tuple, B <: Tuple, M <: Int](a: A, b: B)(using L[A] =:= L[B]): Z[A, B] = (a, b) match
    case (ah *: at, bh *: bt) => ((ah, bh) *: z(at, bt)).asInstanceOf[Z[A, B]]
    case (EmptyTuple, EmptyTuple) => EmptyTuple.asInstanceOf[Z[A, B]]

  println(z(1 *: true *: EmptyTuple, "seven" *: 9.8 *: EmptyTuple)) // < -- 正确压缩元组:((1,seven),(true,9.8))

//    println(z(1 *: EmptyTuple, "seven" *: 9.8 *: EmptyTuple)) // < -- 会导致编译时错误,正如所期望的:"Cannot prove that test.Tuples.L[(Int *: EmptyTuple.type)] =:= test.Tuples.L[(String, Double)]..."

编辑2:
实际上事实证明,元组的长度已经限制为相等,否则匹配类型Z将无法解析,所以我想问题只是如何避免在z()中使用强制转换。

英文:

I'm trying to zip tuples together and use match types to get the exact type of the resulting zip. I have a match type and the function:

  type Z[A &lt;: Tuple, B &lt;: Tuple] &lt;: Tuple = (A, B) match
    case (EmptyTuple, EmptyTuple) =&gt; EmptyTuple
    case (a *: as, b *: bs) =&gt; (a, b) *: Z[as, bs]

  def z[A &lt;: Tuple, B &lt;: Tuple](a: A, b: B): Z[A, B] = (a, b) match
    case (EmptyTuple, EmptyTuple) =&gt; EmptyTuple
    case (ah *: at, bh *: bt) =&gt; (ah, bh) *: z(at, bt)

However, both cases in z() result in an error:
Found: EmptyTuple.type Required: test.Tuples.Z[A, B] and Found: (Any, Any) *: test.Tuples.Z[Tuple, Tuple] Required: test.Tuples.Z[A, B], respectively. I would have guessed this would be a pretty straightforward application of match types, but clearly I'm wrong. What am I missing here?

I would also like to restrict both the match type and the function z() to tuples that have the same length (like how shapeless' Length could be used in scala 2), but perhaps that is a separate question.

EDIT:

I managed to get the function z() working with explicit casting, but I still think there has to be a way to avoid that:

  def z[A &lt;: Tuple, B &lt;: Tuple, M &lt;: Int](a: A, b: B): Z[A, B] = (a, b) match
    case (ah *: at, bh *: bt) =&gt; ((ah, bh) *: z(at, bt)).asInstanceOf[Z[A, B]]
    case (EmptyTuple, EmptyTuple) =&gt; EmptyTuple.asInstanceOf[Z[A, B]]

Also, I was able to get the length aspect working for the function z() as well, but I would love to know if a) there is a cleaner/less verbose way to achieve this (perhaps without the need to define L) and b) if there's a way to restrict the type arguments to Z to be tuples of the same length:

  type L[T &lt;: Tuple] &lt;: Int = T match
    case EmptyTuple =&gt; 0
    case _ *: t =&gt; 1 + L[t]

  type Z[A &lt;: Tuple, B &lt;: Tuple] &lt;: Tuple = (A, B) match
    case (EmptyTuple, EmptyTuple) =&gt; EmptyTuple
    case (a *: as, b *: bs) =&gt; (a, b) *: Z[as, bs]

  def z[A &lt;: Tuple, B &lt;: Tuple, M &lt;: Int](a: A, b: B)(using L[A] =:= L[B]): Z[A, B] = (a, b) match
    case (ah *: at, bh *: bt) =&gt; ((ah, bh) *: z(at, bt)).asInstanceOf[Z[A, B]]
    case (EmptyTuple, EmptyTuple) =&gt; EmptyTuple.asInstanceOf[Z[A, B]]

  println(z(1 *: true *: EmptyTuple, &quot;seven&quot; *: 9.8 *: EmptyTuple)) // &lt;-- correctly zips tuples: ((1,seven),(true,9.8))

//    println(z(1 *: EmptyTuple, &quot;seven&quot; *: 9.8 *: EmptyTuple)) // &lt;-- results in compile-time error as desired: &quot;Cannot prove that test.Tuples.L[(Int *: EmptyTuple.type)] =:= test.Tuples.L[(String, Double)]...&quot;

EDIT 2:
So actually it turns out that the tuple lengths are already restricted to be equal, as otherwise the match type Z does not resolve, so I guess the question is just how to avoid the casts in z().

答案1

得分: 2

目前[匹配类型][1] 在值层面上有许多限制

&gt; 仅当满足以下条件时才会对匹配表达式的类型进行特殊模式的类型推断
&gt; 
&gt; 1. 匹配表达式的模式中没有守卫条件
&gt; 2. 匹配表达式的被匹配对象类型是匹配类型的子类型
&gt; 3. 匹配表达式和匹配类型具有相同数量的情况
&gt; 4. 匹配表达式的模式全部是[类型化模式][2]并且这些类型与匹配类型中对应的类型模式是 `=:=`

您的代码

```scala
type Z[A <: Tuple, B <: Tuple] <: Tuple = (A, B) match
  case (EmptyTuple, EmptyTuple) => EmptyTuple
  case (a *: as, b *: bs) => (a, b) *: Z[as, bs]

def z[A <: Tuple, B <: Tuple](a: A, b: B): Z[A, B] = (a, b) match
  case (EmptyTuple, EmptyTuple) => EmptyTuple
  case (ah *: at, bh *: bt) => (ah, bh) *: z(at, bt)

违反了条件 4。case (EmptyTuple, EmptyTuple)case (ah *: at, bh *: bt) 不是类型化模式。

尝试以下修改可能会有意义:

type Z[A <: Tuple, B <: Tuple] <: Tuple = (A, B) match
  case (EmptyTuple, EmptyTuple) => EmptyTuple
  case (a *: as, b *: bs) => (a, b) *: Z[as, bs]

def z[A <: Tuple, B <: Tuple](a: A, b: B): Z[A, B] = (a, b) match
  case _: (EmptyTuple, EmptyTuple) => EmptyTuple
  case t: (_ *: _, _ *: _) => t match
    case (ah *: at, bh *: bt) => (ah, bh) *: z(at, bt)

但不幸的是,这样做不起作用,因为会发生类型擦除。

z((1, "a"), (true, 2.0)) // ()

实际上,case _: (EmptyTuple, EmptyTuple)case t: (_ *: _, _ *: _) 只是 case _: (_, _),会匹配所有情况。如果交换这两个情况,z((1, "a"), (true, 2.0)) 会抛出运行时异常(java.lang.IndexOutOfBoundsException: 0)。

以下使用嵌套的匹配类型/模式匹配的方法似乎可以工作:

type Z[A <: Tuple, B <: Tuple] <: Tuple = A match
  case EmptyTuple => Z1[B]
  case a *: as => Z2[a, as, B]

type Z1[B <: Tuple] <: Tuple = B match
  case EmptyTuple => EmptyTuple

type Z2[A, As <: Tuple, B <: Tuple] <: Tuple = B match
  case b *: bs => (A, b) *: Z[As, bs]

def z[A <: Tuple, B <: Tuple](a: A, b: B): Z[A, B] = a match
  case _: EmptyTuple => z1(b)
  case a1: (_ *: _) => a1 match
    case a2 *: as => z2(a2, as, b)

def z1[B <: Tuple](b: B): Z1[B] = b match
  case _: EmptyTuple => EmptyTuple

def z2[A, As <: Tuple, B <: Tuple](a: A, as: As, b: B): Z2[A, As, B] = b match
  case b1: (_ *: _) => b1 match
    case b2 *: bs => (a, b2) *: z(as, bs)

// 编译通过
summon[Z[(Int, String), (Boolean, Double)] =:= ((Int, Boolean), (String, Double))]

z((1, "a"), (true, 2.0)) // ((1,true),(a,2.0))

// 无法编译
// summon[Z[(Int, String, Long), (Boolean, Double)] =:= ((Int, Boolean), (String, Double))]

// 无法编译
// summon[Z[(Int, String), (Boolean, Double, Long)] =:= ((Int, Boolean), (String, Double))]

原帖链接

相关链接


<details>
<summary>英文:</summary>
Currently [match types][1] have many limitations on value level:
&gt; This special mode of typing for match expressions is only used when
&gt; the following conditions are met:
&gt; 
&gt; 1. The match expression patterns do not have guards
&gt; 2. The match expression scrutinee&#39;s type is a subtype of the match type scrutinee&#39;s type
&gt; 3. The match expression and the match type have the same number of cases
&gt; 4. The match expression patterns are all [Typed Patterns][2], and these types are `=:=` to their corresponding type patterns in the match type
Your code

type Z[A <: Tuple, B <: Tuple] <: Tuple = (A, B) match
case (EmptyTuple, EmptyTuple) => EmptyTuple
case (a *: as, b *: bs) => (a, b) *: Z[as, bs]

def z[A <: Tuple, B <: Tuple](a: A, b: B): Z[A, B] = (a, b) match
case (EmptyTuple, EmptyTuple) => EmptyTuple
case (ah *: at, bh *: bt) => (ah, bh) *: z(at, bt)

breaks condition 4. `case (EmptyTuple, EmptyTuple)` and `case (ah *: at, bh *: bt)` are not typed patterns.
It would make sense to try

type Z[A <: Tuple, B <: Tuple] <: Tuple = (A, B) match
case (EmptyTuple, EmptyTuple) => EmptyTuple
case (a *: as, b *: bs) => (a, b) *: Z[as, bs]

def z[A <: Tuple, B <: Tuple](a: A, b: B): Z[A, B] = (a, b) match
case : (EmptyTuple, EmptyTuple) => EmptyTuple
case t: (
*: _, _ *: _) => t match
case (ah *: at, bh *: bt) => (ah, bh) *: z(at, bt)


but unfortunately this doesn&#39;t work because of type erasure

z((1, "a"), (true, 2.0)) // ()

Actually, `case _: (EmptyTuple, EmptyTuple)` and `case t: (_ *: _, _ *: _)` are just `case _: (_, _)` and match everything. And if we swap the cases then `z((1, &quot;a&quot;), (true, 2.0))` throws runtime exception (`java.lang.IndexOutOfBoundsException: 0`).
The following approach with nested match types/pattern matchings seems to work

type Z[A <: Tuple, B <: Tuple] <: Tuple = A match
case EmptyTuple => Z1[B]
case a *: as => Z2[a, as, B]

type Z1[B <: Tuple] <: Tuple = B match
case EmptyTuple => EmptyTuple

type Z2[A, As <: Tuple, B <: Tuple] <: Tuple = B match
case b *: bs => (A, b) *: Z[As, bs]

def z[A <: Tuple, B <: Tuple](a: A, b: B): Z[A, B] = a match
case : EmptyTuple => z1(b)
case a1: (
*: _) => a1 match
case a2 *: as => z2(a2, as, b)

def z1[B <: Tuple](b: B): Z1[B] = b match
case _: EmptyTuple => EmptyTuple

def z2[A, As <: Tuple, B <: Tuple](a: A, as: As, b: B): Z2[A, As, B] = b match
case b1: (_ *: _) => b1 match
case b2 *: bs => (a, b2) *: z(as, bs)

// compiles
summon[Z[(Int, String), (Boolean, Double)] =:= ((Int, Boolean), (String, Double))]

z((1, "a"), (true, 2.0)) // ((1,true),(a,2.0))

// doesn't compile
// summon[Z[(Int, String, Long), (Boolean, Double)] =:= ((Int, Boolean), (String, Double))]
// z((1, "a", 3L), (true, 2.0))
// Match type reduction failed since selector EmptyTuple.type
// matches none of the cases
// case b *: bs => (Long, b) *: Z[EmptyTuple.type, bs]

// doesn't compile
// summon[Z[(Int, String), (Boolean, Double, Long)] =:= ((Int, Boolean), (String, Double))]
// z((1, "a"), (true, 2.0, 3L))
// Match type reduction failed since selector Long *: EmptyTuple.type
// matches none of the cases
// case EmptyTuple => EmptyTuple


https://stackoverflow.com/questions/64434175/how-to-get-match-type-to-work-correctly-in-scala-3
https://stackoverflow.com/questions/74355212/shapeless3-and-annotations
[1]: https://docs.scala-lang.org/scala3/reference/new-types/match-types.html
[2]: https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#typed-patterns
</details>

huangapple
  • 本文由 发表于 2023年2月6日 04:48:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75355407.html
匿名

发表评论

匿名网友

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

确定