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元编程-内联的主要内容,如果未能解决你的问题,请参考以下文章