案例类和伴随对象共享命名空间和实现?

Case class and companion object sharing namespace and implementation?

提问人:Ostap Strashevskii 提问时间:4/3/2023 最后编辑:Dmytro MitinOstap Strashevskii 更新时间:4/3/2023 访问量:84

问:

所以,这不是一个全球性的问题。但。。。这就像一个 DRY 问题。 让我们介绍一下。 如果我有一个带有某个抽象成员的特征

trait WithSomeValue {
val someValue: Int
}

以及一些具有案例类及其同伴的结构都继承了这种特征, 如果我必须做一些静态的东西,比如一个值并将其保存在 Static(companion) 中,就像这样:

object Foo extends WithSomeValue {
override val someValue: Int = 1
}

我也需要在 case 类中实现 trait 时分配它

case class Foo(val x: Int) extends WithSomeValue {
override val someValue: Int = Foo.somevalue// I don`t like to write this! I feel myself DRY and tired ;)
} 

我能做些什么?我能做一些优雅的事情来避免一次又一次地写这个作业吗?只在一个地方写它?

我需要在静态上下文中具有可用的静态成员 我必须得到一个层次结构 - 特征>大小写类?但我不想写 DRY 代码。我希望存在一些技巧,以避免代码重复。

Scala 样板 companion-object

评论

2赞 Luis Miguel Mejía Suárez 4/3/2023
这是一种奇怪的设计,既可以作为实例成员访问,也可以作为相同类型的“静态”成员访问。- 你在这里试图解决的元问题是什么?someValue

答:

1赞 Dmytro Mitin 4/3/2023 #1

例如,可以重写 case 类及其伴随对象的公共父级中的成员

trait WithSomeValue {
  val someValue: Int
}

trait WithSomeValueImpl extends WithSomeValue {
  override val someValue: Int = 1
}

object Foo extends WithSomeValueImpl
case class Foo(x: Int) extends WithSomeValueImpl

Foo.someValue     // 1
Foo(42).someValue // 1

我不需要所有层次结构的一个静态值,而是每个对象伴侣一个值

怎么样

trait WithSomeValue {
  val someValue: Int
}

trait FooLike extends WithSomeValue {
  override val someValue: Int = 1
}

object Foo extends FooLike
case class Foo(x: Int) extends FooLike

trait BarLike extends WithSomeValue {
  override val someValue: Int = 2
}

object Bar extends BarLike
case class Bar(x: Int) extends BarLike

?


实际上,这不是代码重复,因为 case 类及其伴随对象是完全不同的类

类伴随对象与案例类本身

someValue可以在其中完全不同地实现。

无论如何,您可以使用以下宏注释(请参阅下面的 sbt 设置)。它在从类中复制成员的伴随对象中生成成员。如果伴随对象不存在,则注释将创建它。

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro annotations")
class companionSomeValue extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro CompanionSomeValueMacros.macroTransformImpl
}

object CompanionSomeValueMacros {
  def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    def modify(cls: ClassDef, obj: ModuleDef): Tree = (cls, obj) match {
      case (
        q"$_ class $_[..$_] $_(...$_) extends { ..$_ } with ..$_ { $_ => ..$stats }",
        q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }"
      ) =>
        val someValue = stats.collectFirst {
          case t@q"$_ val someValue: $_ = $_" => t
        }.get

        q"""
          $cls

          $mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
            ..$body
            $someValue
          }
        """
    }

    annottees match {
      case (cls: ClassDef) :: (obj: ModuleDef) :: Nil => modify(cls, obj)
      case (cls: ClassDef) :: Nil => modify(cls, q"object ${cls.name.toTermName} extends WithSomeValue")
    }
  }
}
// in a different subproject

trait WithSomeValue {
  val someValue: Int
}

@companionSomeValue
case class Foo(x: Int) extends WithSomeValue {
  override val someValue: Int = 1
}
object Foo extends WithSomeValue

@companionSomeValue
case class Bar(x: Int) extends WithSomeValue {
  override val someValue: Int = 2
}

Foo.someValue     // 1
Foo(42).someValue // 1
Bar.someValue     // 2
Bar(42).someValue // 2

//scalac: {
//  case class Foo extends WithSomeValue with scala.Product with scala.Serializable {
//    <caseaccessor> <paramaccessor> val x: Int = _;
//    def <init>(x: Int) = {
//      super.<init>();
//      ()
//    };
//    override val someValue: Int = 1
//  };
//  object Foo extends WithSomeValue {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    override val someValue: Int = 1
//  };
//  ()
//}

//scalac: {
//  case class Bar extends WithSomeValue with scala.Product with scala.Serializable {
//    <caseaccessor> <paramaccessor> val x: Int = _;
//    def <init>(x: Int) = {
//      super.<init>();
//      ()
//    };
//    override val someValue: Int = 2
//  };
//  object Bar extends WithSomeValue {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    override val someValue: Int = 2
//  };
//  ()
//}

实现略有不同。批注在类中生成委派给伴随对象成员的成员的成员。

@compileTimeOnly("enable macro annotations")
class someValueFromCompanion extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro SomeValueFromCompanionMacros.macroTransformImpl
}

object SomeValueFromCompanionMacros {
  def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    def modifyClass(cls: ClassDef): ClassDef = cls match {
      case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" =>
        q"""
          $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
            ..$stats
            override val someValue = ${tpname.toTermName}.someValue
          }
        """
    }

    def modify(cls: ClassDef, obj: ModuleDef): Tree = q"..${Seq(modifyClass(cls), obj)}"

    annottees match {
      case (cls: ClassDef) :: (obj: ModuleDef) :: Nil => modify(cls, obj)
    }
  }
}
@someValueFromCompanion
case class Foo(x: Int) extends WithSomeValue
object Foo extends WithSomeValue {
  override val someValue: Int = 1
}

@someValueFromCompanion
case class Bar(x: Int) extends WithSomeValue
object Bar extends WithSomeValue {
  override val someValue: Int = 2
}

//scalac: {
//  case class Foo extends WithSomeValue with scala.Product with scala.Serializable {
//    <caseaccessor> <paramaccessor> val x: Int = _;
//    def <init>(x: Int) = {
//      super.<init>();
//      ()
//    };
//    override val someValue = Foo.someValue
//  };
//  object Foo extends WithSomeValue {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    override val someValue: Int = 1
//  };
//  ()
//}

//scalac: {
//  case class Bar extends WithSomeValue with scala.Product with scala.Serializable {
//    <caseaccessor> <paramaccessor> val x: Int = _;
//    def <init>(x: Int) = {
//      super.<init>();
//      ()
//    };
//    override val someValue = Bar.someValue
//  };
//  object Bar extends WithSomeValue {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    override val someValue: Int = 2
//  };
//  ()
//}

Scala 中为 Case 类自动生成 Companion 对象(宏注释的 sbt 设置)

自动生成案例类的案例对象

使用方法为案例类生成伴随对象(字段 = 方法)

评论

0赞 Ostap Strashevskii 4/3/2023
我不需要所有层次结构都有一个静态值,而是每个对象伴侣一个值。(例如类 Bar,其中 someValue = 2)
0赞 Ostap Strashevskii 4/3/2023
有趣的解决方案,但我只能使用案例类,而不是特征 =/