Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there other languages besides D with static if?

I think D's static if is an interesting language feature. That prompts my question: Are there are other examples of compiled languages in which the compiler has a strong notion of the code and there are languages facilities to access them?

For example, this code provides something similar to repr from Python:

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);
  }
}

I think this is cool is because it allows a different and more general approach to what overloading does, which is kind of an inside out way to make code more dynamic, compared to features like this. For example, the compiler knows how many fields my class has, but there's no way for my code to access that information at compile time in most languages.

CAVEAT: That last paragraph had opinions in it, but I just mean to provide some motivation and clarification for my question, not elicit controversy. I just want to find out if any other compiled languages have such features.

like image 658
Grumdrig Avatar asked Nov 11 '09 20:11

Grumdrig


2 Answers

Any language with real macros has a form of static if. For instance Lisp and Nemerle let you construct the code that a macro expands to using programming constructs like 'if' and for-loops. Those are essentially compile-time decisions and let you do something similar to static if. In the case of Nemerle macros are basically plug-ins to the compiler that are executed at compile-time.

In C++ there's boost MPL library which has a kind of static if that can be used to choose between two types. You could put some code inside the two types in a run() member and get something kinda similar, but with very cumbersome syntax.

For example with Boost MPL you could do something like this:

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();

In D that'd be:

static if(is(T == float)) {
     /* float code */
}
else {
     /* int code */
}
like image 69
Baxissimo Avatar answered Oct 20 '22 16:10

Baxissimo


For a "language's awareness of code", there's no better I've seen than Lisp and its macro facility -- specifically, Common Lisp. But the trade there is that most of the time an object's type is not known at compile time or macroexpansion time. For literals, the types are known, so you can find examples of aggressive macros that test to see if an object is a literal and, if so, treat it one way -- maybe based on its type -- and otherwise prepare the detected variable for run-time type inspection.

Here's an example I adapted from the CLLIB library (part of the CLOCC library) several years ago. The goal is to provide functions that will chop a prefix string off of some other string with a matching prefix. The prefix may be known at macroexpansion time or it may not. If it is, we can an optimization: compute the prefix's length first and embed it as a literal, so that it's not recomputed on each call to the generated function. The macro is daunting at first, but the actual generated code is small.

(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))

See the form

(if (stringp prefix)

in the middle? That's inspecting the first argument at macroexpansion time and, depending on whether the argument is a literal or symbol, its type may or may not be known. If the type is a symbol, we assume that we should wait until run time to reconsider it as a variable pointing to some other value.

Here's the expansion for the form (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))))

Note that the variable #:PREFIX-LENGTH-5343 is bound to the computed length of FOO, bound here to variable #:PREFIX-5342.

Now look at the expansion for the form (after-prefix "foo" bar), where the prefix is now a string literal:

(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)))

Now there's no computing the length of "foo"; it's inlined as 3.

It may seem like too much work in this example, but being able to do such things is a good power to have, as your question opines.

like image 29
seh Avatar answered Oct 20 '22 15:10

seh