C++ 中的预处理器及其在 Scala 中的替代方案

preprocessors in C++ and their alternatives in Scala

预处理器指令在 Scala 中的替代实现是哪个,就像在 C++ 中一样?假设我有这样的东西:

 #ifdef ADD
 class Add extends Expr {
   Expr left , right ;
   Add ( Expr l, Expr r)
     { left =l; right =r; }
 #ifdef EVAL
   double eval () {
     return left.eval () +
       right.eval ();
   }
 #endif
 #ifdef PRINT
   void print () {
       left.print ();
         System.out.print("+");
         right.print();
   }
 #endif
 }
 #endif

我怎样才能在 Scala 中拥有与此等效的东西?

如果未定义相应的常量,C/C++ 预处理器将不会为 ifdef 块中的代码发出任何内容。 Scala 没有预处理器,据我所知,从字节代码生成中排除 Scala 代码的唯一方法是从构建中排除代码。

你可以用 sbt 做到这一点,方法是使用定义 ADD 的逻辑来控制 Add.scala 是否是构建的一部分。

EVAL 和 PRINT 常量必须以类似的方式处理,每个文件都有一个文件,并使这些方法在 Add class 上可用,使用与添加方法相同的技巧,例如双或长。我真的不知道是否可以将 RichC1 和 RichC2 组合成一个 class,这本身就是一个有趣的问题。

在任何情况下,这都会 咳嗽 几乎肯定会导致一团糟 咳嗽。唯一的好处是代码大小的无限减少。如果这种减少很重要,那么首先 Scala 就不太可能是正确的工具。简单地删除 ifdef 并在构建中包含所有代码可能会更好。

首先,你要问问自己,"Why do I want to conditionally include pieces of code that impact many objects?"(扩展我的评论)

传统上,预处理控制代码用于

  1. 功能配置控制 - 构建中包含哪些功能。这部分是您定义的界面,但主要是您如何使用和调用功能集。
  2. 架构控制 - 软件 运行 所处的环境,例如操作系统和处理器架构。
  3. 注释和横切关注点 - 调试、断言、日志记录、约束检查等

对于第一项,功能配置控制,非常重要的是要注意使用预处理器方法,虽然在小规模上很方便,但确实会使软件变得模糊不清,并可能导致您陷入困境。

Scala 的方式是定义一组接口,称为特征,然后将这些接口组合成一个完整的画面。如果你真的需要一个特定的组件不包含在代码中,那么你可以创建一组只包含你想要的代码的 classes 并将它们打包到一个模块(包或 jar)中。然后将该模块包含在最终包装中。

第二个问题以类似的方式解决。您为公共代码创建包,为平台特定项目创建包。然后,在 运行 时间,你动态地 select 你加载了哪些包。这可以通过显式包路径来完成,或者如果你想对此有所技巧,你可以尝试创建一个 class 加载器来为你做脏活。但是,从长远来看,提前处理事情会更好。

第三个问题已通过 Scala 中的几种方法解决。

Java 和 Scala 中使用的一种方法是注释。这些使用编程标记方法来指示特定的 classes、方法或字段具有特殊处理。参见Scala documentation。然而,使用它来标记方法不存在并不是一个好的用途,即使它是可能的。例如,@deprecated 注释用于告诉程序员 API 元素何时消失,并允许编译器或 IDE 标记应升级的项目。

Scala 中可用的另一种方法是使用宏。来自 this page,我们有这样的宣传:宏非常适合代码生成、静态检查和领域特定语言。理论上,您可以将它们用于您一直尝试进行的预处理。

一般来说,预处理方法会导致 "kitchen sink" 类型的编程。另一个很难处理的问题是,您的方法要求 应用程序中的每个模块,无论是库还是主程序,都需要使用相同的标志进行编译。 这对您的 make 系统来说是一个巨大的负担,尤其是当您通过了数百个源文件标记时。