En los lenguajes de programación name binding (binding = encuadernado, encarpetado, ligadura, unificación) es la asociación de valores con identificadores. Decimos de un identificador ligado a un valor que es una referencia a dicho valor. El concepto de binding es un concepto proveído por los lenguajes de programación: a nivel de máquina no existe el concepto de binding, de relación (nombre, valor). El concepto de Binding esta intimamente relacionado con el concepto de ámbito scope), ya que el análisis de ámbito es la determinación de las relaciones de binding.
El problema del análisis de ámbito sería sencillo sino fuera porque los lenguajes de programación suelen permitir el uso del mismo nombre12.1 para denotar distintos elementos de un programa. Es por ello que es necesario determinar que definición o declaración se aplica a una determinada ocurrencia de un elemento.
En la definición anterior no se especifica en que momento se resuelve la correspondencia (nombre, definición).
Se habla de static binding cuando las reglas y la resolución de la correspondencia (nombre, definición) puede ser resuelta en tiempo de compilación, a partir del análisis del texto del programa fuente (también se habla de early binding).
Por el contrario cuando se habla de dynamic binding cuando la determinación de que definición se aplica a un nombre es establecida en tiempo de ejecución (también se denomina late binding o virtual binding).
Un ejemplo de static binding es una llamada a a una función en C: la función referenciada por un identificador no puede cambiarse en tiempo de ejecución. Un ejemplo de binding dinámico puede ocurrir cuando se trabaja con métodos polimorfos en un lenguaje de programación orientada a objetos, ya que la definición completa del tipo del objeto no se conoce hasta el momento de la ejecución.
El siguiente ejemplo de Dynamic_bindingtomado de la wikipedia, ilustra el binding dinámico.
Supongamos que todas las formas de vida son mortales.
En OOP podemos decir que la clase Persona
y la clase Planta
deben implementar los métodos de Mortal
, el cual contiene
el método muere
.
Las personas y las plantas mueren de forma diferente, por ejemplo las plantas no tienen un corazón que se detenga. Dynamic binding es la práctica de determinar que definición/declaración se aplica a un método en tiempo de ejecución:
void mata(Mortal m) { m.muere(); }
No esta claro cual es la clase actual de m
, una persona o una planta.
Ambos Planta.muere
y Persona.muere
pueden ser invocados.
Cuando se usa dynamic binding, el objeto m
es examinado en tiempo de
ejecución para determinar que método es invocado. Esto supone una 'renuncia' por
parte del lenguaje y su compilador a obtener una definición completa del objeto.
one
, una en la línea 11 y otra en la
línea 20.
pl@europa:~/src/perl/perltesting$ cat -n ds.pl 1 package Father; 2 use warnings; 3 use strict; 4 5 sub new { 6 my $class = shift; 7 8 bless { @_ }, $class; 9 } 10 11 sub one { 12 "Executing Father::one\n"; 13 } 14 15 package Child; 16 use warnings; 17 use strict; 18 our @ISA = 'Father'; 19 20 sub one { 21 "Executing Child::one\n"; 22 } 23 24 package main; 25 26 for (1..10) { 27 my $class = int(rand(2)) ? 'Child' : 'Father'; 28 my $c = $class->new; 29 print $c->one; 30 }¿Que definiciones se aplican a los 10 usos del nombre
one
en la línea 28?
¿Estos usos constituyen un ejemplo de binding estático
o dinámico?
¿Cuál es el ámbito de las declaraciones de one
?
Incluso en los casos en los que la resolución del binding se deja para el momento de la ejecución
el compilador debe tener información suficiente para poder generar código. En el caso
anterior, el compilador de Perl infiere de la presencia de la flecha en $c->one
que one
es el nombre de una subrutina.
pl@europa:~/src/perl/testing$ cat -n symbolic.pl 1 use warnings; 2 use strict; 3 4 sub one { 5 "1\n"; 6 } 7 8 sub two { 9 "2\n"; 10 } 11 12 my $x = <>; 13 chomp($x); 14 15 no strict 'refs'; 16 print &$x();Al ejecutarlo con entrada
one
obtenenmos:
pl@europa:~/src/perl/testing$ perl symbolic.pl one 1¿El uso de la línea 16 es un ejemplo de binding estático o dinámico? ¿Cuál es el binding de las declaraciones de
x
?
Two
hereda de One
.
pl@europa:~/src/perl/testing$ cat -n latebinding.pl 1 package One; 2 use warnings; 3 use strict; 4 5 our $x = 1; 6 sub tutu { 7 "Inside tutu: x = $x\n"; 8 } 9 10 package Two; 11 use warnings; 12 use strict; 13 our @ISA = 'One'; 14 15 our $x = 2; 16 17 print Two->tutu();¿Qué definición de
$x
se aplica al uso en la línea 7?
¿Cuál será la salida del programa?
"ámbito del nombre x"como una abreviación de
"el ámbito de la declaración del nombre x que se aplica a esta ocurrencia de x"
En algunos lenguajes - especialmente en los lenguajes dinámicos- la diferenciación entre tiempo de compilación y tiempo de ejecución puede ser difusa. En el siguiente fragmento de código Perl se usa el módulo Contextual::Return para crear una variable cuya definición cambia con la forma de uso.
lhp@nereida:~/Lperl/src/testing$ cat -n context1.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use Contextual::Return; 4 5 my $x = BOOL { 0 } NUM { 3.14 } STR { "pi" }; 6 7 unless ($x) { warn "¡El famoso número $x (".(0+$x).") pasa a ser falso!\n" } # executed! lhp@nereida:~/Lperl/src/testing$ context1.pl ¡El famoso número pi (3.14) pasa a ser falso!
Obsérvese que el binding de $x
es estático y que a los
tres usos de $x
en la línea 7 se les asigna la definición
en la línea 5.
La declaración de $x
ocurre en lo que Perl denomina
'tiempo de compilación',
sin embargo, el hecho de que un módulo cargado en tiempo de compilación
puede ejecutar sentencias permite a Contextual::Return
expandir el lenguaje de las declaraciones Perl.
pl@europa:~/src/perl/testing$ cat -n contextual.pl 1 #!/usr/bin/perl -w 2 use strict; 3 use Contextual::Return; 4 5 sub sensible { 6 return STR { "one" } 7 NUM { 1 } 8 LIST { 1,2,3 } 9 HASHREF { {name => 'foo', value => 99} } 10 ; 11 } 12 13 print "Result = ".sensible()."\n"; 14 print "Result = ".(0+sensible())."\n"; 15 print "Result = ",sensible(),"\n"; 16 print "Result = (name = ",sensible()->{name},", value = ", sensible()->{value},")\n";Cuando se ejecuta, este programa produce la siguiente salida:
pl@europa:~/src/perl/testing$ ./contextual.pl Result = one Result = 1 Result = 123 Result = (name = foo, value = 99)
Las relaciones de definición-uso de la función sensible
¿Son un caso de
binding estático o de binding dinámico?
¿Cuál es el ámbito de la declaración de sensible
en las líneas 5-11?
Como ya sabemos, es falso que en el ámbito de una declaración que define a x
dicha declaración se aplique a todas las ocurrencias de x
en su ámbito.
En un ámbito estático, una declaración local a la anterior puede ocultar la visibilidad de
la declaración anterior de x
.
El concepto de nombre depende del lenguaje. Algunos lenguajes permiten que en un cierto ámbito haya mas de una definición asociada con un identificador. Un mecanismo que puede ser usado para determinar univocamente que definición se aplica a un determinado uso de un nombre es que el uso del nombre vaya acompañado de un sigil. (podría reinterpretarse que en realidad el nombre de una variable en Perl incluye el sigil).
Así, en Perl tenemos que es
legal tener diferentes variables con nombre x
: $x
, @x
, %x
, &x
, *x
,
etc. ya que van prefijadas por diferentes sigils
$
, @
, etc. El sigil que prefija x
determina que definición se aplica al uso de x
(La palabra sigil hace referencia a 'sellos mágicos' - combinaciones de símbolos
y figuras geométricas - que son usados en algunas invocaciones con el propósito de producir un
sortilegio).
En algunos casos existen mecanismos para la extensión de los nombres. En Perl es posible acceder a una variable de paquete ocultada por una léxica usando su nombre completo.
La asignación de una declaración a ciertos usos de un identificador puede requerir de otras fases de análisis semántico, como el análisis de tipos. El uso de los nombres de campo de un registro en Pascal y en C constituye un ejemplo:
1 type 2 a = ^b; 3 b = record 4 a: Integer; 5 b: Char; 6 c: a 7 end; 8 var 9 pointertob: a; 10 c : Integer; 11 12 ... 13 new(pointertob); 14 15 pointertob^.c := nil; 16 c := 4; 17 ...
El uso de c
en la ĺinea 15 es posible porque el tipo de la expresión pointertob^
es un registro. La definición que se aplica al uso de c
en la línea 16 es la de la línea 10.
También es posible hacer visible un nombre escondido - sin necesidad de extender el identificador -
mediante alguna directiva que lo haga visible:
Un ejemplo es la declaración with
the Pascal:
new(pointertob); with pointertob^ do begin a := 10; b := 'A'; c := nil end; ...
En algunos lenguajes se distingue entre declaraciones que sólo proporcionan información sobre el elemento y lo hacen visible - pero no asignan memoria o producen código para la implementación del mismo - y otras que si producen dicho código. A las primeras se las suele llamar declaraciones y a las segundas definiciones. En tales lenguajes se considera un error que dos declaraciones de un mismo elemento difieran.
Por ejemplo, en C una variable o función sólo es definida una vez,
pero puede ser declarada varias veces.
El calificativo extern
es usado en C para indicar que una declaración provee
visibilidad pero no conlleva definición (creación):
extern char stack[10]; extern int stkptr;
Estas declaraciones le dicen al compilador C que las definiciones de los nombres
stack
y stackptr
se encuentran en otro fichero.
Si la palabra extern
fuera omitida el compilador asignaría memoria para
las mismas.
Otro ejemplo en el que una directiva hace visible una definición escondida
es el uso de la declaración our de Perl cuando un paquete está repartido
entre varios ficheros que usan repetitivamente strict
:
pl@europa:~/src/perl/perltesting$ cat -n useA.pl 1 #!/usr/bin/perl 2 package A; 3 use warnings; 4 use strict; 5 6 use A; 7 8 #our $x; 9 print "$x\n";La variable
$x
esta declarada en el fichero A.pm
:
pl@europa:~/src/perl/perltesting$ cat -n A.pm 1 package A; 2 use warnings; 3 use strict; 4 5 our $x = 1; 6 7 1;
Sin embargo la compilación de useA.pl
produce errores,
pues $x
no es visible:
pl@europa:~/src/perl/perltesting$ perl -c useA.pl Variable "$x" is not imported at useA.pl line 9. Global symbol "$x" requires explicit package name at useA.pl line 9. useA.pl had compilation errors.El mensaje se arregla descomentando la declaración de
$x
en la línea 8 de useA.pl
:
pl@europa:~/src/perl/perltesting$ perl -ce `sed -e 's/#our/our/' useA.pl` -e syntax OKLa declaración de la línea 8 hace visible la variable
$x
en el
fichero useA.pl
.
¿Cual es entonces el ámbito de la declaración de $x
en la línea 5 de
A.pm
? ¿Es todo el paquete? ¿O sólo el segmento del paquete que está en
el fichero A.pm
? (se supone que trabajamos con strict
activado).
Aunque en los lenguajes dinámicos la creación/definición del elemento asociado con un nombre puede postergarse hasta el tiempo de ejecución, la generación de código para la sentencia de uso suele requerir un conocimiento (aunque sea mínimo) del objeto que esta siendo usado. En algunos casos, es necesario inferir la declaración a partir del uso, de manera que la declaración asociada con un uso es construida a partir del propio uso.
Los lenguajes típicamente estáticos fuertemente tipeados suelen requerir que para todo uso exista una declaración explícita y completa del nombre y de las operaciones que son válidas sobre el mismo al finalizar la fase de compilación. Sin embargo, el código para la creación/definición de algunos objetos puede ser postergado a la fase de enlace. De hecho, la resolución de ciertos enlaces pueden ocurrir durante la fase de ejecución (énlace dinámico).
El siguiente ejemplo hace uso de un typeglob selectivo en la línea 8 para
definir la función ONE
:
pl@europa:~/src/perl/testing$ cat -n glob.pl 1 use warnings; 2 use strict; 3 4 sub one { 5 "1\n" 6 } 7 8 *ONE = \&one; 9 print ONE();Al ejecutar este programa se produce la salida;
pl@europa:~/src/perl/testing$ perl glob.pl 1¿El uso de
ONE
en la línea 9 es un ejemplo de binding estático o dinámico?
¿Cuál es el ámbito de la declaración de ONE
?
Responda estas mismas preguntas para esta otra variante del ejemplo anterior:
pl@nereida:~/src/perl/perltesting$ cat -n coderef.pl 1 use warnings; 2 use strict; 3 4 *ONE = sub { "1\n" }; 5 print ONE();
En los ejemplos anteriores el propio uso del nombre ONE
actúa como una declaración: Perl deduce
de la presencia de paréntesis después de ONE
que ONE
es el nombre de una función.
Esta información es suficiente para generar el código necesario. Podría decirse
que la forma del uso declara al ente ONE
y que la línea de uso conlleva una declaración
implícita. Sin embargo, la creación/definición completa de ONE
es postergada hasta la fase de
ejecución.
pl@europa:~/src/perl/testing$ cat -n globwarn.pl 1 use warnings; 2 use strict; 3 4 sub one { 5 "1\n" 6 } 7 8 *ONE = \&one; 9 my $x = ONE; 10 print $x;Al compilar se obtiene un error:
pl@europa:~/src/perl/testing$ perl -c globwarn.pl Bareword "ONE" not allowed while "strict subs" in use at globwarn.pl line 9. globwarn.pl had compilation errors.¿Sabría explicar la causa de este cambio de conducta?
lusasoft@LusaSoft:~/src/perl/perltesting$ cat -n globheader.pl 1 #!/usr/bin/perl 2 use warnings; 3 use strict; 4 5 sub ONE; 6 7 sub one { 8 "1\n" 9 } 10 11 *ONE = \&one; 12 my $x = ONE; 13 print $x;¿Cual es el significado de la línea 5?
En el caso del lenguaje Simple C introducido en la práctica 12.3 hay una única declaración que se aplica a cada ocurrencia correcta de un nombre en el ámbito de dicha declaración.
Esto no tiene porque ser siempre así: en ciertos lenguajes
una redeclaración de un cierto nombre x
puede que sólo oculte
a otra declaración previa de x
si las dos declaraciones
asignan el mismo tipo a x
. Esta idea suele conocerse
como sobrecarga de identificadores. De todos modos, sigue
siendo cierto que para que el programa
sea considerado correcto es necesario que sea posible inferir
para cada ocurrencia de un identificador que única definición se
aplica. Así una llamada a una cierta función
min(x,y)
llamaría a diferentes
funciones min
según fueran los tipos de x
e y
.
Para resolver este caso es necesario combinar las fases
de análisis de ámbito y de análisis de tipos.
Algunos lenguajes - especialmente los lenguajes funcionales - logran eliminar la mayoría de las declaraciones. Disponen de un mecanismo de inferencia que les permite - en tiempo de compilación - deducir del uso la definición y propiedades del nombre.
Véase como ejemplo de inferencia la siguiente sesión en OCaml
pl@nereida:~/src/perl/attributegrammar/Language-AttributeGrammar-0.08/examples$ ocaml Objective Caml version 3.09.2 # let minimo = fun i j -> if i<j then i else j;; val minimo : 'a -> 'a -> 'a = <fun> # minimo 2 3;; - : int = 2 # minimo 4.9 5.3;; - : float = 4.9 # minimo "hola" "mundo";; - : string = "hola"
El compilador OCaml
infiere el tipo de las expresiones.
Así el tipo asociado con la función minimo
es
'a -> 'a -> 'a
que es una expresión de tipo que contiene
variables de tipo. El operador ->
es asociativo a derechas
y asi la expresión debe ser leída como 'a -> ('a -> 'a)
.
Básicamente dice:
El tipo de la expresión es una función que toma un argumento de tipo 'a
(donde 'a
es una variable tipo que será instanciada en el momento del
uso de la función) y devuelve una función que toma elementos de tipo 'a
y retorna elementos de tipo 'a
.
En el ámbito dinámico, cada nombre para el que se usa ámbito dinámico
tiene asociada una pila de bindings. Cuando se crea un nuevo ámbito
dinámico se empuja en la pila el viejo valor (que podría no estar definido).
Cuando se sale del ámbito se saca de la pila el antiguo valor.
La evaluación de x
retorna siempre el valor en el top de la pila.
La sentencia local de Perl provee de ámbito dinámico
a las variables de paquete.
Una aproximación a lo que ocurre cuando se ejecuta
local
es:
DECLARACIÓN DE local |
SIGNIFICADO |
{ local($SomeVar); $SomeVar = 'My Value'; ... } |
{ my $TempCopy = $SomeVar; $SomeVar = undef; $SomeVar = 'My Value'; ... $SomeVar = $TempCopy; } |
La diferencia entre ámbito dinámico y estático debería quedar mas clara observando la conducta del siguiente código
lhp@nereida:~/Lperl/src$ cat -n local.pl 1 #!/usr/bin/perl -w 2 use strict; 3 4 our $x; 5 6 sub pr { print "$x\n"; } 7 sub titi { my $x = "titi"; pr(); } 8 sub toto { local $x = "toto"; &pr(); &titi(); } 9 10 $x = "global"; 11 &pr(); 12 &toto(); 13 &titi();
Cuando se ejecuta, se obtiene la siguiente salida:
> local.pl global toto toto global
local
una declaración o una sentencia?
¿Que declaraciones se aplican a los diferentes
usos de $x
en las líneas 6, 7, 8 y 10?.