¿Cuándo sería útil el alcance dinámico?


9

Con el alcance dinámico, una persona que llama puede acceder a las variables de la persona que llama. Pseudo código C:

void foo()
{
    print(x);
}

void bar()
{
    int x = 42;
    foo();
}

Como nunca he programado en un lenguaje que admita el alcance dinámico, me pregunto cuáles serían algunos casos de uso del mundo real para el alcance dinámico.


¿tal vez es más fácil de implementar cuando el intérprete se implementa en algún otro idioma en particular?
Alf P. Steinbach

2
Es lo suficientemente poco útil como para que la mayoría de los idiomas que alguna vez lo usaron (por ejemplo, Lisp) ya no lo usen. Solo por un ejemplo obvio, la mayoría de las primeras implementaciones de Lisp usaban el alcance dinámico, pero ahora todas las variantes principales (por ejemplo, CL, Scheme) usan el alcance léxico.
Jerry Coffin

1
@JerryCoffin: Excepciones notables incluyen Perl y Emacs Lisp, ambos utilizaron el ámbito dinámico originalmente, y ahora (Perl 5, Emacs 24) tienen soporte para el ámbito dinámico y léxico. Es bueno poder elegir.
Jon Purdy

@JerryCoffin No coincide exactamente con el ejemplo codificado, pero JavaScript todavía hace un amplio uso del alcance dinámico si entiendo la pregunta correcta. Sin embargo, todavía estoy tratando de pensar en una ventaja general que proporciona que no solo compensa una corta aparición del lenguaje.
Adrian

@JerryCoffin Todavía hay un alcance dinámico utilizado activamente en Common Lisp, principalmente en torno al control dinámico de lectura e impresión.
Vatine

Respuestas:


15

Una aplicación muy útil del alcance dinámico es pasar parámetros contextuales sin tener que agregar nuevos parámetros explícitamente a cada función en una pila de llamadas

Por ejemplo, Clojure admite el alcance dinámico a través de la encuadernación , que se puede utilizar para reasignar temporalmente el valor de *out*impresión. Si vuelve a vincular *out*, cada llamada a imprimir dentro del alcance dinámico del enlace se imprimirá en su nueva secuencia de salida. Muy útil si, por ejemplo, desea redirigir toda la salida impresa a algún tipo de registro de depuración.

Ejemplo: en el siguiente código, la función do-stuff se imprimirá en la salida de depuración en lugar de la salida estándar, pero tenga en cuenta que no necesité agregar un parámetro de salida a do-stuff para habilitar esto ...

(defn do-stuff [] 
  (do-other-stuff)
  (print "stuff done!"))

(binding [*out* my-debug-output-writer]
  (do-stuff))

Tenga en cuenta que los enlaces de Clojure también son subprocesos locales, por lo que no tiene problemas con el uso concurrente de esta capacidad. Esto hace que los enlaces sean considerablemente más seguros que (ab) usando variables globales para el mismo propósito.


2

(Descargo de responsabilidad: nunca he programado en un lenguaje de alcance dinámico)

El alcance es mucho más fácil de implementar y potencialmente más rápido. Con el alcance dinámico, solo se necesita una tabla de símbolos (las variables actualmente disponibles). Simplemente lee de esta tabla de símbolos para todo.

Imagina en Python la misma función.

def bar():
    x = 42;
    foo(42)

def foo(x):
    print x

Cuando llamo a la barra, pongo x en la tabla de símbolos. Cuando llamo a foo, tomo la tabla de símbolos utilizada actualmente para la barra y la empujo a la pila. Luego llamo a foo, que le ha pasado x (probablemente se haya puesto en la nueva tabla de símbolos al llamar a la función). Después de salir de la función, tengo que destruir el nuevo alcance y restaurar el antiguo.

Con el alcance dinámico, esto no es necesario. Solo necesito conocer la instrucción a la que debo regresar cuando la función finaliza, ya que no se debe hacer nada en la tabla de símbolos.


"Potencialmente más rápido" solo en un intérprete (ingenuo); los compiladores pueden hacerlo mucho mejor con el alcance léxico que con el alcance dinámico. Además, "con el alcance dinámico, esto no es necesario ..." está mal: con el alcance dinámico, cuando finaliza el alcance de una variable (por ejemplo, la función regresa), debe actualizar la tabla de símbolos para restaurar su valor anterior. En realidad, creo que el código normalmente se referiría directamente a un "objeto de símbolo" que tendría un campo mutable para el valor actual de la variable, mucho más rápido que hacer una búsqueda de tabla cada vez. Pero aún así, el trabajo de actualización no solo desaparece.
Ryan Culpepper

