Version 20 of Tcl in comparison

Updated 2003-11-16 18:23:59

Richard Suchenwirth 2002-12-16 - For people experienced in other languages, it may be interesting to compare code snippets between Tcl and other languages, to demonstrate similarities and differences. Please add more from your experience!

C

 void countdown(int n) {         | proc countdown {n} {
    int i;                       |
    for(i=n; i>0; i--) {         |    for {set i $n} {$i>0} {incr i -1} {
       printf("%d...\n", i);     |        puts $i...
    }                            |    }
 }                               | }

C /* The above could be: */

 void countdown(int n) {
    for (; n>0; n--)
       printf("%d...\n",n);
 }
  • Everything is a command in Tcl. Function definitions are done with the proc command, assignments with the set command, in/decrementation of integers with the incr command.
  • Retrieving the value of a variable goes with prefixed $ sign - while mentioning the name of a variable it is not used
  • Variable type rarely matters and is not declared. Only incr would complain if its first argument is not an integer
  • Formatting values into strings goes in simple cases by straight string concatenation like in $i.... for stronger control, format is comparable to sprintf
  • puts, unlike printf(), needs no explicit newline.

AM The above proc could look like this in Fortran (90):

  subroutine countdown( n ) 
     integer :: n
     integer :: i
     do i = n,1,-1
        write(*,*) i, '...'
     enddo
  endsubroutine 

The main difference with either C or Tcl is that in Fortran the do-loop is very different kind of control construct: it is really an iteration over a predefined set of values, whereas in C and Tcl the three parts gouverning the iteration can be almost anything. (The Fortran control variable can be an integer only).

Scheme

 (define foo 42)                 | set foo 42
 (define (square x) (* x x))     | proc square x {expr $x * $x}
 (define bar (square foo))       | set bar [square $foo]
 (define grill '(square foo))    | set grill {square $foo}
  • Functions are also defined with the proc command. Their result is the last executed command in the body
  • Arithmetics is not done with prefix functions, but Infix-style like in C, in the argument(s) to the expr command
  • set is used for variables, proc for functions - no unified define in Tcl (except if you write one - see the Scheme page ;-)
  • Tcl commands on toplevel are not enclosed in parens. Embedded commands like the first call to square go into brackets for eager evaluation; the equivalent to quoting is curly braces around a string
 (define (abs x)                 | proc abs x {
   (cond ((> x 0) x)             |    expr { $x > 0?  $x :
         ((= x 0) 0)             |           $x == 0? 0 :
         ((< x 0) (- x))))       |           $x < 0?  -$x}
                                 | }
                                 | or: proc abs x {expr abs($x)}
  • expr can, with the ?: operator, operate like cond
  • It also has built-in math functions that can be exported in a one-liner as shown (Importing expr functions)
  • Tcl needs less parens (which often amount to braces or brackets)
 (define (abs x)                 | proc abs x {
  (if (< x 0)                    |    if {$x < 0} {
      (- x)                      |        expr -$x
      x))                        |    } else {return $x}
  • If,for,while take a test expression in infix notation, as used for expr
  • The branches of an if must be commands - in place of the return x one might write set x

TV The C code from above:

 int countdown(n)
 int n;
 {
    int i;
    for (i=n; i>0; i--) {
       printf("%d...\n", i);
    }
 }

in more traditional, non-cross source file argument checking, non-ansi notation, can also be represented in assembly code. For the above, using the gnu C compiler under cygwin, the following assembly can be generated, which looks obfuscated, because of the complex or hard to see through variable space and stack handling:

 LC0:
         .ascii "%d...\12\0"
 .globl _countdown
         .def    _countdown;     .scl    2;      .type   32;     .endef
 _countdown:
         pushl   %ebp
         movl    %esp, %ebp
         subl    $24, %esp
         movl    8(%ebp), %eax
         movl    %eax, -4(%ebp)
 L10:
         cmpl    $0, -4(%ebp)
         jg      L13
         jmp     L11
 L13:
         movl    -4(%ebp), %eax
         movl    %eax, 4(%esp)
         movl    $LC0, (%esp)
         call    _printf
         leal    -4(%ebp), %eax
         decl    (%eax)
         jmp     L10
 L11:
         leave
         ret

Or, with the optimizer (-O) on, and more verbose assembly code:

         .text
 LC0:
         .ascii "%d...\12\0"
 .globl _countdown
         .def    _countdown;     .scl    2;      .type   32;     .endef
 _countdown:
         pushl   %ebp
         movl    %esp, %ebp
         pushl   %ebx
         subl    $20, %esp
         movl    8(%ebp), %ebx    #  n,  i
         testl   %ebx, %ebx       #  i
         jle     L8
 L6:
         movl    %ebx, 4(%esp)    #  i
         movl    $LC0, (%esp)
         call    _printf
         decl    %ebx     #  i
         testl   %ebx, %ebx       #  i
         jg      L6
 L8:
         addl    $20, %esp
         popl    %ebx
         popl    %ebp
         ret

This irrespective of another possible optimisation step in the assembler, to parallelize adjacent instuctions.

Of course the implicit register assignment of the counter and loop test variable, which apart from optimizer we could also define in the C variable declaration, doesn't make all to much sense in the light of the relatively time consuming printf call.

In Intel mnemonics, the last becomes:

         .text
 LC0:
         .ascii "%d...\12\0"
 .globl _countdown
         .def    _countdown;     .scl    2;      .type   32;     .endef
 _countdown:
         push    ebp
         mov     ebp, esp
         push    ebx
         sub     esp, 20
         mov     ebx, DWORD PTR [ebp+8]   #  i,  n
         test    ebx, ebx         #  i
         jle     L8
 L6:
         mov     DWORD PTR [esp+4], ebx   #  i
         mov     DWORD PTR [esp], OFFSET FLAT:LC0
         call    _printf
         dec     ebx      #  i
         test    ebx, ebx         #  i
         jg      L6
 L8:
         add     esp, 20
         pop     ebx
         pop     ebp
         ret

With extreme optimisation by the compiler using -O4, the function may end up macrofied, so that it is no longer called as a subroutine.


Tcl and other languages


See also BOOK Programming Language Examples Alike Cookbook, http://www.merd.net/pixel/language-study/scripting-language/ and CL's ill-maintained personal notes on language comparison [L1 ].