【问题标题】:Scala: How to invoke method with type parameter and manifest without knowing the type at compile time?Scala:如何在编译时不知道类型的情况下调用带有类型参数和清单的方法?
【发布时间】:2013-09-24 13:50:47
【问题描述】:

我有一个具有以下签名的函数:

myFunc[T <: AnyRef](arg: T)(implicit m: Manifest[T]) = ???

如果我在编译时不知道参数的确切类型,如何调用此函数?

例如:

val obj: AnyRef = new Foo()    // At compile time obj is defined as AnyRef,
val objClass = obj.getClass    // At runtime I can figure out that it is actually Foo
// Now I would need to call `myFunc[Foo](obj.asInstanceOf[Foo])`,
// but how would I do it without putting [Foo] in the square braces?

我想写一些逻辑上类似的东西:

myFunc[objClass](obj.asInstanceOf[objClass])

谢谢!

更新:

问题无效 - 正如@DaoWen、@Jelmo 和@itsbruce 正确指出的那样,我试图做的事情完全是一派胡言!我只是严重地考虑了这个问题。 谢谢你们!太糟糕了,我不能接受所有的答案都是正确的:)

所以,问题是由以下情况引起的:

我正在使用Salat 库将对象序列化到/从 BSON/JSON 表示。 Salat 有一个 Grater[T] 类,用于序列化和反序列化。 BSON 对 反序列化 的方法调用如下所示:

val foo = grater[Foo].asObject(bson)

到这里,类型参数的作用就很明确了。我当时想做的是使用相同的 Grater 来序列化我的域模型中的任何实体。于是我写了:

val json = grater[???].toCompactJSON(obj)

我立即急忙进行反思,只是没有看到表面上有明显的解决方案。即:

grater[Entity].toCompactJSON(obj)  // where Entity...

@Salat trait Entity                // is a root of the domain model hierarchy

有时事情比我们想象的要容易得多! :)

