Scala / Dotty - 将一个 Trait 混入一个已存在的对象中

4

@LuisMiguelMejíaSuárez 这个想法是支持添加特性的流畅接口(例如(new Organism).addWings()会产生一个有翅膀的生物对象,而(new Organism).addWings().addLegs()则会产生一个有翅膀和腿的生物对象。为了避免疑义,WingsLegs都是特质。)这个功能将允许像makeItFly(Organism with Wings)这样的函数接受两个输入:1)一个带有翅膀的生物对象和2)一个带有翅膀和腿的生物对象。 - xyz2006tor
@LuisMiguelMejíaSuárez 基本需求是在编译时静态检查makeItFly的主体,以确保它“具备运行所需的一切”,并进行该检查。 - xyz2006tor
@LuisMiguelMejíaSuárez 这就像是一种类型级别的依赖注入概念(其中一个依赖项被注入到 makeItFly 中)。 - xyz2006tor
也许会感兴趣:https://www.softwaretalks.io/v/4544/security-with-scala-refined-types-and-object-capabilities-by-will-sargent - Luis Miguel Mejía Suárez
你谈论这个问题的方式与类型类密切相关,查看我的答案以了解一种简单的方法来实现你想要的,至少如果我理解正确的话。 - francoisr
显示剩余2条评论
3个回答

3

看看似乎被遗弃但是非常近期的库zio-delegate

import zio.delegate._

class SomeClass

trait SomeTrait {
  def test() = println("It just works!")
}

val someClass = new SomeClass

val result: SomeClass with SomeTrait =
  Mix[SomeClass, SomeTrait].mix(someClass, new SomeTrait {})

result.test()

它仍然是基于宏(macro)的,而在Scala中,使用Mixin到这种程度并不常见。据我所知,Zio完全改变了这种模式。


非常有趣 - 感谢您指向该库。 - xyz2006tor

3

如果您想要类(class),仍然需要使用宏(macro)。

class SomeClass1 extends SomeClass with SomeTrait

将自动生成。

我检查了一下,宏仍然可以使用(稍作修改)

def toPersisted[T](instance: T, id: Long): T with Persisted = macro impl[T]

def impl[T: c.WeakTypeTag](c: blackbox.Context)(instance: c.Tree, id: c.Tree): c.Tree = {
  import c.universe._

  val typ = weakTypeOf[T]
  val symbol = typ.typeSymbol
  if (!symbol.asClass.isCaseClass)
    c.abort(c.enclosingPosition, s"toPersisted only accepts case classes, you provided $typ")

  val accessors = typ.members.sorted.collect { case x: TermSymbol if x.isCaseAccessor && x.isMethod => x }
  val fieldNames = accessors map (_.name)

  val instanceParam = q"val instance: $typ"
  val idParam = q"${Modifiers(Flag.PARAMACCESSOR)} val id: Long"
  val superArgs = fieldNames map (fieldName => q"instance.$fieldName")
  val ctor =
    q"""def ${termNames.CONSTRUCTOR}($instanceParam, $idParam) = {
      super.${termNames.CONSTRUCTOR}(..$superArgs)
      ()
    }"""
  val idVal = idParam.duplicate
  val tmpl = Template(List(tq"$typ", tq"Persisted"), noSelfType, List(idVal, ctor))
  val cname = TypeName(c.freshName(symbol.name.toString + "$Persisted"))
  val cdef = ClassDef(NoMods, cname, Nil, tmpl)

  q"""
     $cdef
     new $cname($instance, $id)
    """
}

case class MyClass(i: Int, s: String)

val x = MyClass(1, "a")

val y = toPersisted(x, 2L)

y.i // 1
y.s // a
y.id // 2

3

那么使用类型类呢?

从您在问题评论中提供的示例:

trait Organism
trait Winged[O <: Organism]
trait Legged[O <: Organism]

class Dog extends Organism
object Dog {
   implicit val legged: Legged[Dog] = new Legged[Dog] { ... }
}

class Fly extends Organism
object Fly {
   implicit val winged: Winged[Fly] = new Winged[Fly] { ... }
   implicit val legged: Legged[Fly] = new Legged[Fly] { ... }
}

这是一种非常灵活的方法,它允许您在设计特定生物时定义LeggedWinged属性,或者在相应的伴随对象之外通过 implicits 添加它们。您可以通过在伴随对象中提供 implicit 来始终让生物拥有腿/翅膀,或者将这一选择留给您代码的使用者。
然后您可以定义:
// Only Winged organisms (ie. `O` for which `Winged[O]` is available implicitly
def makeItFly[O <: Organism : Winged](o: O) 

嗨Francois - 谢谢你的回复。让我再仔细考虑一下。理想情况下,我希望避免定义新类(因为在我的用例中除了“有翅膀”和“有腿”之外还有许多特征,并且可能组合的数量 - 因此可能的类数 - 增长呈指数级)。但是,我意识到在这里可能没有其他选项。 - xyz2006tor
我认为这是我需求的最佳解决方案。谢谢Francois。 - xyz2006tor

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