Ah, no sabía que el alcance dinámico aún restablecería el valor antes de que se llamara a la función. Pensé que todos se referían al mismo valor.
jsternberg

2
Sí, todavía hay anidamiento. De lo contrario, sería equivalente a hacer que cada variable sea global y simplemente hacer una asignación simple.
Ryan Culpepper

2

El manejo de excepciones en la mayoría de los idiomas utiliza el alcance dinámico; cuando se produce una excepción, el control se transferirá nuevamente al controlador más cercano en la pila de activación (dinámica).


Por favor comente sobre la razón del voto negativo. ¡Gracias!
Eyvind

Esta es una toma interesante. ¿Diría también que las returndeclaraciones en la mayoría de los idiomas usan el alcance dinámico, porque devuelven el control a la persona que llama en la pila?
ruakh

1

No estoy 100% seguro de si esto es una coincidencia exacta, pero creo que al menos se acerca lo suficiente en un sentido general para mostrar dónde puede ser útil para romper o cambiar las reglas de alcance.

El lenguaje Ruby viene con la clase de plantillas ERB, que por ejemplo en Rails se usa para generar archivos html. Si lo usa se ve así:

require 'erb'

x = 42
template = ERB.new <<-EOF
  The value of x is: <%= x %>
EOF
puts template.result(binding)

Las bindingmanos acceden a las variables locales a la llamada al método ERB, por lo que puede acceder a ellas y usarlas para completar la plantilla. (El código entre los EOF es una cadena, la parte entre <% =%> evaluada como código Ruby por ERB y declararía su propio alcance como una función)

Un ejemplo de Rails lo demuestra aún mejor. En un controlador de artículos, encontrará algo como esto:

def index
  @articles = Article.all

  respond_to do |format|
    format.html
    format.xml  { render :xml => @posts }
  end
end

El archivo index.html.erb podría usar la variable local @articlescomo esta (en este caso, la creación de un objeto ERB y el enlace son manejados por el marco Rails, por lo que no lo verá aquí):

<ul>
<% @articles.each do |article| %>
  <li><%= article.name</li>
<% end %>
</ul>

Entonces, mediante el uso de una variable de enlace, Ruby permite ejecutar el mismo código de plantilla en diferentes contextos.

La clase ERB es solo un ejemplo de uso. Ruby permite en general obtener el estado real de ejecución con enlaces de variables y métodos mediante el uso del enlace Kernel #, que es muy útil en cualquier contexto en el que desee evaluar un método en un contexto diferente o desee mantener un contexto para su uso posterior.


1

Los casos de uso para el alcance dinámico son IHMO igual que para las variables globales. El alcance dinámico evita algunos de los problemas con las variables globales que permiten una actualización más controlada de la variable.

Algunos casos de uso que se me ocurren:

  • Registro : tiene más sentido agrupar registros por contexto de tiempo de ejecución que desde un contexto léxico. Podría vincular dinámicamente el registrador en un controlador de solicitudes para que todas las funciones llamadas desde allí compartan el mismo "requestID" en las entradas del registro.
  • Redirección de salida
  • Asignadores de recursos : para realizar un seguimiento de los recursos asignados para una acción / solicitud en particular.
  • Manejo de excepciones .
  • Comportamiento contextual en general, por ejemplo, Emacs cambiando la asignación de teclas en ciertos contextos .

Por supuesto, el alcance dinámico no es "absolutamente obligatorio", pero alivia la carga de tener que pasar datos de vagabundeo a lo largo de la cadena de llamadas o tener que implementar proxies globales inteligentes y administradores de contexto.

Pero, una vez más, el alcance dinámico es útil cuando se trata de elementos que muchas veces se tratan como globales (registro, salida, asignación / gestión de recursos, etc.).


-2

Prácticamente no hay ninguno. Espero que, por ejemplo, nunca use la misma variable dos veces.

void foo() {
    print_int(x);
}
void bar() {
    print_string(x);
}

Ahora, ¿cómo llamas a foo y bar desde la misma función?

Esto es efectivamente similar al simple uso de una variable global, y es malo por las mismas razones.


2
x = 42; foo(); x = '42'; bar();?
Luc Danton el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.