【问题讨论】:

    标签: scala reflection types implicit


    【解决方案1】:

    似乎在我写这个答案时,问题的作者意识到他不需要在运行时解析Manifests。但是,在我看来,这是我在编写 Yaml [反] 序列化库时成功解决的完全合法的问题,所以我将答案留在这里。


    使用ClassTags 甚至TypeTags 可以做你想做的事。我不知道 Manifests 因为该 API 已被弃用并且我没有使用它,但我相信使用清单会更容易,因为它们不像新的 Scala 反射那么复杂。仅供参考,Manifest 的继任者是 TypeTag

    假设你有以下函数:

    def useClasstag[T: ClassTag](obj: T) = ...
    
    def useTypetag[T: TypeTag](obj: T) = ...
    

    您需要使用obj: AnyRef 作为参数调用then,同时为obj.getClass 类提供ClassTagTypeTag 作为隐式参数。

    ClassTag 是最简单的。您可以直接从Class[_] 实例创建ClassTag

    useClasstag(obj)(ClassTag(obj.getClass))
    

    就是这样。

    TypeTags 更难。你需要使用Scala反射从对象中获取一个,然后你必须使用Scala反射的一些内部。

    import scala.reflect.runtime.universe._
    import scala.reflect.api
    import api.{Universe, TypeCreator}
    
    // Obtain runtime mirror for the class' classloader
    val rm = runtimeMirror(obj.getClass.getClassLoader)
    
    // Obtain instance mirror for obj
    val im = rm.reflect(obj)
    
    // Get obj's symbol object
    val sym = im.symbol
    
    // Get symbol's type signature - that's what you really want!
    val tpe = sym.typeSignature
    
    // Now the black magic begins: we create TypeTag manually
    // First, make so-called type creator for the type we have just obtained
    val tc = new TypeCreator {
      def apply[U <: Universe with Singleton](m: api.Mirror[U]) =
        if (m eq rm) tpe.asInstanceOf[U # Type]
        else throw new IllegalArgumentException(s"Type tag defined in $rm cannot be migrated to other mirrors.")
    }
    // Next, create a TypeTag using runtime mirror and type creator
    val tt = TypeTag[AnyRef](rm, tc)
    
    // Call our method
    useTypetag(obj)(tt)
    

    如您所见,这种机器相当复杂。这意味着只有当你真的需要它时才应该使用它,而且正如其他人所说,你真正需要它的情况非常罕见。

    【讨论】:

      【解决方案2】:

      目前尚不清楚您要实现什么目标,多了解一些上下文可能会有所帮助。不管怎样,这是我的 2 美分。

      在这里使用 Manifest 对您没有帮助,因为类型参数需要在编译时知道。我的建议是这样的:

      def myFunc[T](arg: AnyRef, klass: Class[T]) = {
        val obj: T = klass.cast(arg)
        //do something with obj... but what?
      }
      

      你可以这样称呼它:

      myFunc(obj, Foo.class)
      

      请注意,我看不到您如何在 myFunc 中做一些有用的事情。在编译时,除了可用于 AnyRef 的方法之外,您不能在 T 类型的对象上调用任何方法。而如果你想使用反射来操作myFunc的参数,那么就没有必要将其强制转换为特定类型。

      【讨论】:

        【解决方案3】:

        这是使用类型安全的 OO 语言的错误方法。如果你需要这样做,你的设计是错误的。

        myFunc[T <: AnyRef](arg: T)(implicit m: Manifest[T]) = ???
        

        这当然是无用的,正如您可能已经发现的那样。您可以对可能是 anything 的对象调用什么样的有意义的函数?您不能直接引用它的属性或方法。

        我想写一些逻辑上类似的东西:

        myFunc[objClass](obj.asInstanceOf[objClass])
        

        为什么?这种事情一般只在非常特殊的情况下才需要。例如,您是否正在编写一个将使用依赖注入的框架?如果您没有对 Scala 的功能进行一些高度技术性的扩展,则没有必要这样做。

        我打赌你对这个类了解更多一些东西,因为你说你不知道确切类型。基于类的 OO 工作方式的一个重要部分是,如果您想对一般类型的对象(包括其所有子类型)执行某些操作,则将该行为放入属于该类的方法中。如果需要,让子类覆盖它。

        坦率地说,您尝试做的事情的方法是在您对类型有足够了解的上下文中调用该函数。

        【讨论】:

          【解决方案4】:

          这行不通。这样想:你要求编译器为一个直到运行时才知道的类创建一个类 Manifest(在编译时!)。

          但是,我感觉您以错误的方式处理问题。 AnyRef 真的是你在编译时对Foo 类型了解最多的吗?如果是这样的话,你怎么能用它做任何有用的事情呢? (除了为AnyRef 定义的少数方法外,您将无法在其上调用任何方法。)

          【讨论】:

          • 做这样的事情可能会有用。考虑以下示例。您需要对有关某个类的某种元信息进行编码(可能用于某种序列化)。您制作用于以类型安全方式编写此元信息的 API。但是你需要为大量的类编写这个元模型。自然会想到某种反射/宏观解决方案。但是您用于创建元模型的 API 是类型安全的,可能使用 TypeTags/Manifests。因此,您确实需要一种在对静态类型一无所知的情况下调用此类型安全 API 的方法。
          • @VladimirMatveev - 在这种情况下,您需要在方法内部进行模式匹配或其他操作,并且该方法的泛型类型参数仍然无用。我的意思不是说你永远不会使用AnyRef 作为超类型,而是AnyRef 和泛型类型的这种组合没有任何意义。
          • 通用参数为程序员提供了一个方便的接口。如果它经过精心设计,它将提高程序的类型安全性。但是,有时这个接口应该由其他代码调用,而不是由程序员直接调用。例如,您有一个方法construct[T: TypeTag],您需要使用只能在运行时获得的类型来调用它,例如具有案例类的参数类型。我并不是说这是一个常见的用例,但它确实存在。
          猜你喜欢
          • 1970-01-01
          • 2013-05-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多