【问题标题】:How to reference parameter of a higher kinded type parameteter?如何引用更高种类的类型参数的参数?
【发布时间】:2019-04-24 19:51:19
【问题描述】:

假设你有这样的特质:

trait Foo[A]{
    def foo: A
}

我想创建一个这样的函数:

def getFoo[A <: Foo[_]](a: A) = a.foo

Scala 编译器为该函数的返回类型推导出 Any。 如何在getFoo 的签名(或正文)中引用匿名参数_? 换句话说,如何取消匿名化参数?

我希望能够使用类似的功能

object ConcreteFoo extends Foo[String] {
  override def foo: String = "hello"
}

val x : String = getFoo(ConcreteFoo)

由于明显的原因导致编译失败,因为 getFoo 被隐式声明为 Any

如果使用 Scala(2.12 版)无法做到这一点,我会对这种限制的理性或技术原因感兴趣。 我确信有关于此的文章和现有问题,但我似乎缺少正确的搜索词。


更新:现有答案准确地回答了我的问题,但我想我对我的实际用例不够准确。对困惑感到抱歉。我希望能够写作

def getFoo[A <: Foo[_]] = (a: A) => a.foo

val f = getFoo[ConcreteFoo.type]

//In some other, unrelated place
val x = f(ConcreteFoo)

因为我没有A类型的参数,如果有,编译器无法推导出参数RA

def getFoo[R, A <: Foo[R]]: (A => R) = (a: A) => a.foo

喜欢建议。我想避免手动提供类型参数R(在这种情况下为String),因为它感觉多余。

【问题讨论】:

  • 他们称它为“匿名”正是因为它没有名称,因此无法引用。所以,“去匿名化”的字面意思是“给它一个名字”:def getFoo[B, A &lt;: Foo[B]](a: A): B = a.foo

标签: scala generics


【解决方案1】:

从字面上回答您的确切问题:

def getFoo[R, A <: Foo[R]](a: A): R = a.foo

但是由于你没有使用A类型,你实际上可以省略它并且完全绑定&lt;: Foo[..],只保留返回类型:

def getFoo[R](a: Foo[R]): R = a.foo

更新(问题已发生相当大的变化)

您可以偷运额外的 apply 调用,从单独的隐式返回类型见证中推断返回类型:

trait Foo[A] { def foo: A }

/** This is the thing that remembers the actual return type of `foo`
  * for a given `A <: Foo[R]`.
  */
trait RetWitness[A, R] extends (A => R)

/** This is just syntactic sugar to hide an additional `apply[R]()`
  * invocation that infers the actual return type `R`, so you don't
  * have to type it in manually.
  */
class RetWitnessConstructor[A] {
  def apply[R]()(implicit w: RetWitness[A, R]): A => R = w
}

def getFoo[A <: Foo[_]] = new RetWitnessConstructor[A]

现在它看起来几乎像您想要的那样,但您必须提供隐式,并且您必须使用额外的一对圆括号调用 getFoo[ConcreteFoo.type]()

object ConcreteFoo extends Foo[String] {
  override def foo: String = "hey"
}

implicit val cfrw = new RetWitness[ConcreteFoo.type, String] {
  def apply(c: ConcreteFoo.type): String = c.foo
}

val f = getFoo[ConcreteFoo.type]()
val x: String = f(ConcreteFoo)

我不确定这是否真的值得,这不一定是最直接的事情。隐含的类型级计算,隐藏在一些微妙的语法糖后面:这可能是隐藏在这两个括号 () 后面的太多魔法。除非您期望 foo 的返回类型会经常更改,否则向 getFoo 添加第二个通用参数并显式写出返回类型可能会更容易。

【讨论】:

  • 好的,这既快速又简单。谢谢你。我想我的 MCVE 对我的实际用例不够准确。我为我的用例添加了一个示例,您能否扩展您的答案?
  • @LukeG 是的,这也可以。使用 Scala 的类型系统可以完成很多奇怪的事情......更新了答案。
  • 感谢您的精彩补充。现在我认为这确实不值得。我尝试将它与解析器组合器一起使用,其中getFoo[A] 的返回类型不是A =&gt; R,而是Parser[R]。我不确定我是否做错了什么,但显然情况只会变得更糟。
  • @LukeG Parser 组合子通常以相似的方式在相似的上下文中使用,并且它们通常看起来大致相同。在使用解析器组合器时,我从来没有觉得需要像你在这里尝试过的东西。也许这是一个 XY 问题。
  • 假设这是一个简单的问题,但变得不必要地复杂了。出于(或多或少是强制性的)学术原因,我有一个单独的扫描仪和解析器。扫描程序生成List[Token]。一些标记扩展trait ValueToken[A]{def value: A}(主要是文字,即具有“值”的标记与关键字和分隔符相对)。每当我在语法规则中使用这些标记时,我都希望它尽可能简单。作为完美主义者,我希望以通用的方式为每个T &lt;: ValueToken[_] 提供它,而不是写出来。
猜你喜欢
  • 2011-04-18
  • 2019-10-07
  • 1970-01-01
  • 2018-07-04
  • 1970-01-01
  • 2018-04-26
  • 2015-11-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多