Scala3元编程-内联

Posted effe技术空间

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala3元编程-内联相关的知识,希望对你有一定的参考价值。

    书接上回,内联除了能用在函数中,还可以用在类和特质(trait)中。

    1. 内联重载(overload)

    对于重载来说内联和非内联是等价的,因此是否进行内联并不影响字节码。

    2. 内联抽象函数和重写(override)

    让我们分别考虑六种情况:

    (1) 内联函数实现接口:

trait Logger { def log(x: Any): Unit}class InlineLogger extends Logger {  inline def log(x: Any): Unit = println(x)}

    按照内联的定义,调用InlineLogger.log是会内联的,也就是说字节码会折叠,直接调用Logger.log则不会内联。

def logged(logger: Logger, x: Any) = logged.log()logged(new InlineLogger, 3)

    以上的代码通过编译和反编译查看字节码,可以发现结果和没有内联的字节码是一样的。官方称之为保留的内联函数(retained inline method),也就是说即使你直接调用InlineLogger.log,Logger.log方法仍然保留。

    (2) 内联函数重写普通函数

class Logger { def log(x: Any): Unit = println(x)}class NoLogger extends Logger {  inline override def log(x: Any): Unit}

    这种情况和上面类似,按照内联的定义是可以直接内联NoLogger.log,如果只调用NoLogger.log则可以不需要生成Logger.log的字节码。但是这样可能会破坏语义,因此实际上仍然生成了Logger.log的字节码。

    (3) 内联函数重写内联函数

class Logger { inline def log(x: Any): Unit = println(x)}class NoLogger extends Logger { inline override def log(x: Any): Unit}

    这种情况是不允许的,因为每个内联函数都被视作了final,无法被重写。可以看到编译会报错。

    (4) 抽象内联函数

trait AbstInlineLogger {  inline def log(x: Any): Unit }

    比如我们可以像上面这样用内联函数实现接口

def logged(logger: AbstInlineLogger, x: Any): Unit = logger.log(x)

    但是我们不能直接调用AbsInlineLogger.log,因为抽象函数无法被内联

    我们可以通过在logged前加上inline,来让函数的在编译时被内联,完整的代码如下

trait AbstInlineLogger { inline def log(x: Any): Unit } class InlineLogger extends AbstInlineLogger {  inline def log(x: Any): Unit = println(x) } inline def logged(logger: AbstInlineLogger, x: Any): Unit = logger.log(x)   def main(args:Array[String]) = logged(new InlineLogger, 5)

    综合以上的情况,我们可以总结:在接口中应用内联时,需要在调用时,接口处,和具体实现中都使用inline,才能确保语义正确,从而让编译器实现内联。

    (5) 用带内联参数的内联函数重写

class Logger {  def log(x: Any): Unit = println(x)}class NoLogger extends Logger {  inline def log(inline x: Any): Unit = ()}

    这种情况在调用Logger.log时对参数进行评估,但是在调用NoLogger.log时参数的评估又会被弃用,因此调用语义会发生变化。编译器对于这种情况是不允许的,会报上图的错误。

     (6) 抽象内联函数和内联参数

trait AbstInlineLogger {  inline def log(inline x: Any): Unit}
class InlineLogger extends AbstInlineLogger {   inline def log(inline x: Any): Unit = println(x)}
inline def logged(logger: AbstInlineLogger, inline x: Any): Unit = logger.log(x)
val inlineLogger = new InlineLogger logged(inlineLogger, print())

    在这种情况下,先是消除AbstInlineLogger.log,编译InlineLogger.log,同时保留对print函数的调用,最后一句的编译相当于以下语句

println(print())

    根据以上的实验,我们可以得出以下结论:所有的内联函数都是final函数;接口中的内联函数只能由内联函数实现;如果一个内联函数重写常规函数或是实现普通的接口,那么在编译时它仍然是保留的;而一个保留的内联函数是不能有内联参数的。


参考资料:

1.  https://dotty.epfl.ch/docs/reference/metaprogramming/inline.html

2. https://docs.scala-lang.org/scala3/guides/macros/inline.html

3. N. Stucki, A. Biboudis, S. Doeraene, and M. Odersky, “Semantics-Preserving Inlining for MetaprogrammingSemantics-Preserving Inlining for Metaprogramming,” main.tex, vol. 48, pp. 1–11, Nov. 2020.

以上是关于Scala3元编程-内联的主要内容,如果未能解决你的问题,请参考以下文章

inline内联函数

2C++ 的升级

<code> vs <pre> vs <samp> 用于内联和块代码片段

Scala 3正式发布,一门现代的多范式编程语言

scala3-for循环

初学scala3——使用future实现并发