programming-languages computer-science theory language-theory

programming languages - Exactamente ¿cuál es la diferencia entre un "cierre" y un "bloque"?



programming-languages computer-science (6)

He descubierto que muchas personas usan las palabras " cerrar" y " bloquear" de manera intercambiable. La mayoría de estas personas no pueden explicar de qué están hablando.

Algunos programadores de Java (incluso los de consultorías realmente caras) hablan de clases internas anónimas como "bloques" y "cierres", pero sé que esto no es cierto. (No puede pasar variables mutables desde el alcance del método en el que están definidas ...)

Estoy buscando:

  • una definición precisa de informática de un bloque
  • una definición precisa de informática de un cierre
  • y aclaración sobre la diferencia entre los dos.

Realmente me gustaría ver enlaces, artículos o referencias de libros sobre estos, por favor .


5

Eso es un número entero .

Int workDaysInAWeek = 5

Esa es una variable entera y puede establecerse en un entero diferente. (Si las circunstancias le impiden modificar ese valor, se puede llamar constante ).

Mientras que los números de preocupación anteriores, los bloqueos y cierres tienen que ver con algoritmos. La distinción entre bloques y cierres , respectivamente, también es equivalente a la anterior.


El fuerte y barbudo tiene esto que decir sobre Cierres y Bloques:

http://martinfowler.com/bliki/Closure.html

En un momento dado, dice que un cierre es un bloque que se puede pasar como argumento para un método.


Hay mucha confusión aquí porque hay términos con múltiples definiciones y múltiples cosas distintas que se confunden simplemente porque generalmente se encuentran juntas.

Primero, tenemos "bloque". Eso es solo una porción léxica de código que forma una unidad, el cuerpo de un bucle, por ejemplo. Si el lenguaje realmente tiene alcance de bloque, entonces se pueden definir variables que solo existen dentro de ese bloque de código.

En segundo lugar, tenemos código invocable como tipo de valor. En los lenguajes funcionales, estos son valores de funciones, a veces llamados "funs", "funciones anónimas" (porque la función se encuentra en el valor, no el nombre asignado, no necesita un nombre para llamarlos) o " lambdas "(del operador utilizado para crearlos en el cálculo Lambda de Church). Se los puede llamar "cierres", pero no son automáticamente cierres verdaderos; para calificar, deben encapsular ("cerrar") el alcance léxico que rodea su creación, es decir, las variables definidas fuera del alcance de la función en sí, pero dentro del alcance de su definición todavía están disponibles cada vez que se llama a la función, incluso si el punto de llamada es después de que la variable referenciada saliera del alcance y se haya reciclado su almacenamiento.

Por ejemplo, considere este Javascript:

function makeClosure() { var x = "Remember me!"; return function() { return "x=''" + x + "''"; } } // console.log(x); // The above is an error; x is undefined var f = makeClosure(); console.log(f()); // The above outputs a string that includes x as it existed when f was created.

La variable x se define solo dentro del cuerpo de la función makeClosure ; fuera de esa definición, no existe. Después de llamar a makeClosure , la x declarada dentro debería desaparecer. Y lo es, desde el punto de vista de la mayor parte del código. Pero la función devuelta por makeClosure se declaró mientras x existía, por lo que todavía tiene acceso cuando la llamas más tarde. Eso lo convierte en un verdadero cierre.

Puede tener valores de función que no sean cierres, porque no conservan el alcance. También puedes tener cierres parciales; Los valores de la función de PHP solo conservan las variables específicas que se deben enumerar en el momento en que se crea el valor.

También puede tener valores de código invocables que no representan funciones completas en absoluto. Smalltalk llama a estos "cierres de bloque", mientras que Ruby los llama "procs", aunque muchos Rubyistas simplemente los llaman "bloques", porque son la versión reificada de lo que se creó con la sintaxis { ... } o do ... end . Lo que los hace distintos de lambdas (o "cierres de funciones") es que no introducen un nuevo nivel de llamada. Si el código en el cuerpo de un cierre de bloque invoca el return , regresa de la función / método externo en el que existe el cierre del bloque, no solo el bloque en sí.

Ese comportamiento es fundamental para preservar lo que RD Tennent etiquetó como el "principio de correspondencia", que establece que usted debería poder reemplazar cualquier código con una función en línea que contenga ese código en el cuerpo y lo llame de inmediato. Por ejemplo, en Javascript, puedes reemplazar esto:

x=2 console.log(x)

con este:

(function(){x = 2;})(); console.log(x)

Ese ejemplo no es muy interesante, pero la capacidad de hacer este tipo de transformación sin afectar el comportamiento del programa juega un papel clave en la refactorización funcional. Pero con lambdas, tan pronto como haya incrustado declaraciones de return , el principio ya no se cumple:

