为什么for推导式会扩展到`withFilter`?

7
我正在为关系(类似于SQL的)操作制作DSL。我有一个带有.apply:((Symbol,...)) => Obj方法的Rep[Table]类型,该方法返回一个定义了.flatMap:T1 => T2.map:T1 => T3函数的对象Obj。由于Rep[Table]类型不知道底层表的模式,因此apply方法就像是一个投影-仅投影在参数元组中指定的字段(很像无类型Scalding API)。现在类型T1类似于“元组”,其长度由一些shapeless魔法限制为投影元组的长度,但是元组元素的类型由api用户决定,因此代码如下:
val a = loadTable(...)
val res = a(('x, 'y)).map { (t: Row2[Int, Int]) =>
  (1, t(0))
}

或者
val res = a(('x, 'y)).map { (t: Row2[String, String]) =>
  (1, t(0))
}

一切正常。请注意,必须显式指定map/flatMap函数的参数类型。然而,当我尝试在for循环中使用它时:

val a = loadTable(...)
val b = loadTable(...)
val c = loadTable(...)

val res = for {
  as: Row2[Int, Int] <- a(('ax, 'ay))
  bs: Row2[Int, Int] <- b(('bx, 'by))
  cs: Row2[Int, Int] <- c(('cx, 'cy))
} yield (as(0), as(1), bs(0), bs(1), cs(0), cs(1))

IT在缺少withFilter运算符时报错。添加.withFilter:T1 => Boolean并不起作用-它会抱怨“扩展函数缺少参数类型”,因为T1由某些类型参数化。只有添加.withFilter: Row[Int,Int] => Boolean才能使其正常工作,但这显然不是我想要的。

我的问题是:为什么首先要调用withFilter,以及如何在我的参数化元组类型T1中使用它?


编辑 最终,我选择了一个.withFilter:NothingLike => BoolLike,对于简单检查(如_.isInstanceOf[T1])是无操作的,对于一般情况则使用更受限制的.filter:T1 => BoolLike


我一直以为 withFilter 是在 for 推导式中针对单子类型的,用于包含一个守卫条件(类似于 for 推导式中的 if),但我可能是错的。 - cmbaxter
我们俩一样。 - Pyetras
我认为你在左侧的类型注释使它调用withFilter以确保结果是正确的类型。 - Daenyth
@Daenyth 是的,那正是正在发生的事情。不幸的是,原作者希望这种类型能够传播到 map 方法中,以便它能够正确地执行。 - gzm0
2个回答

15

很遗憾,当您期望基于lambda参数类型进行类型推断时,无法使用for-comprehensions。

实际上,在您的示例中,as: Row2[Int, Int]被解释为模式匹配:

val res = for {
  as: Row2[Int, Int] <- a(('ax, 'ay))
} yield (...)

Translates to something like:

a(('ax, 'ay)).withFilter(_.isInstanceOf[Row2[Int, Int]]).map(...)

在for推导式中进行模式匹配非常有用:

val names = for {
  ("name", name) <- keyValPairs
} yield name

但这种折衷方式的结果是,您无法显式指定lambda的参数类型。


好的,谢谢,这很有启示性。所以我可能可以通过类似 withFilter(t: Any => Boolean) 的东西来解决它,但是然后我会得到“未经检查的类型擦除”警告,这可能是来自 isInstanceOf 调用吗?withFilter(t: Nothing => Boolean) 可以正常工作,但当我尝试将其扩展以实际执行一些有用的操作时,似乎可能会遇到麻烦... - Pyetras
咳咳... withFilter(t:Nothing => Boolean)几乎肯定不是您想要的。虽然您可以使用返回Boolean的任何Function1调用withFilter,但您无法在该方法内调用该函数(因为没有类型为Nothing的值)。 - gzm0
稍微澄清一下我最终得到的结果:在我的情况下,该函数返回的是一个抽象语法树而不是具体值,其签名更像是 Rep[Nothing] => Rep[Boolean],其中 Rep 类型是协变的,调用也可以正常工作。 - Pyetras

2

我也遇到了这个问题。感谢gzm0解释了Scala编译器的行为,我想出了这个解决方法:

import cats._
import cats.data._
import cats.implicits._

object CatsNEL extends App {
  val nss: NonEmptyList[(Int, String)] = NonEmptyList.of((1,"a"), (2, "b"), (3, "c"))
  val ss: NonEmptyList[String] = for {
    tuple <- nss
    (n, s) = tuple
  } yield s
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接