除了D语言以外,还有哪些编程语言支持静态if语句?

13
我认为D语言的static if是一种有趣的语言特性。这促使我提出一个问题:是否有其他编译型语言,编译器具有代码的强烈概念,并且有语言设施可以访问它们?
例如,以下代码提供了类似于Python中的repr的功能:
char[] repr(T)(T value) {
  static if (is(typeof(value.__repr__))) { // class T provides a "repr()" method
    return value.__repr__();  
  } else static if (is(T:string)) {
    return `"` ~ value ~ `"`;
  // ...other cases...
  } else {
    return toString(value);
  }
}

我认为这很酷,因为它允许一种不同且更普遍的方法来处理重载,这是一种从内到外的方式,使代码更加动态,与此类功能相比。例如,编译器知道我的类有多少个字段,但在大多数语言中,我的代码无法在编译时访问该信息。

注意:最后一段文字含有观点,但我只是想提供一些动机和澄清我的问题,而不是引起争议。我只是想找出是否有其他编译语言具有这样的功能。


1
许多C和C++编译器会在编译时确定if表达式,如果可以确定,则会删除未使用的代码。不确定这是否完全回答了您的问题。 - Mark Ransom
不行 - 上面的例子无法编译,因为静态if块中的代码将无效。例如,如果T是int类型,则value.repr()将会编译错误。 - Grumdrig
1
你需要将上面的代码改为 "static if(is(typeof(value.repr)))" 才能编译通过。或者改为 "static if(__traits(compiles, value.repr))"。 - Baxissimo
3个回答

10
任何带有真正宏的语言都具有静态if的形式。例如Lisp和Nemerle让您使用编程结构(如'if'和for循环)构造宏扩展的代码。这些基本上是编译时决策,让您执行类似于静态if的操作。在Nemerle的情况下,宏基本上是编译器的插件,在编译时执行。

在C++中,有Boost MPL库,其中有一种静态if,可用于选择两种类型之间。您可以将一些代码放入run()成员的两种类型中,并获得类似的东西,但其语法非常繁琐。

例如,使用Boost MPL,您可以做以下操作:

struct float_impl { 
    static void run() { /* float case code */ }
}
struct int_impl { 
    static void run() { /* int case code */ }
}

typedef typename if_<
          is_same<T, float>
        , float_impl
        , int_impl
        >::type impl_t;
impl_t::run();

在D语言中,那将是:
static if(is(T == float)) {
     /* float code */
}
else {
     /* int code */
}

很棒。Nemerle对我来说是新的,非常有趣。期待探索它。Lisp宏我应该想到了。 - Grumdrig

2
“语言对代码的认知意识”方面,我所见过的最好的例子莫过于Lisp及其宏功能——具体来说是Common Lisp。但其中的取舍是,在大多数情况下,对象的类型在编译时或宏展开时并不知道。对于字面量,类型是已知的,因此可以找到一些侵略性的宏示例,测试对象是否为字面量,如果是,则以某种方式处理它——也许是基于其类型——否则准备检测变量以进行运行时类型检查。
以下是我几年前从CLLIB库(CLOCC库的一部分)改编的一个示例。目标是提供函数,用于从具有匹配前缀的其他字符串中截取前缀字符串。前缀可能在宏展开时已知,也可能不知道。如果已知,则可以进行优化:首先计算前缀的长度并将其嵌入为字面量,以便在每次调用生成的函数时不会重新计算。该宏起初看起来令人生畏,但实际生成的代码很小。
(defmacro after-prefix-core (comparison-op prefix string &optional length)
  "Similar to cllib:string-beg-with-cs."
  (flet ((chop (prefix prefix-length string string-length)
           `(when (and (>= ,string-length ,prefix-length)
                       (,comparison-op ,prefix ,string :end2 ,prefix-length))
              (subseq ,string ,prefix-length ,string-length))))
    (let* ((gstring (gensym "STRING-"))
           (gstring-length (gensym "STRING-LENGTH-")))
      `(let* ((,gstring ,string)
              (,gstring-length ,(or length `(length ,gstring))))
         ,(if (stringp prefix)
              ;; Constant -- length known at expansion time.
              (let ((prefix-length (length prefix)))
                (chop prefix prefix-length gstring gstring-length))
              ;; Other form -- length not known at expansion time.
              (let ((gprefix (gensym "PREFIX-"))
                    (gprefix-length (gensym "PREFIX-LENGTH-")))
                `(let* ((,gprefix ,prefix)
                        (,gprefix-length (length ,gprefix)))
                   ,(chop gprefix gprefix-length gstring gstring-length))))))))


(defmacro after-prefix (prefix string &optional length)
  "Similar to cllib:string-beg-with."
  `(after-prefix-core string-equal ,prefix ,string ,length))


(defmacro after-prefix-cs (prefix string &optional length)
  "Similar to cllib:string-beg-with-cs."
  `(after-prefix-core string= ,prefix ,string ,length))

看一下表格。
(if (stringp prefix)

“在中间”?这是在宏展开时检查第一个参数,根据参数是文字还是符号,其类型可能已知或未知。如果类型是符号,我们“假设”应该等到运行时将其重新考虑为指向某个其他值的变量。
下面是表达式“(after-prefix foo bar)”的展开:
(LET* ((#:STRING-5340 BAR) (#:STRING-LENGTH-5341 (LENGTH #:STRING-5340)))
  (LET* ((#:PREFIX-5342 FOO) (#:PREFIX-LENGTH-5343 (LENGTH #:PREFIX-5342)))
    (WHEN
        (AND (>= #:STRING-LENGTH-5341 #:PREFIX-LENGTH-5343)
             (STRING-EQUAL #:PREFIX-5342 #:STRING-5340 :END2 #:PREFIX-LENGTH-5343))
      (SUBSEQ #:STRING-5340 #:PREFIX-LENGTH-5343 #:STRING-LENGTH-5341))))

请注意,变量#:PREFIX-LENGTH-5343绑定到FOO的计算长度,这里绑定到变量#:PREFIX-5342
现在看看表单(after-prefix "foo" bar)的扩展,其中前缀现在是一个字符串字面量:
(LET* ((#:STRING-5463 BAR) (#:STRING-LENGTH-5464 (LENGTH #:STRING-5463)))
  (WHEN (AND (>= #:STRING-LENGTH-5464 3) (STRING-EQUAL "foo" #:STRING-5463 :END2 3))
    (SUBSEQ #:STRING-5463 3 #:STRING-LENGTH-5464)))

现在无法计算“foo”的长度,因为它被内联为3。
在这个例子中可能看起来有点麻烦,但是能够做这样的事情是一个很好的能力,正如你的问题所提到的那样。

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接