如何将特征声明为采用隐式“构造函数参数”?

Posted

技术标签:

【中文标题】如何将特征声明为采用隐式“构造函数参数”?【英文标题】:How to declare traits as taking implicit "constructor parameters"? 【发布时间】:2011-10-22 11:06:19 【问题描述】:

我正在设计一个类层次结构,它由一个基类和几个特征组成。基类提供了几种方法的默认实现,并且traits通过abstract override选择性地覆盖某些方法,从而充当可堆叠的traits/mixin。

从设计的角度来看,这很好用,并且映射到域,以便我可以从这里(一个特征)添加一个过滤函数和一个来自这里的谓词(另一个特征)等。

但是,现在我希望我的一些特征采用隐式参数。我很高兴从设计的角度来看这仍然是有意义的,并且在实践中不会令人困惑。但是,我无法说服编译器使用它来运行。

问题的核心似乎是我不能为特征提供构造函数参数,这样它们就可以被标记为隐式。在方法实现中引用隐式参数无法通过预期的“找不到隐式值”消息进行编译;我试图将隐含从构造阶段(实际上,它始终在范围内)“传播”到方法中通过

implicit val e = implicitly[ClassName]

但是(正如你们中的许多人所期望的那样)定义失败并显示相同的消息。

似乎这里的问题是我无法说服编译器用implicit ClassName 标志标记特征本身的签名,并强制调用者(即那些将特征混合到对象中的人)提供隐含的。目前我的调用者正在这样做,但编译器并未在此级别进行检查。


有什么方法可以将特征标记为需要在构造时提供某些隐式?

(如果没有,这只是还没有实现,还是有更深层次的原因导致这是不切实际的?)

【问题讨论】:

【参考方案1】:

实际上,我以前经常想要这个,但只是想出了这个主意。你可以翻译

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

至 [已编辑:原始版本未提供对其他方法的隐式访问]

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,因为它是特征中的抽象字段吗? (如果不是,我不明白它是如何设置的;您介意解释一下吗?) 是的,但是看评论:如果他想使用隐式,他可以写val implWrap = ClassNameW。我没有看到更好的方法:正如您在问题中提到的那样,特征只是没有 any 构造函数参数(可以标记为隐式)。 我做了类似的事情,但使用类型推断来帮助我。 ***.com/a/30178723/1212596【参考方案2】:

这是不可能的。

但您可以使用 implicitly 和 Scala 的类型推断来尽可能轻松地完成此操作。

trait MyTrait 

    protected[this] implicit def e: ClassName


然后

class MyClass extends MyTrait 

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


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

【讨论】:

Scala 2.13: could not find implicit value for parameter e 无法编译,很遗憾 @HartmutP。在类中添加显式类型后与我合作(即val e: ClassName【参考方案3】:

我遇到过几次这个问题,确实有点烦人,但不是太多。抽象成员和参数通常是做同一件事的两种替代方式,各有优缺点。对于具有抽象成员的特征并不太方便,因为您仍然需要另一个类来实现特征。*

因此,您应该在 trait 中简单地声明一个抽象值,以便实现类必须为您提供一个隐含的值。请参阅以下示例 - 正确编译,并显示了实现给定特征的两种方法:

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 的回答中,但我尝试制作一个更引人注目和方便的示例,放弃使用不需要的功能。

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

【讨论】:

但是,这样在Base[T] 中定义方法时,您无法访问隐式Ordering[T]。如果你让numT 隐含,你解决了这个 问题,但是val numT = implicitly[Ordering[T]] 变成了一个无限循环。 如果你修改Base 来解决这个问题,你不能写Der1,但是Der2 仍然可以工作——并且在等效的同时仍然比Der1 更紧凑。 trait Base[T] implicit val numT: Ordering[T] class Der2[T](implicit val numT: Ordering[T]) extends Base[T]【参考方案4】:

你可以这样做:

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

【讨论】:

【参考方案5】:

因为看起来这是不可能的,所以我选择在基类的构造函数上声明隐式val。正如问题中指出的那样,这并不理想,但它可以满足编译器的要求,并且在我的特定情况下,实际上并没有太大的负担。

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

【讨论】:

以上是关于如何将特征声明为采用隐式“构造函数参数”?的主要内容,如果未能解决你的问题,请参考以下文章

如何将@Autowired 构造函数参数分别设置为“required=false”

scala类构造函数参数

Kotlin:为什么构造函数参数默认具有“内部”可见性?

结构作为构造函数参数(在 main 中声明)

Chapter13:拷贝控制

如何将构造函数参数传递给 AppDomain.CreateInstanceXXX?