18 KiB
contributors | translators | filename | ||||||
---|---|---|---|---|---|---|---|---|
|
|
learntcl-es.tcl |
Tcl fue creado por John Ousterhout como un lenguaje reutilizable de scripting para herramientas de diseño de circuitos de las que él era autor. En 1997 recibió el ACM Software System Award por Tcl. Tcl puede ser utilizado tanto como lenguaje de scripting embebido, como lenguaje de programación general. Puede ser utilizado también como una biblioteca portable de C, incluso en casos donde no se requieren capacidades de scripting, ya que provee de estructuras de datos tales como cadenas (string) de caracteres dinámicas, listas y tablas hash. La biblioteca de C también provee funcionalidad portable para cargar bibliotecas dinámicas, formato de cadenas y conversión de código, operaciones sobre el sistema de ficheros, operaciones de red y más. Algunas características reseñables de Tcl:
-
Conveniente API de red multiplataforma
-
Sistema de ficheros totalmente virtualizado
-
Canales apilables de E/S
-
Asíncrono hasta el núcleo
-
Corrutinas completas
-
Un modelo de hebras reconocido como robusto y fácil de usar
Tcl tiene mucho en común con Lisp pero, en lugar de listas, Tcl utiliza cadenas de caracteres como moneda de cambio del lenguaje. Todos los valores son cadenas. Una lista es una cadena con un formato definido, y el cuerpo de un procedimiento (un script) es también una cadena en lugar de un bloque. Para incrementar el rendimiento, Tcl cachea internamente representaciones estructuradas de estos valores. Las rutinas con listas, por ejemplo, operan en la representación interna en caché, y Tcl se ocupa de actualizar la representación en cadenas si es realmente necesario en el script. El diseño copy-on-write de Tcl permite a los autores de scripts mover grandes volúmenes de datos sin incurrir en el consumo adicional de memoria. Los procedimientos son automáticamente compilados (byte-compiled) a no ser que utilicen rutinas dinámicas como "uplevel", "upvar" o "trace".
Programar en Tcl es un placer. Le resultará atractivo a hackers que encuentren atractivo Lisp, Forth o Smalltalk, y a ingenieros y científicos que simplemente quieren ponerse a trabajar con una herramienta que se doblega a su voluntad. La disciplina de exponer toda la funcionalidad programática como rutinas, incluyendo cosas como iteraciones y operaciones matemáticas que normalmente están en la sintaxis de otros lenguajes, permitiendo fundirse en el fondo de cualquier funcionalidad específica del dominio que necesita un proyecto. Su sintaxis, incluso más simple que la de lisp, simplemente se quita de en medio.
#! /bin/env tclsh
###############################################################################
## 1. Directrices
###############################################################################
# ¡Tcl no es ni Sh ni C! Es necesario decirlo porque el entrecomillado estándar
# de shell casi funciona en Tcl, y es común que la gente empiece con Tcl e
# intente utilizar sintaxis de otros lenguajes. Funciona al principio, pero
# rápidamente conduce a frustración cuando los scripts se vuelven más complejos.
# Las llaves son un mecanismo de entrecomillado, no de sintaxis para la construcción
# de bloques de código o listas. Tcl no tiene ninguna de ellas. Las llaves se
# usan para escapar caracteres especiales, lo que las hace apropiadas para
# entrecomillar cuerpos de procedimientos y cadenas que deberían ser interpretadas
# como listas.
###############################################################################
## 2. Sintaxis
###############################################################################
# Un script consiste en comandos delimitados por saltos de línea o puntos y coma.
# Cada comando es una llamada a una rutina. La primera palabra es el nombre de
# la rutina a llamar, y las siguientes palabras son argumentos de la rutina.
# Las palabras están delimitadas por espacios. Puesto que cada argumento es una
# palabra en el comando, y una cadena de caracteres, puede no ser entrecomillada:
set part1 Sal
set part2 ut; set part3 ations
# el símbolo del dólar introduce la sustitución de variables:
set greeting $part1$part2$part3
# Cuando "set"recibe sólamente el nombre de una variable, devuelve su valor:
set part3 ;# Returns the value of the variable.
# Los corchetes delimitan un script que será evaluado y sustituido por su resultado:
set greeting $part1$part2[set part3]
# Un script incrustado puede estar compuesto de múltiples comandos, el último de
# los cuales devuelve el resultado de la sustitución:
set greeting $greeting[
incr i
incr i
incr i
]
puts $greeting ;# La salida es "Salutations3"
# Cada palabra en un comando es una cadena, incluyendo el nombre de la rutina,
# así que se pueden utilizar sustituciones allí también. Dada esta asignación
# de variable,
set action pu
# los siguientes tres comandos son equivalentes:
puts $greeting
${action}ts $greeting
[set action]ts $greeting
# La barra invertida suprime el significado especial de los caracteres:
set amount \$16.42
# La barra invertida añade significado especial a ciertos caracteres:
puts lots\nof\n\n\n\n\n\nnewlines
# Una palabra encerrada entre llaves no está sujeta a interpretación especial o
# sustitución, excepto que una barra invertida antes de una llave no cuenta al
# buscar la llave de cierre:
set somevar {
This is a literal $ sign, and this \} escaped
brace remains uninterpreted
}
# En una palabra delimitada por comillas dobles, los espacios pierden su significado
# especial:
set name Neo
set greeting "Hello, $name"
# Un nombre de variable puede ser cualquier cadena:
set {first name} New
# La forma de sustitución de variables utilizando llaves permite nombres de
# variable más complejos:
set greeting "Hello, ${first name}"
# "set" puede utilizarse siempre en lugar de la sustitución de variables, y permite
# utilizar cualquier nombre de variable:
set greeting "Hello, [set {first name}]"
# Para desempaquetar una lista en un el comando, se utiliza el operador de expansión,
# "{*}". Estos dos comandos son equivalentes:
set name Neo
set {*}{name Neo}
# Un array es una variable especial que sirve como contenedor de otras variables.
set person(name) Neo
set person(destiny) {The One}
set greeting "Hello, $person(name)"
# "variable" se puede utilizar para declarar o asignar variables. Al contrario
# que "set", que utiliza el espacio de nombres global y el actual para resolver
# un nombre de variable, "variable" usa solamente el actual:
variable name New
# "namespace eval" crea un nuevo espacio de nombres en caso de no existir.
# Un espacio de nombres puede contener tanto rutinas como variables:
namespace eval people {
namespace eval person1 {
variable name Neo
}
}
# Use dos o más ":" para delimitar componentes del espacio de nombres en nombres
# de variables:
namespace eval people {
set greeting "Hello $person1::name"
}
# Dos o más ":" también delimitan componentes del espacio de nombres en nombres
# de rutinas:
proc people::person1::speak {} {
puts {I am The One.}
}
# Nombres completos comienzan con dos ":":
set greeting "Hello $::people::person1::name"
###############################################################################
## 3. No más sintaxis
###############################################################################
# El resto de funcionalidades se implementa mediante rutinas. Desde este punto,
# no hay nueva sintaxis. Todo lo que queda para aprender Tcl es acerca del
# comportamiento de rutinas individuales y el significado que asignan a sus
# argumentos.
###############################################################################
## 4. Variables y espacios de nombres
###############################################################################
# Cada variable y cada rutina están asociadas a algún espacio de nombres
# Para terminar con un intérprete inútil, sólo hay que eliminar el espacio de
# nombres global. No es algo muy útil, pero sirve para ilustrar la naturaleza
# de Tcl. El nombre del espacio de nombres global es en realidad la cadena
# vacía, pero la única forma de representarlo es como un nombre completo. Para
# probarlo, se puede usar esta rutina.
proc delete_global_namespace {} {
namespace delete ::
}
# Como "set" siempre mantiene su vista en los espacios de nombres global y actual,
# es más seguro utilizar "variable" para declarar o asignar un valor a una
# variable. Si una variable llamada "nombre" ya existe en el espacio de nombres
# global, usar "set" asignará un valor a la variable local en lugar de a la
# variable del espacio de nombres actual, mientras que "variable" opera en el
# espacio de nombres actual solamente.
namespace eval people {
namespace eval person1 {
variable name Neo
}
}
# Una vez que una variable es declarada en un espacio de nombres, [set] la vé
# en lugar de una variable de idéntico nombre en el espacio de nombres global:
namespace eval people {
namespace eval person1 {
variable name
set name Neo
}
}
# En cambio, si "set" tiene que crear una nueva variable, siempre lo hace en el
# espacio de nombres actual:
unset name
namespace eval people {
namespace eval person1 {
set name neo
}
}
set people::person1::name
# Un nombre absoluto siempre comienza con el nombre del espacio de nombres global
# (cadena vacía), seguido de dos ":":
set ::people::person1::name Neo
# En el interior de un procedimiento, la variable enlaza una variable en el espacio
# de nombres actual en el ámbito local:
namespace eval people::person1 {
proc fly {} {
variable name
puts "$name is flying!"
}
}
###############################################################################
## 4. Rutinas incorporadas
###############################################################################
# Las operaciones matemáticas se pueden hacer con "expr":
set a 3
set b 4
set c [expr {$a + $b}]
# Como "expr" realiza sustituciones de variables por sí mismo, es necesario
# poner la expresión entre llaves para prevenir a Tcl sustituir las variables
# primero. Ver "http://wiki.tcl.tk/Brace%20your%20#%20expr-essions" para más
# detalles.
# "expr" entiende sustitución de variables y scripts:
set c [expr {$a + [set b]}]
# "expr" provee de un conjunto de funciones matemáticas:
set c [expr {pow($a,$b)}]
# Los operadores matemáticos están disponibles como rutinas en el espacio de
# nombres ::tcl::mathop
::tcl::mathop::+ 5 3
# Las rutinas pueden ser importadas desde otros espacios de nombres:
namespace import ::tcl::mathop::+
set result [+ 5 3]
# Los valores no numéricos deben ser entrecomillados, y los operadores como "eq"
# pueden utilizarse para restringir la operación a una comparación de cadenas:
set name Neo
expr {{Bob} eq $name}
# Los operadores generales recurren a la comparación de cadenas si una operación
# numérica no es factible.
expr {{Bob} == $name}
# "proc" crea nuevas rutinas:
proc greet name {
return "Hello, $name!"
}
# Se pueden especificar múltiples parámetros:
proc greet {greeting name} {
return "$greeting, $name!"
}
# Como se dijo antes, las llaves no construyen un bloque de código. Cada valor,
# incluso el tercer argumento de "proc", es una cadena. El comando anterior
# puede ser reescrito sin usar llaves:
proc greet greeting\ name return\ \"\$greeting,\ \$name!\"
# Cuando el último parámetro es el valor literal "args", todos los argumentos
# extra pasados a la rutina son recogidos en una lista y asignado a "args":
proc fold {cmd first args} {
foreach arg $args {
set first [$cmd $first $arg]
}
return $first
}
fold ::tcl::mathop::* 5 3 3 ;# -> 45
# La ejecución condicional se implementa como una rutina:
if {3 > 4} {
puts {This will never happen}
} elseif {4 > 4} {
puts {This will also never happen}
} else {
puts {This will always happen}
}
# Los bucles se implementan como rutinas. Los primer y tercer argumentos de "for"
# son tratados como scripts, mientras que el segundo lo es como una expresión:
set res 0
for {set i 0} {$i < 10} {incr i} {
set res [expr {$res + $i}]
}
unset res
# El primer argumento de "while" se trata también como una expresión:
set i 0
while {$i < 10} {
incr i 2
}
# Una lista es una cadena, y los elementos de la lista se delimitan con espacios
# en blanco:
set amounts 10\ 33\ 18
set amount [lindex $amounts 1]
# El espacio en blanco dentro de una lista debe ser entrecomillado:
set inventory {"item 1" item\ 2 {item 3}}
# Generalmente, es mejor idea usar rutinas de listas al modificarlas:
lappend inventory {item 1} {item 2} {item 3}
# Las llaves y barras invertidas pueden utilizarse para formatear valores más
# complejos en una lista. Una lista parece un script, excepto en que el carácter
# de nueva línea y el ":" pierden su significado especial, y no hay sustitución
# de variable o scripts. Esta característica hace Tcl homoicónico. Hay tres
# elementos en la siguiente lista:
set values {
one\ two
{three four}
five\{six
}
# Como, al igual que todos los valores, una lista es una cadena, operaciones de
# cadenas pueden ser realizadas sobre ellas, corriendo el riesgo de corromper
# el formato de la lista:
set values {one two three four}
set values [string map {two \{} $values] ;# $values is no-longer a \
properly-formatted list
# La forma segura de conseguir una lista debidamente formateada es utilizando
# las rutinas propias de lista:
set values [list one \{ three four]
lappend values { } ;# add a single space as an item in the list
# Se puede utilizar "eval" para evaluar un valor como un script:
eval {
set name Neo
set greeting "Hello, $name"
}
# Una lista siempre puede ser pasada a "eval" como un script compuesto de un único
# comando:
eval {set name Neo}
eval [list set greeting "Hello, $name"]
# Por lo tanto, cuando se utiliza "eval", use "list" para construir el comando
# deseado:
set command {set name}
lappend command {Archibald Sorbisol}
eval $command
# Un error común es no usar funciones de listas al construir un comando:
set command {set name}
append command { Archibald Sorbisol}
try {
eval $command ;# El error es que "set" tiene demasiados argumentos en \
{set name Archibald Sorbisol}
} on error {result eoptions} {
puts [list {received an error} $result]
}
# Este error puede ocurrir fácilmente con "subst":
set replacement {Archibald Sorbisol}
set command {set name $replacement}
set command [subst $command]
try {
eval $command ;# El mismo error que antes: demasiados argumentos a "set" en \
{set name Archibald Sorbisol}
} trap {TCL WRONGARGS} {result options} {
puts [list {received another error} $result]
}
# "list" formatea correctamente un valor para su sustitución:
set replacement [list {Archibald Sorbisol}]
set command {set name $replacement}
set command [subst $command]
eval $command
# "list" se utiliza normalmente para formatear valores para su sustitución en
# scripts: Hay muchos ejemplos de esto más abajo.
# "apply" evalúa una lista de dos elementos como una rutina:
set cmd {{greeting name} {
return "$greeting, $name!"
}}
apply $cmd Whaddup Neo
# Un tercer elemento puede ser utilizado para especificar el espacio de nombres
# donde aplicar la rutina:
set cmd [list {greeting name} {
return "$greeting, $name!"
} [namespace current]]
apply $cmd Whaddup Neo
# "uplevel" evalúa un script en un nivel superior de la pila de llamadas:
proc greet {} {
uplevel {puts "$greeting, $name"}
}
proc set_double {varname value} {
if {[string is double $value]} {
uplevel [list variable $varname $value]
} else {
error [list {not a double} $value]
}
}
# "upvar" enlaza una variable en el nivel actual de la pila de llamadas a una
# variable en un nivel superior:
proc set_double {varname value} {
if {[string is double $value]} {
upvar 1 $varname var
set var $value
} else {
error [list {not a double} $value]
}
}
# Deshacerse de la rutina "while" incorporada, y utilizar "proc" para definir
# una nueva:
rename ::while {}
# la manipulación se deja como ejercicio:
proc while {condition script} {
if {[uplevel 1 [list expr $condition]]} {
uplevel 1 $script
tailcall [namespace which while] $condition $script
}
}
# "coroutine" crea una nueva pila de llamadas, una nueva rutina en la que
# introducir esa pila de llamadas, y luego llama a dicha rutina. "yield" suspende
# la evaluación en esa pila y devuelve el control a la pila que efectúa la llamada.
proc countdown count {
# devuelve algo al creador de la corrutina, efectivamente pausando esta
# pila de llamadas por ahora.
yield [info coroutine]
while {$count > 1} {
yield [incr count -1]
}
return 0
}
coroutine countdown1 countdown 3
coroutine countdown2 countdown 5
puts [countdown1] ;# -> 2
puts [countdown2] ;# -> 4
puts [countdown1] ;# -> 1
puts [countdown1] ;# -> 0
catch {
puts [coundown1] ;# -> invalid command name "countdown1"
} cres copts
puts $cres
puts [countdown2] ;# -> 3
# Pilas de corrutinas pueden cederse el control entre sí:
proc pass {whom args} {
return [yieldto $whom {*}$args]
}
coroutine a apply {{} {
yield
set result [pass b {please pass the salt}]
puts [list got the $result]
set result [pass b {please pass the pepper}]
puts [list got the $result]
}}
coroutine b apply {{} {
set request [yield]
while 1 {
set response [pass c $request]
puts [list [info coroutine] is now yielding]
set request [pass a $response]
}
}}
coroutine c apply {{} {
set request [yield]
while 1 {
if {[string match *salt* $request]} {
set request [pass b salt]
} else {
set request [pass b huh?]
}
}
}}
# Pon las cosas en marcha
a