function foo1() { if (1) { return; } console.log("foo1: This should never run.") } foo1() function foo2() { if (1) { (function() { return; })(); } console.log("foo2: This should never run.") } foo2()

La segunda función es diferente de la primera; console.log se ejecuta, porque la return solo regresa de la función anónima, no de foo2 . Esto rompe el principio de correspondencia.

Esta es la razón por la cual Ruby tiene ambos procs y lambdas, aunque la distinción es una fuente perenne de confusión para los novatos. Ambos procs y lambdas son objetos de la clase Proc , pero se comportan de manera diferente, como se indicó anteriormente: un return simplemente regresa del cuerpo de una lambda, pero regresa del método que rodea a un proceso.

def test p = proc do return 1 end l = lambda do return 1 end r = l[] puts "Called l, got #{r}, still here." r = p[] puts "Called p, got #{r}, still here?" end

El método de test anterior nunca llegará a las segundas puts , porque la invocación de p hará que la test regrese inmediatamente (con un valor de retorno de 1). Si Javascript tenía cierres de bloque, podría hacer lo mismo, pero no (aunque hay una propuesta para agregarlos).


Los términos que está utilizando se usan comúnmente en estos días en Ruby, aunque los constructos aparecieron previamente en Algol, Smalltalk y Scheme. Citaría el estándar Ruby, si hubiera uno.

No estoy seguro de poder responder tu pregunta exacta, pero puedo ilustrarlo. Mis disculpas si ya sabes esto ...

def f &x yield x end def g y = "block" t = f { p "I''m a #{y}" } y = "closure" t end t = g t.call

Y...

$ ruby exam.rb "I''m a block" "I''m a closure" $

Entonces, un bloque es una secuencia anónima de código de función asociada a una llamada de método. Se usa en todo Ruby API. Cuando haces que sea más fácil crear una función anónima resulta que son útiles para todo tipo de cosas.

Pero tenga en cuenta que después de que f regresa, entonces g regresa, nos aferramos al bloque devolviéndolo de f (como x ) y luego de g (como t ). Ahora llamamos al bloque por segunda vez. De nuevo, tenga en cuenta que g() ha regresado. ¡Pero el bloque se refiere a una variable local en una instancia de función (y alcance) que ya no existe más! Y obtiene el nuevo valor de y ?

Entonces, un cierre es un objeto similar a una función que se cierra sobre su alcance léxico. Son bastante difíciles de implementar porque destruyen el modelo do-it-with-a-stack que es tan útil para variables locales en instancias de llamadas a funciones.

1. Ruby tiene varios sabores de objetos funcionales similares a los cierres; este es solo uno de ellos.


Mientras que un bloque es solo un fragmento de código que puede estar compuesto por declaraciones y declaraciones, pero nada más, un cierre es un objeto real de primera clase, una variable real que tiene un bloque como valor.

La principal diferencia es que un bloque simplemente agrupa las instrucciones juntas (por ejemplo, el cuerpo de una instrucción while ), mientras que un cierre es una variable que contiene algún código que se puede ejecutar.

Si tiene un cierre, por lo general, puede pasarlo como un parámetro para funciones, currificarlo y descurrificarlo, ¡y principalmente invocarlo!

Closure c = { println ''Hello!'' } /* now you have an object that contains code */ c.call()

Por supuesto, los cierres son más potentes, son variables y se pueden usar para definir el comportamiento personalizado de los objetos (mientras que normalmente se deben usar interfaces u otros enfoques OOP en la programación).

Puede pensar en un cierre como una función que contiene lo que esa función hace dentro de sí mismo.

Los bloques son útiles porque permiten el alcance de las variables. Por lo general, cuando define una variable dentro de un ámbito, puede anular las definiciones externas sin ningún problema y existirán nuevas definiciones solo durante la ejecución del bloque.

for (int i = 0; i < 10; ++i) { int t = i*2; printf("%d/r/n", t); }

t se define dentro del bloque (el cuerpo de la instrucción for ) y durará justo dentro de ese bloque.


Un bloque es algo sintáctico: una unidad lógica de enunciados (más relacionada con el alcance que con el cierre ).

if (Condition) { // Block here } else { // Another block }

Un cierre está relacionado con funciones o clases anónimas: un objeto anónimo (función), un fragmento de código que está vinculado a un entorno (con sus variables).

def foo() { var x = 0 return () => { x += 1; return x } }

Aquí foo devuelve un cierre! La variable local x persiste a través del cierre incluso después de que termine foo y puede incrementarse mediante llamadas de la función anónima devuelta.

val counter = foo() print counter() // Returns 2 print counter() // Return 3

Tenga en cuenta que Ruby es el único en el que el bloqueo y el cierre se tratan de manera similar ya que lo que Ruby llama al bloqueo es un cierre:

(1..10).each do |x| p x end

Allí, each método pasa una función de cierre (tomando un parámetro x) que se llama bloque en Ruby.