Compile-time calculation

From Rosetta Code
Revision as of 18:49, 25 January 2010 by rosettacode>Kevin Reid (→‎Common Lisp: new example)
Compile-time calculation is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Some programming languages allow calculation of values at compile time. For this task, calculate 10! at compile time. Print the result when the program is run.

Discuss what limitations apply to compile-time calculations in your language.

Warnings:

  • This task is a draft, you can modify it if you find a way to enhance it.

C++

<lang cpp>#include <iostream>

template<int i> struct Fac {

   static const int result = i * Fac<i-1>::result;

};

template<> struct Fac<1> {

   static const int result = 1;

};


int main(int argc, char* argv[]) {

   std::cout << "10! = " << Fac<10>::result << "\n";

}</lang>

Compile-time calculations in C++ look quite different from normal code. We can only use templates, type definitions and a subset of integer arithmetic. It is not possible to use iteration. C++ compile-time programs are similar to programs in pure functional programming languages, albeit with a peculiar syntax.

Common Lisp

Assuming a definition from Factorial function#Common Lisp:

<lang lisp>(defmacro ct-factorial (n)

 (factorial n))</lang>

The factorial function must be defined before a use of the ct-factorial macro is evaluated or compiled.

If the data resulting from the compile-time calculation is not necessarily a number or other self-evaluating object then the macro must quote it to avoid it being interpreted as code (a form):

<lang lisp>(defmacro ct-factorial (n)

 `(quote ,(factorial n)))</lang>

or <lang lisp>(defmacro ct-factorial (n)

 `',(factorial n))</lang>

Forth

During a word definition, you can drop out of the compilation state with [ and go back in with ]. (This is where the naming conventions for [CHAR] and ['] come from.) There are several flavors of LITERAL for compiling the result into the word.

<lang forth>: fac ( n -- n! ) 1 swap 1+ 2 max 2 ?do i * loop ;

main ." 10! = " [ 10 fac ] literal . ;

see main

main
 .\" 10! = " 3628800 . ; ok</lang>

OCaml

OCaml calculates simple mathematical operations at compile-time, for example in the code below (24 * 60 * 60) will be replaced by its result 86400.

<lang ocaml>let days_to_seconds n =

 let conv = 24 * 60 * 60 in
 (n * conv)
</lang>

It is easy to verify this using the argument -S to keep the intermediate assembly file:

ocamlopt -S sec.ml 
grep 86400 sec.s
        imull   $86400, %eax

Oz

<lang oz>functor import

  System Application

prepare

  fun {Fac N}
     {FoldL {List.number 1 N 1} Number.'*' 1}
  end
  Fac10 = {Fac 10}

define

  {System.showInfo "10! = "#Fac10}
  {Application.exit 0}

end</lang>

Code in the prepare section of a functor is executed at compile time. External modules that are used in this code must be imported with a require statement (not shown in this example). Such external functors must have been compiled before the current functor is compiled (ozmake will automatically take care of this).

It is possible to export variables that are defined in the prepare statement. However, such variables must not be stateful entities, e.g. it is not possible to export a cell that was defined at compile time.

Perl

There are few limits on code you can put in BEGIN blocks, which are executed at compile-time. Unfortunately, you can't in general save the compiled form of a program to run later. Instead, perl recompiles your program every time you run it.

<lang perl>my $tenfactorial; print "$tenfactorial\n";

BEGIN

  {$tenfactorial = 1;
   $tenfactorial *= $_ foreach 1 .. 10;}</lang>

Tcl

In Tcl, compilation happens dynamically when required rather than being a separate step. That said, it is possible to use the language's introspection engine to discover what code has been compiled to, making it easy to show that known-constant expressions are compiled to their results. Generating the expression to compile is then simple enough.

Works with: Tcl version 8.5

<lang tcl>proc makeFacExpr n {

   set exp 1
   for {set i 2} {$i <= $n} {incr i} {
       append exp " * $i"
   }
   return "expr \{$exp\}"

} eval [makeFacExpr 10]</lang> How to show that the results were compiled? Like this: <lang tcl>% tcl::unsupported::disassemble script [makeFacExpr 10] ByteCode 0x0x4de10, refCt 1, epoch 3, interp 0x0x31c10 (epoch 3)

 Source "expr {1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10}"
 Cmds 1, src 45, inst 3, litObjs 1, aux 0, stkDepth 1, code/src 0.00
 Commands 1:
     1: pc 0-1, src 0-44
 Command 1: "expr {1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10}"
   (0) push1 0 	# "3628800"
   (2) done 

</lang> As you can see, that expression was transformed into just a push of the results (and an instruction to mark the end of the bytecode segment).