【问题标题】:How to pass an implicit parameter from an instance?如何从实例传递隐式参数?
【发布时间】:2011-04-30 06:38:59
【问题描述】:

我正在尝试定义一个类,它将作为一个字段作为一个集合,并希望能够直接从容器类操作这个集合:

case class MyClass(prop: String) extends TraversableLike[Int,MyClass] {
   private def mySet: Set[Int]() = Set()

  override def foreach[U](f: Int => U) = data.foreach[U](f)

  override def newBuilder: Builder[Int, MyClass] =
    new ArrayBuffer[Int] mapResult (a => MyClass(prop, a.toSet))

  implicit def canBuildFrom: CanBuildFrom[MyClass, Int, MyClass] =
    new CanBuildFrom[MyClass, Int, MyClass] {
      def apply(): Builder[Int, MyClass] = newBuilder
      def apply(from: MyClass): Builder[Int, MyClass] = newBuilder
    }
}

我希望能够做到

var obj = MyClass("hello")
obj += 1
obj = obj map (_+1)

第一条指令 (obj+= 1) 有效,但第二条无效。 问题是我无法将隐式 canBuildFrom 放入对象 MyClass 中,因为构建器需要依赖于实例的信息(在本例中为 prop 字段)。

是否有解决方案使我的隐式可访问并保持其实例依赖性? 我想避免让我的班级变得可变。

【问题讨论】:

    标签: scala scala-collections


    【解决方案1】:

    您的代码有几个问题:

    • Set[Int]() 不是 mySet 的有效类型,您应该删除 ()
    • 成员mySet 应该是val,而不是def
    • 您正在调用不存在的 apply() 方法
    • 如果您想在 Traversable 级别挂钩到集合层次结构,您不会获得像 + 和派生的 += 这样的方法。如果你代表一个集合,那么它应该是Set

    这是一个修改后的尝试:

    import mutable.Builder
    import generic.CanBuildFrom
    
    class MyClass private (val prop: String, private val mySet: Set[Int] = Set())
        extends immutable.Set[Int] with SetLike[Int, MyClass] {
    
      def -(elem: Int) = MyClass(prop, mySet - elem)
      def +(elem: Int) = MyClass(prop, mySet + elem)
      def contains(elem: Int) = mySet.contains(elem)
      def iterator = mySet.iterator
    
      override def empty: MyClass = MyClass(prop)
    
      override def stringPrefix = "MyClass(" + prop + ")"
    }
    
    object MyClass {
    
      def DefaultProp = "DefaultProp"
    
      def apply(prop: String, mySet: Set[Int] = Set()) = new MyClass(prop, mySet)
    
      def newBuilder(prop: String = DefaultProp): Builder[Int, MyClass] =
        Set.newBuilder[Int] mapResult (set => MyClass(prop, set))
    
      implicit def canBuildFrom: CanBuildFrom[MyClass, Int, MyClass] =
        new CanBuildFrom[MyClass, Int, MyClass] {
          def apply(): Builder[Int, MyClass] = newBuilder()
          def apply(from: MyClass): Builder[Int, MyClass] = newBuilder(from.prop)
        }
    
    }
    

    然后你可以写:

    var obj = MyClass("hello")
    obj += 1
    println(obj) // prints MyClass(hello)(1)
    obj = obj map (_ + 1)
    println(obj) // prints MyClass(hello)(2)
    

    让我们剖析一下:

    MyClass 现在明确地是一个不可变集合,具有在SetLike 的类型参数中声明的自定义表示。 prop 是公共 val 成员;实际的集合 mySet 是一个私有 val。

    然后我们需要实现Set所依赖的四个操作,只需将它们转发mySet即可。 (这看起来像是可以排除的。对于Seqs,有一个类SeqForwarder 做类似的工作;不过我找不到SetForwarder)。最后,我们提供了一个empty 方法,内置的继承构建器也依赖于该方法。最后,覆盖stringPrefix 可以使用“MyClass”和prop 的值更好地表示字符串。

    请注意,canBuildFrom object MyClass 调用 newBuilder,尽可能传递原始集合的 prop。这意味着大多数情况下,您可以在映射等时保留此值。MyClass 实例。但是,我们需要为prop 定义一个默认值,因为CanBuildFroms 必须定义一个apply 方法,该方法不知道原始集合是什么。 (问题:为什么会发生这种情况?)

    最后,我们对newBuilder 的实现不再依赖ArrayBuffers,而是直接构建Set 实例,该实例将被您的新MyClass 实例包装。

    更多资源:

    【讨论】:

    • 很好的答案。我的错误的关键是我正在考虑我不能将 canBuildFrom 放在我的实例之外,因为我需要属性,但我没有看到实例是在 canBuildFrom 的 apply() 方法中给出的。谢谢 !对于 Set vs TraversableLike,我以为会增加实现抽象,但无论如何,Set 已经是一个抽象集合了。
    • Philippe 如果你想要一个特定类型的 Set 包装器,你的回答完美地补充了this question。关于SetForwarder,我很想在this question上得到更多答案...
    • @Frank 对于代理与转发器的问题,我认为 huynhjl 和您已经很好地概述了差异,我没有什么要补充的。
    • Philippe 顺便说一下,您的代码更改了 equals & hashCode 函数,因为它只需要 Set 内容。我在我的源代码中修复了它,但我只是想注意它,以防其他人会阅读你的示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多