如何将特质声明为具有隐式“构造函数参数”?

41
我正在设计一个类层次结构,包括一个基类和一些特征。基类提供了几种方法的默认实现,而特征通过 abstract override 有选择地覆盖某些方法,以充当可堆叠的特征/混合物。
从设计角度来看,这很有效,并且将过滤函数(一个特征)与谓词(另一个特征)等组合起来,可以映射到域。
但现在我希望我的一些特征使用隐式参数。从设计角度来看,这仍然是有意义的,实际上不会引起困惑。但是,我无法让编译器运行它。
问题的核心似乎是我无法为特征提供构造函数参数,使它们可以标记为隐式参数。在方法实现中引用隐式参数会导致编译器无法找到隐式值的预期错误消息;我尝试将隐式参数从构建阶段(在实践中,它始终在作用域内)“传递”到方法中,以使其可用。
implicit val e = implicitly[ClassName]

但是(正如你们中的许多人所期望的那样),那个定义也以相同的信息失败了。

这里的问题似乎在于我无法说服编译器使用 implicit ClassName 标记特质本身的签名,并强制调用者(即将特质混入对象的人)提供 implicit。目前我的调用者 正在 这样做,但编译器并未在此级别上检查。


有没有办法将一个特质标记为要求在构建时可用某些隐式值?

(如果没有,那么这只是尚未实现还是有更深层次的原因使其不切实际?)

5个回答

17

其实我之前经常想要这个,但是刚想到这个主意。你可以翻译。

trait T(implicit impl: ClassName) {
  def foo = ... // using impl here
}

将implicit访问权限更改为其他方法可访问

trait T {
  // no need to ever use it outside T
  protected case class ClassNameW(implicit val wrapped: ClassName)

  // normally defined by caller as val implWrap = ClassNameW 
  protected val implWrap: ClassNameW 

  // will have to repeat this when you extend T and need access to the implicit
  import implWrap.wrapped

  def foo = ... // using wrapped here
}

这难道不会让调用者在匿名对象中显式定义implWrap吗?因为它是trait中的一个抽象字段。(如果不是,我不明白它是如何设置的;您能解释一下吗?) - Andrzej Doyle
是的,但请注意评论:如果他想使用implicit,他可以只写val implWrap = ClassNameW。我没有看到更好的方法来做到这一点:正如您在问题中提到的那样,traits根本没有任何构造函数参数(可以标记为implicit)。 - Alexey Romanov
我也做类似的事情,但使用类型推断来帮助我。https://dev59.com/-Ww05IYBdhLWcg3w3VeX#30178723 - Paul Draper

17

这是不可能的。

但是你可以使用implicitly和Scala的类型推断来尽可能地减少痛苦。

trait MyTrait {

    protected[this] implicit def e: ClassName

}

然后

class MyClass extends MyTrait {

    protected[this] val e = implicitly // or def

}

简洁,甚至不需要在扩展类中编写类型。


1
Scala 2.13:could not find implicit value for parameter e无法编译,非常遗憾。 - Hartmut Pfarr
@HartmutP。在我添加类中的显式类型(即val e: ClassName)后与我一起工作。 - Michael Mior

13
我曾多次遇到这个问题,确实有点烦人,但不是太严重。抽象成员和参数通常是完成相同任务的两种替代方式,它们各有优缺点。对于特质而言,拥有抽象成员不会太不方便,因为你仍然需要另一个类来实现特质*。
因此,你应该在特质中简单地声明一个抽象值,这样实现类就必须为你提供一个隐式值。请看下面的例子 - 它可以正确编译,并展示了实现给定特质的两种方法:
trait Base[T] {
    val numT: Ordering[T]
}
/* Here we use a context bound, thus cannot specify the name of the implicit
 * and must define the field explicitly.
 */
class Der1[T: Ordering] extends Base[T] {
    val numT = implicitly[Ordering[T]]
    //Type inference cannot figure out the type parameter of implicitly in the previous line
}
/* Here we specify an implicit parameter, but add val, so that it automatically
 * implements the abstract value of the superclass.
 */
class Der2[T](implicit val numT: Ordering[T]) extends Base[T]

我展示的基本思想也存在于Knut Arne Vedaa的答案中,但我试图提供一个更具说服力和方便的示例,省略了不必要的功能。

  • 这不是特质不能接受参数的原因 - 我不知道。我只是认为在这种情况下这种限制是可以接受的。

2
然而,这种方式在定义Base[T]中的方法时无法访问隐式的Ordering[T]。如果将numT设置为隐式,则可以解决此问题,但是val numT = implicitly[Ordering[T]]会导致无限循环。 - Alexey Romanov
1
如果您修改Base以解决此问题,则无法编写Der1,但是Der2仍然有效 - 并且与Der1等效的同时更加紧凑。trait Base[T] { implicit val numT: Ordering[T] } class Der2[T](implicit val numT: Ordering[T]) extends Base[T] - Blaisorblade

0
你可以这样做:
abstract class C

trait A { this: C =>
    val i: Int
}    

implicit val n = 3

val a = new C with A {
    val i = implicitly[Int]
}

但我不确定这样做是否有任何意义 - 你可以直接引用隐式值。

我猜你想要的是摆脱实例化中i的实现,但正如你自己所说,问题的核心在于特质不接受构造函数参数 - 无论它们是否是隐式的都没有关系。

解决这个问题的一个可能方法是向已经有效的语法添加一个新功能:

trait A {
    implicit val i: Int
}

如果隐式变量在作用域内,编译器将会实现 i


0

由于看起来这不太可能实现,我选择在基类构造函数中声明隐式val的选项。正如问题中指出的那样,这并不理想,但它能满足编译器,并且从实用角度来看,在我的特定情况下并不会带来太大负担。

如果有更好的解决方案,我很乐意听取并接受。


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