【问题标题】:Scala / Dotty - Mix a trait into an EXISTING objectScala / Dotty - 将特征混合到现有对象中
【发布时间】:2020-09-24 20:03:04
【问题描述】:

有没有办法将特征混合到 Dotty 或 Scala 中的现有对象中?

class SomeClass
trait SomeTrait

// This works, but it's not what I'm looking for:
new SomeClass with SomeTrait

// This is what I'm looking for, but it breaks:
val someClass = new SomeClass
someClass with SomeTrait

This 回答提供了一个宏观解决方案,但它已经 7 岁了,我希望(祈祷!)更简单的东西。

【问题讨论】:

  • 我会从你为什么想要这个开始?什么是元问题。想要为现有对象混合额外的特征对我来说没有多大意义,它的用例是什么?
  • @LuisMiguelMejíaSuárez 这个想法是支持一个流畅的接口来添加特性(例如,(new Organism).addWings() 会产生一个Organism with Wings 对象,而(new Organism).addWings().addLegs() 会产生一个Organism with Wings with Legs 对象。为了避免怀疑,WingsLegs 都是特征。)。然后,此功能将允许像 makeItFly(Organism with Wings) 这样的函数将 both 1) Organism with Wings 对象和 2) Organism with Wings with Legs 对象作为输入。
  • @LuisMiguelMejíaSuárez 基本需要是静态检查makeItFly 的主体,以确保它“拥有运行所需的一切”并在编译时进行检查.
  • @LuisMiguelMejíaSuárez 这几乎就像一个类型级别的依赖注入概念(将依赖注入makeItFly)。
  • 如果有兴趣,也许可以:softwaretalks.io/v/4544/…

标签: scala scala-macros dotty scala-3


【解决方案1】:

改用类型类怎么样?

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

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 属性,或者稍后通过各自伴随对象之外的隐式添加它们。您可以通过在伴随对象中提供隐式来强制有机体始终拥有腿/翅膀,或者将其留给代码的用户。

然后你可以定义

// Only Winged organisms (ie. `O` for which `Winged[O]` is available implicitly
def makeItFly[O <: Organism : Winged](o: O) 

【讨论】:

  • 您好弗朗索瓦--谢谢您的回复。让我再考虑一下。理想情况下,我想避免定义新类(因为在我的用例中,除了 WingedLegged 之外还有许多特征,并且可能的组合数量 - 因此可能的类数量 - 呈指数增长)。但是,我意识到我在这里可能没有选择。
  • 我认为这是满足我需求的最佳解决方案。谢谢弗朗索瓦。
【解决方案2】:

看看看似废弃但相当新的图书馆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()

它仍然是基于宏的,在 Scala 中使用 mixins 到那种程度是不常见的。 Zio 完全改变了另一种模式,IIUC。

【讨论】:

  • 真的很有趣 - 感谢您提供指向库的指针。
【解决方案3】:

如果你想上课,你仍然需要一个宏

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-08
    • 2015-05-26
    • 2018-03-21
    • 1970-01-01
    • 2011-04-23
    • 2017-10-25
    • 1970-01-01
    相关资源
    最近更新 更多