Julia: Las reglas de alcance variable global conducen a un comportamiento poco intuitivo en el REPL / notebook

Creado en 21 ago. 2018  ·  98Comentarios  ·  Fuente: JuliaLang/julia

Ejemplo 1

Esto surgió con un estudiante que actualizó de 0.6 a 1.0 directamente, por lo que ni siquiera tuvo la oportunidad de ver una advertencia de desaprobación, y mucho menos encontrar una explicación para un nuevo comportamiento:

julia> beforefor = true
true

julia> for i in 1:2
         beforefor = false
       end

julia> beforefor  # this is surprising bit
true

julia> beforeif = true
true

julia> if 1 == 1
         beforeif = false
       end
false

julia> beforeif  # Another surprise!
false

julia> function foo()
         infunc = true
         for i in 1:10
           infunc = false
         end
         <strong i="7">@show</strong> infunc
       end
foo (generic function with 1 method)

julia> foo()  # "I don't get this"
infunc = false 

Ejemplo 2

julia> total_lines = 0
0

julia> list_of_files = ["a", "b", "c"]
3-element Array{String,1}:
 "a"
 "b"
 "c"

julia> for file in list_of_files
         # fake read file
         lines_in_file = 5
         total_lines += lines_in_file
       end
ERROR: UndefVarError: total_lines not defined
Stacktrace:
 [1] top-level scope at ./REPL[3]:4 [inlined]
 [2] top-level scope at ./none:0

julia> total_lines  # This crushs the students willingness to learn
0

"Entiendo" por qué sucede esto en el sentido que creo que puedo explicar, con suficiente referencia a los arcanos en el manual sobre lo que introduce ámbitos y lo que no, pero creo que esto es problemático para el uso interactivo.

En el ejemplo uno, obtienes un fallo silencioso. En el ejemplo dos, obtiene un mensaje de error que es muy no hay cuchara. Eso es más o menos comparable a un código de Python que escribí en un cuaderno en el trabajo hoy.

No estoy seguro de cuáles son las reglas en Python, pero sí sé que, en general, no se pueden asignar cosas en el ámbito global sin invocar global. Pero en el REPL funciona, presumiblemente porque en el REPL las reglas son diferentes o se aplica la misma lógica como si todas estuvieran en el ámbito de función.

No puedo defender las reglas lo suficiente como para proponer el cambio concreto que me gustaría, y según Slack, esto no es necesariamente percibido como un problema por algunas personas, por lo que no sé a dónde ir con esto, excepto para marcarlo.

Referencias cruzadas:

19324

https://discourse.julialang.org/t/repl-and-for-loops-scope-behavior-change/13514
https://stackoverflow.com/questions/51930537/scope-of-variables-in-julia

REPL minor change

Comentario más útil

@JeffBezanson , recuerde que a muchos de nosotros nos gustaría usar a Julia como un sustituto de Matlab, etcétera, en cursos técnicos como álgebra lineal y estadística. Estos no son cursos de programación y los estudiantes a menudo no tienen experiencia en programación. Nunca hacemos programación estructurada; casi todo es interactivo con fragmentos cortos y variables globales.

Además, la razón por la que estoy usando un lenguaje dinámico en primer lugar es para cambiar con fluidez entre la exploración interactiva y una programación más disciplinada. La incapacidad de usar el mismo código en un contexto global y funcional es un obstáculo para ese fin, incluso para alguien que está acostumbrado a los conceptos de alcance, y es mucho peor para los estudiantes que no tienen antecedentes de informática.

Todos 98 comentarios

(Según @mlubin , este es el cambio relevante https://github.com/JuliaLang/julia/pull/19324)

Stefan sugirió aquí que una posibilidad para resolver este problema es el ajuste automático de las entradas REPL en bloques let

Pero, ¿no sería confuso que no pudieras hacer

a = 1

y usar a después de eso? A menos que se inserte global para todas las asignaciones de nivel superior, ¿supongo?

El comportamiento no sería simplemente envolver todo en un bloque let , es más complicado que eso. Debe enlazar cualquier global que esté asignado dentro de la expresión y luego extraer el valor de enlace a un global al final de la expresión.

Por lo tanto, convertiría a = 1 en algo como a = let a; a = 1; end . Y algo como

for i in 1:2
    before = false
end

se convertiría en esto:

before = let before = before
    for i in 1:2
        before = false
    end
end

Francamente, estoy bastante molesto porque la gente solo está dando esta retroalimentación ahora. Este cambio ha estado en el maestro durante diez meses.

Soy culpable de no haber seguido al maestro muy cerrado hasta hace poco, por lo que esta retroalimentación es de hecho un poco tarde. Más que una preocupación para los programadores (la mayoría de los bucles for estarán dentro de una función en el código de la biblioteca) Me temo que esto es una preocupación para la enseñanza. A menudo, los bucles for se enseñan antes que las funciones o los alcances (por supuesto, debe comprender los alcances para comprender realmente lo que está sucediendo, pero al enseñar, las cosas a menudo se simplifican).

Aquí se vuelve un poco difícil enseñar a un principiante cómo sumar números del 1 al 10 sin explicar funciones o variables globales.

Francamente, estoy bastante molesto porque la gente solo está dando esta retroalimentación ahora. Este cambio ha estado en el maestro durante diez meses.

Para ser justos, Julia 0.7 fue lanzada hace 13 días. Este es un nuevo cambio para la mayoría de los usuarios de Julia.

Francamente, estoy bastante molesto porque la gente solo está dando esta retroalimentación ahora. Este cambio ha estado en el maestro durante diez meses.

Desafortunadamente para aquellos de nosotros que no podemos soportar vivir al límite, es nuevo desde nuestra perspectiva.

Francamente, estoy bastante molesto porque la gente solo está dando esta retroalimentación ahora. Este cambio ha estado en el maestro durante diez meses.

Y para aquellos de nosotros que hemos sido alentados a permanecer alejados de las ramas de desarrollo, "es completamente nuevo desde nuestra perspectiva".

¿Podemos volver a centrarnos en el tema en cuestión ahora, en lugar de tener una meta discusión sobre cuánto tiempo ha tenido la gente para probar esto? Es lo que es ahora, así que miremos hacia adelante.

Soy culpable de no haber seguido al maestro muy cerrado hasta hace poco, por lo que esta retroalimentación es de hecho un poco tarde. Más que una preocupación para los programadores (la mayoría de los bucles for estarán dentro de una función en el código de la biblioteca). Me temo que esto es una preocupación para la enseñanza. A menudo, los bucles for se enseñan antes que las funciones o los ámbitos (por supuesto, debe comprender los ámbitos para comprender realmente lo que está sucediendo, pero al enseñar, las cosas a menudo se simplifican).

Aquí se vuelve un poco difícil enseñar a un principiante cómo sumar números del 1 al 10 sin explicar funciones o variables globales.

Este es un gran punto. Después de descubrir cuál es realmente el problema, es sorprendente lo poco que aparece. Es un problema menor con una gran cantidad de código de Julia en la naturaleza y en las pruebas, y reveló muchas variables que eran accidentalmente globales (en ambas pruebas de Julia Base de acuerdo con el PR original, y noté esto en la mayoría de Pruebas de DiffEq). En la mayoría de los casos, parece que el comportamiento sutilmente incorrecto no es lo que obtienes (esperar un cambio en un bucle), sino que esperar poder usar una variable en un bucle es lo que he descubierto que es la gran mayoría de donde esto se muestra al actualizar los scripts de prueba a v1.0. Entonces, lo bueno es que, en la mayoría de los casos, al usuario se le presenta un error y no es difícil de solucionar.

Lo malo es que es un poco detallado tener que poner global x dentro de los bucles, y ahora su código REPL también es diferente del código de función. Si es o no un comportamiento más intuitivo que antes, es una opinión difícil porque definitivamente hubo algunos casos extremos en el alcance local duro / suave y, por lo tanto, esto es claramente más fácil de explicar. Pero al mismo tiempo, si bien tiene una explicación mucho más concisa que el comportamiento anterior, ahora es más fácil llegar a los casos extremos en los que es importante comprender las reglas de alcance. 🤷‍♂️.

Por mi parte, me gustaría ver los experimentos con el bloqueo let . Esto mantendría el aspecto de "realmente no querías tantos globales", junto con la explicación de alcance simplificada, mientras que al mismo tiempo haría que el código REPL se comportara como interiores de funciones (que aparentemente es lo que siempre hemos querido). O a la inversa, hacer que las personas especifiquen las variables que quieren que actúen como globales.

global x = 5
for i = 1:5
  println(x+i)
end

podría ser una buena forma de mantener la claridad y haría que el "código REPL es lento debido a los globales" sea mucho más obvio. La desventaja es que, una vez más, colocar cosas en una función no requeriría los marcadores global .

Pero dada la forma en que esto tiende a aparecer, no es realmente un juego o un espectáculo. Lo clasificaría como una verruga que debería recibir una mención en cualquier taller, pero no es que la v1.0 sea inutilizable por eso. Espero que cambiar este comportamiento no se clasifique como ruptura y requiera v2.0 sin embargo.

No estoy tan seguro de que me guste la idea de que el REPL deba comportarse como un interior funcional. Claramente no lo es, así que espero que se comporte como un alcance global. Para mí, el REPL que no se comporte como un alcance global sería potencialmente incluso más confuso que la discrepancia que causa este problema.

Independientemente, al menos creo que la documentación debería ser algo más explícita sobre este tema. Al leer casualmente los documentos, habría asumido que necesitaría usar la palabra clave local para que el comportamiento se produzca en el alcance global de forma predeterminada.

Por mi parte, me gustaría ver los experimentos con el bloqueo let . Esto mantendría el aspecto de "realmente no querías tantos globales", junto con la explicación de alcance simplificada, mientras que al mismo tiempo haría que el código REPL se comporte como interiores de funciones (que aparentemente es lo que siempre hemos querido)

Si buscamos "REPL es lo mismo que el interior de una función", también deberíamos pensar en outer :

julia> i = 1
1

julia> for outer i = 1:10
       end
ERROR: syntax: no outer variable declaration exists for "for outer"

versus:

julia> function f()
          i = 0
          for outer i = 1:10
          end
          return i
       end
f (generic function with 1 method)

julia> f()
10

Francamente, estoy bastante molesto porque la gente solo está dando esta retroalimentación ahora. Este cambio ha estado en el maestro durante diez meses.

La gente no ha estado usando master para uso interactivo o para la enseñanza, lo han estado usando para actualizar paquetes, que solo se ven afectados mínimamente por esto y en su mayoría están escritos por programadores experimentados.

(Sin embargo, fui una de las pocas personas que dio su opinión en el número 19324, donde defendí el antiguo comportamiento ).

Una forma ininterrumpida de salir de esto sería volver al comportamiento anterior (idealmente no insertando bloques let implícitos ni nada; simplemente restaure el código anterior en julia-syntax.scm como una opción) en el REPL. O más bien, para que esté disponible en entornos como IJulia que lo deseen, agregue una bandera soft_global_scope=false a include , include_string y Core.eval para restaurar la comportamiento antiguo.

(Sin embargo, fui una de las pocas personas que dio su opinión en el n. ° 19324, donde defendí el comportamiento anterior).

Sí, y se lo agradezco mucho. No importa mucho ahora, ya que tomamos la decisión, lo dejamos hornear durante diez meses y ahora lo hemos lanzado con un compromiso a largo plazo con la estabilidad. Así que lo único que podemos hacer ahora es centrarnos en qué hacer en el futuro.

Tener la opción de elegir entre el comportamiento anterior y el nuevo es interesante, pero se siente muy hack. Eso significa que no solo a veces tenemos un comportamiento de alcance que aparentemente todos encontraron increíblemente confuso, sino que no siempre lo tenemos y si lo tenemos o no depende de una bandera global. Me temo que se siente bastante insatisfactorio.

Tener la opción de elegir entre el comportamiento anterior y el nuevo es interesante, pero se siente muy hack.

Si alguien implementa una transformación AST de alcance suave "unbreak me", será muy tentador usarla en IJulia, OhMyREPL, etcétera, momento en el que se obtiene una situación aún más problemática en la que el REPL predeterminado se ve roto.

Eso no es lo que estoy diciendo. Claramente, deberíamos usar la misma solución en todos esos contextos. Pero implementarlo como dos variaciones diferentes en las reglas de alcance parece menos limpio que implementarlo como una transformación de código con un conjunto de reglas de alcance. Pero quizás esos sean funcionalmente equivalentes. Sin embargo, parece más fácil de explicar en términos de las nuevas reglas de alcance más simples + una transformación que toma la entrada de estilo REPL y la transforma antes de evaluarla.

Eso podría hacerse como Meta.globalize(m::Module, expr::Expr) que transforma una expresión al anotar automáticamente cualquier global que exista en el módulo como global si se asignan dentro de cualquier ámbito sin función de nivel superior. Por supuesto, creo que eso es equivalente a lo que hacía el analizador anterior, pero un poco más transparente, ya que puede llamar a Meta.globalize usted mismo y ver qué evaluará el REPL.

Eso podría hacerse como Meta.globalize(m::Module, expr::Expr) que transforma una expresión al anotar automáticamente cualquier global que exista en el módulo como global si se asignan dentro de cualquier ámbito sin función de nivel superior.

De hecho, comencé a buscar implementar algo como esto hace unos minutos. Sin embargo, parece que sería mucho más fácil de implementar como una opción en julia-syntax.jl :

  • Es posible escribir una transformación AST externa, pero parece que hay muchos casos complicados, básicamente tienes que volver a implementar las reglas de alcance, mientras que ya teníamos el código para hacerlo bien en julia-syntax.scm .
  • Es aún más complicado para algo como IJulia que actualmente usa include_string para evaluar un bloque completo de código y obtener el valor de la última expresión. No solo tendríamos que cambiar al análisis de expresión por expresión, sino que podría ser necesario algún tipo de piratería para preservar los números de línea originales (para mensajes de error, etc.). (Aunque encontré un truco para ChangePrecision.jl para este tipo de cosas que también pueden funcionar aquí).
  • Sin mencionar el caso de personas que include archivos externos, que no serían detectados por su transformación AST.

Sin embargo, parece más fácil de explicar en términos de las nuevas reglas de alcance más simples + una transformación que toma la entrada de estilo REPL y la transforma antes de evaluarla.

Dudo seriamente que esto sea más fácil de explicar a los nuevos usuarios que simplemente decir que las reglas son menos exigentes para el uso interactivo o por include con una determinada bandera.

Aquí hay un borrador de una implementación globalize(::Module, ast) : https://gist.github.com/stevengj/255cb778efcc72a84dbf97ecbbf221fe

De acuerdo, he descubierto cómo implementar una función globalize_include_string que conserva la información del número de línea y la he agregado a mi esencia .

Un posible camino a seguir (sin interrupciones), si a las personas les gusta este enfoque:

  1. Lanza un paquete SoftGlobalScope.jl con las funciones globalize etc.
  2. Utilice SoftGlobalScope en IJulia (y posiblemente Juno, vscode y OhMyREPL).
  3. Incorpore las funciones de SoftGlobalScope a una versión futura del paquete REPL stdlib y utilícelo en REPL.

¿O es práctico implementarlo en REPL.jl inmediatamente? No tengo muy claro cómo funcionan las actualizaciones de stdlib en 1.0.

Por favor, eche un vistazo a mi implementación, en caso de que me falte algo que lo haga frágil.

¿No podemos tenerlo como una característica no predeterminada del REPL en 1.1?

Duplicado de # 28523 y # 28750. Para aquellos que dicen que no quieren enseñar a la gente sobre las variables globales, sugiero enseñar las funciones primero, antes de los bucles for . Las funciones son más fundamentales de todos modos, y esto ayudará a establecer la expectativa de que el código debe escribirse en funciones. Si bien entiendo el inconveniente, este comportamiento de alcance se puede convertir en una ventaja pedagógica: "De hecho, las variables globales son una mala idea, particularmente si las usas en bucles, que el lenguaje te hace esforzarte al máximo para usarlas".

Sin embargo, me parece bien agregar una función no predeterminada al REPL para esto.

@JeffBezanson , recuerde que a muchos de nosotros nos gustaría usar a Julia como un sustituto de Matlab, etcétera, en cursos técnicos como álgebra lineal y estadística. Estos no son cursos de programación y los estudiantes a menudo no tienen experiencia en programación. Nunca hacemos programación estructurada; casi todo es interactivo con fragmentos cortos y variables globales.

Además, la razón por la que estoy usando un lenguaje dinámico en primer lugar es para cambiar con fluidez entre la exploración interactiva y una programación más disciplinada. La incapacidad de usar el mismo código en un contexto global y funcional es un obstáculo para ese fin, incluso para alguien que está acostumbrado a los conceptos de alcance, y es mucho peor para los estudiantes que no tienen antecedentes de informática.

recuerde que a muchos de nosotros nos gustaría utilizar a Julia como sustituto de Matlab, etcétera, en cursos técnicos como álgebra lineal y estadística. Estos no son cursos de programación y los estudiantes a menudo no tienen experiencia en programación. Nunca hacemos programación estructurada; casi todo es interactivo con fragmentos cortos y variables globales.

Muchos de los usuarios de Julia no tenemos experiencia en informática (incluyéndome a mí), pero me parece que la actitud adecuada ( especialmente para los estudiantes) es la voluntad de aprender en lugar de exigir que las cosas se cambien para peor para adaptarse a nuestra ingenuidad.

Ahora, no estoy necesariamente implicando que este cambio en particular sería para peor ya que sólo tengo una comprensión limitada de lo que está pasando aquí, pero si se da el caso de que esta es una complicación significativa o lo hace demasiado fácil escribir innecesariamente código con mal desempeño, no parece que valga la pena hacer un cambio para tener un mejor ejemplo de lectura. No puede cambiar las leyes de la física para que los ejemplos de electrostática que le muestre a los estudiantes de primer año sean más aplicables a la vida real.

Entonces, mi pregunta como un usuario que no es de CS y que también se preocupa por el rendimiento es ¿cómo podría equivocarme si esto se convirtiera en el comportamiento predeterminado? ¿Es literalmente solo el tipo de ejemplos que estamos viendo aquí los que son un problema (del que ya era consciente), o es probable que a menudo lo arruinemos de manera más sutil?

Por lo que vale, estoy de acuerdo en que hacer que el código se comporte de manera diferente dependiendo de su alcance adjunto es una característica generalmente indeseable.

Hacer que el código sea más difícil de escribir de forma interactiva, obligar a los principiantes a escribir sus primeros bucles a comprender reglas de alcance oscuras y hacer que el código pegado desde funciones no funcione en alcances globales no ayuda a los programadores a escribir código rápido en funciones. Solo hace que sea más difícil usar a Julia de forma interactiva y más difícil para los principiantes.

¿No podemos tenerlo como una característica no predeterminada del REPL en 1.1?

Hacer que una opción de "deshacerme" sea la predeterminada parece más prudente, especialmente una opción que está dirigida directamente a los usuarios principiantes. Si se trata de una opción no predeterminada, precisamente aquellas personas que más la necesiten serán las que no la tengan habilitada (y no sepan que existe).

¿Qué haría el modo REPL propuesto con los scripts include ed? ¿Dependería la evaluación de las declaraciones globales de si el modo REPL está activado? Si es así, en mi opinión, esto estaría en desacuerdo con la promesa de estabilidad 1.0.

Si hiciéramos algo como esto, parece que podría tener sentido que el módulo determine cómo funciona. Entonces Main sería un módulo de "alcance flexible" mientras que, por defecto, otros módulos serían módulos de "alcance rígido".

Estaba interesado en ver si era posible parchear el REPL para usar la función globalize @stevengj y parece que es sin demasiado esfuerzo (aunque bastante hacky). Vea la esencia . Esto no funciona con Juno (o cualquier otra cosa que llame directamente a Core.eval ).

No voy a recomendar esto a la gente, pero es muy útil para mí cuando se hace el análisis de datos rápido y sucio. Me gustaría mucho ver una solución (mejor pensada), ya que realmente es bastante confuso para los codificadores sin experiencia y, a menudo, reacios (es decir, mis estudiantes) cuando no puede copiar y pegar el código de una función en el REPL para ver lo que hace y viceversa.

julia> a = 0                                                                
0                                                                           

julia> for i = 1:10                                                         
         a += i                                                             
       end                                                                  
ERROR: UndefVarError: a not defined                                         
Stacktrace:                                                                 
 [1] top-level scope at .\REPL[2]:2 [inlined]                               
 [2] top-level scope at .\none:0                                            

julia> using SoftGlobalScope                                                
[ Info: Precompiling SoftGlobalScope [363c7d7e-a618-11e8-01c4-4f22c151e122] 

julia> for i = 1:10                                                         
         a += i                                                             
       end                                                                  

julia> a                                                                    
55                                                                          

(Por cierto: ¡lo anterior es casi tantas pruebas como lo ha hecho!)

¿Qué haría el modo REPL propuesto con los scripts incluidos?

Nada. Básicamente, la propuesta es que esto solo sea para el código ingresado en un mensaje interactivo. Tan pronto como empiece a poner cosas en archivos, necesita aprender las reglas de "alcance estricto". Con suerte, cuando empiece a poner código en archivos, debería empezar a utilizar funciones.

No es ideal que haya reglas de alcance más exigentes para el código global en los archivos que en el indicador. Pero creo que # 19324 combinado con la promesa de estabilidad de Julia 1.0 nos deja sin opciones ideales.

@stevengj :

@JeffBezanson , recuerde que a muchos de nosotros nos gustaría usar a Julia como un sustituto de Matlab, etcétera, en cursos técnicos como álgebra lineal y estadística. Estos no son cursos de programación y los estudiantes a menudo no tienen experiencia en programación. Nunca hacemos programación estructurada; casi todo es interactivo con fragmentos cortos y variables globales.

Habiendo enseñado cursos utilizando Julia a estudiantes con exposición previa a Matlab / R / ..., simpatizo con esta preocupación. Pero al mismo tiempo, no creo que usar a Julia solo como un sustituto de Matlab, etc. sea un enfoque viable: como lo demuestran innumerables veces las preguntas en Discourse y StackOverflow, esto puede conducir a problemas de rendimiento que son difíciles de solucionar y comprender. posiblemente conlleva un costo aún mayor que invertir en comprender en qué se diferencia Julia de estos otros idiomas (consulte las publicaciones con los temas "Traduje este código de Matlab y es 10 veces más lento").

Creo que la cuestión clave es el fracaso silencioso ; el problema en es fácil de entender y solucionar. Sugeriría mantener el nuevo comportamiento, pero dando una advertencia en Main (por defecto; debería ser posible deshabilitarlo).

Para mí, el problema más importante es la inconsistencia percibida. Es decir, estoy de acuerdo con que Julia haga las cosas de manera diferente, pero:

  • ¿Por qué el código pegado desde una función no debería funcionar en un REPL?
  • Ningún otro idioma que haya usado tiene este comportamiento, y es otra barrera para la adopción.
  • ¿Por qué los bloques for se comportan de manera diferente a los bloques begin y if ? ( if alguna manera entiendo, pero un bloque es [debería ser] un bloque).

Con respecto a la viñeta 2, creo que este es un asunto más importante de lo que nosotros, que hemos estado usando a Julia durante un tiempo (y estamos comprometidos con el lenguaje), podríamos entender. Puedo decirles que actualmente soy 0 de 7 para convencer a mi grupo de que escriba código en Julia; dos de ellos se debieron a este problema de bucle for que no pude explicar porque no había estado expuesto a él antes. El resto creo que podemos atribuirlo a mi falta de carisma.

Mi preferencia sería asegurarme de que el código pegado desde una función en un REPL se comporte de manera idéntica a la función, y que los bucles for hagan lo esperado cuando los utilicen para analizar datos de forma interactiva; es decir, específicamente, que mutan variables externas / globales cuando se dirigen sin palabras clave especiales.

No creo que usar a Julia solo como un sustituto de Matlab, etc. sea un enfoque viable: como lo demuestran innumerables veces las preguntas en Discourse y StackOverflow, esto puede conducir a problemas de rendimiento que son difíciles de solucionar y comprender, lo que posiblemente implica un costo aún mayor. que invertir en comprender en qué se diferencia Julia de estos otros idiomas.

Lo siento, pero este argumento me resulta ridículo. No me refiero a clases en las que enseño programación. Hay un lugar para cálculos interactivos simples, y en las clases que no son de CS es común comenzar con los lenguajes de programación como una "calculadora glorificada". Enseñar computación de rendimiento en Julia es un proceso completamente diferente, pero no está de más si ya han estado usando a Julia como su "calculadora".

Si comienza presentando a los estudiantes a Matlab como su "calculadora", es mucho más difícil hacer la transición a la programación "real", porque su primer instinto es hacer todo lo posible con Matlab antes de abandonar el barco, momento en el que sus malos hábitos están arraigados y son reacios a aprender un nuevo idioma. Por el contrario, si comienza con Julia como su calculadora glorificada, cuando llega el momento de hacer una programación más seria, tiene una gama mucho más amplia de opciones disponibles. No es necesario entrenarlos para que apilen todo en operaciones "vectoriales" ni obligarlos a hacer las cosas mal antes de que lo hagan bien.

¿Estás diciendo que no debería usar a Julia en mi curso de álgebra lineal ? ¿O que solo debería usarlo si estoy preparado para enseñar ciencias de la computación además de álgebra lineal?

Estoy de acuerdo con @stevengj tanto en el problema (enseñar a los no programadores se vuelve mucho más difícil) como en la solución (hacer que las cosas funcionen en el REPL y los distintos IDE). Incluir un script aún tendría las reglas de alcance de Julia 1.0, pero eso es menos preocupante, solo hay que tener cuidado de tener el "podemos poner nuestro bucle for en una función y luego llamar a la función" clase antes de "podemos poner nuestro bucle for en un archivo e incluye el archivo "class.

Esto suena como un buen compromiso, ya que la depuración interactiva en el REPL no se vuelve más dolorosa de lo necesario (o más confusa para los nuevos usuarios), mientras que el código normal en los scripts tiene que seguir reglas estrictas de alcance y está a salvo de errores que sobrescriban algunos variables accidentalmente.

¿Estás diciendo que no debería usar a Julia en mi curso de álgebra lineal? ¿O que solo debería usarlo si estoy preparado para enseñar ciencias de la computación además de álgebra lineal?

Es posible que haya entendido mal lo que estaba diciendo (o no lo expresé claramente). Estaba hablando de cursos que utilizan a Julia para enseñar algo en un dominio específico (por ejemplo, enseñé métodos numéricos a estudiantes de posgrado en economía), no cursos de informática (con los que no tengo experiencia).

El punto que estaba tratando de hacer es que es razonable esperar un cierto nivel de diferencia entre Julia y el lenguaje X (que puede ser Matlab); a la inversa, ignorar esto puede (y lo hace) generar problemas.

Personalmente, cuando estoy aprendiendo un nuevo idioma, prefiero enfrentar estos problemas desde el principio; Además, creo que la simplicidad y la coherencia de la semántica del lenguaje es más importante que la similitud con otros lenguajes a largo plazo. Pero reconozco estas preferencias como subjetivas y las personas razonables pueden tener otras diferentes.

He creado el paquete (no registrado) https://github.com/stevengj/SoftGlobalScope.jl

Si esto parece razonable, puedo seguir adelante y registrar el paquete y luego usarlo de forma predeterminada en IJulia (y quizás enviar PR a Juno, etcétera).

El punto que estaba tratando de hacer es que es razonable esperar un cierto nivel de diferencia entre Julia y el lenguaje X (que puede ser Matlab)

Obviamente. Cuando digo "use Julia en lugar de Matlab", no me refiero a que esté tratando de enseñarles la sintaxis de Matlab en Julia, ni me dirijo específicamente a antiguos usuarios de Matlab.

Prefiero enfrentar estos problemas desde el principio

No se trata de diferencias con Matlab per se. Realmente preferiría no hablar sobre el alcance global vs local y la utilidad de una palabra clave global para el análisis estático la primera vez que escribo un bucle frente a estudiantes que no son de informática, o la primera vez que pegan código de un funcionar en el REPL para probarlo de forma interactiva. Prefiero concentrarme en las matemáticas que estoy tratando de usar para expresar el bucle.

Nadie aquí aboga por un alcance interactivo suave solo porque eso es lo que esperan los usuarios de Matlab. digresiones hacia el concepto desconocido de "alcance" seguramente descarrilarán cualquier conferencia que no sea de CS en la que muestre un bucle por primera vez. (E incluso para los usuarios experimentados, es bastante inconveniente verse obligados a agregar global palabras clave cuando trabajamos de forma interactiva).

Otra solución que no se menciona aquí es simplemente dejar de hacer 'para' definir un bloque de alcance (solo funcionar y dejar crearía un nuevo alcance)

@vtjnash , prefiero centrar esta discusión en cosas que podemos hacer antes de Julia 2.0. Sin embargo, estoy de acuerdo en que hacer que el modo interactivo se comporte de manera diferente es solo una solución provisional, y deberíamos contemplar seriamente cambiar las reglas de alcance en unos pocos años.

Buen punto, esto también necesita import Future.scope 😀

(Creo que este módulo / espacio de nombres / efecto de comportamiento ya está reservado / existe)

Como recordatorio aquí, el cambio fue para asegurar que el código se comporte de la misma manera en todos los entornos de alcance global, independientemente de qué más se haya evaluado previamente en ese módulo. Antes de este cambio, podía obtener respuestas completamente diferentes (como resultado de una asignación de alcance diferente) simplemente ejecutando el mismo código dos veces o moviéndolo en un archivo.

Antes de este cambio, podía obtener respuestas completamente diferentes (como resultado de una asignación de alcance diferente) simplemente ejecutando el mismo código dos veces o moviéndolo en un archivo.

La cantidad de quejas que vi sobre eso en la práctica (cero) seguramente será eclipsada por la cantidad de quejas y confusión que verá (y ya está viendo) sobre el comportamiento actual.

Antes de este cambio, podía obtener respuestas completamente diferentes (como resultado de una asignación de alcance diferente) simplemente ejecutando el mismo código dos veces

¿Quiere decir que en el siguiente código, a cambia entre el primer y el segundo bucle for ? En mi opinión, ese es el comportamiento esperado, no un error.

a = 0
for i = 1:5
  a += 1
end

for i = 1:5
  a += 1
end

¿Qué haría el modo REPL propuesto con los scripts incluidos?

@ mauro3 @stevengj ¿Supongo que agregar una función (digamos, exec("path/to/script.jl") ) se puede hacer con solo un aumento de versión menor? También podemos advertir a exec 'ing otro archivo del script exec ' ed y luego poner algunos mensajes pedagógicos allí para empujarlos a usar include .

Algunos pensamientos que escribí anoche mientras trataba de entender este tema (una vez más) para tratar de averiguar cuál podría ser el mejor curso de acción. No hay conclusión, pero creo que esto expone el problema con bastante claridad. Después de haber pensado en este tema durante algunos años, no creo que exista una "solución ideal"; este puede ser uno de esos problemas en los que solo hay opciones subóptimas.


La gente ve ingenuamente el alcance global como un tipo divertido que encierra el alcance local. Esta es la razón por la que los ámbitos globales funcionaron de la manera en que lo hicieron en Julia 0.6 y versiones anteriores:

  • Si un ámbito local externo crea una variable local y se le asigna un alcance local interno, esa asignación actualiza la variable local externa.
  • Si un alcance global externo crea una variable global y un alcance local interno le asigna, entonces esa asignación actualizó previamente la variable global externa.

Sin embargo, la principal diferencia es:

  • Si existe una variable local externa, por diseño, no depende del orden de aparición o ejecución de las expresiones en el ámbito local externo.
  • Sin embargo, si existe una variable global, no puede ser independiente del orden, ya que se evalúan las expresiones en el ámbito global, una a la vez.

Además, dado que los ámbitos globales a menudo son bastante largos (no pocas veces se distribuyen en varios archivos), hacer que el significado de una expresión dependa de otras expresiones a una distancia arbitraria de ella es un efecto de "acción espeluznante a distancia" y, como tal, bastante indeseable. .


Esta última observación muestra por qué es problemático que las dos versiones diferentes de un bucle for en el ámbito global se comporten de manera diferente:

# file1.jl
for i = 1:5
  a += 1
end
# file2.jl
a = 1



md5-f03fb9fa19e36e95f6b80b96bac9811e



```jl
# main.jl
include("file1.jl")
include("file2.jl")
include("file3.jl")

También tenga en cuenta que los contenidos de file1.jl y file3.jl son idénticos y podríamos simplificar el ejemplo al incluir el mismo archivo dos veces con un significado y comportamiento diferente cada vez.

Otro caso problemático es una sesión REPL de larga duración. ¿Prueba un ejemplo de algún lugar en línea? Falla porque tiene una variable global con el mismo nombre que el ejemplo usa para una variable local en un bucle for o una construcción similar. Entonces, la noción de que el nuevo comportamiento es el único que puede causar confusión definitivamente no es precisa. Estoy de acuerdo en que el nuevo comportamiento es un problema de usabilidad en el REPL, pero solo quiero moderar la conversación y presentar el otro lado claramente aquí.

Mi pequeña sugerencia, que no se ocupa del problema de las respuestas, pero sería útil para fines didácticos cuando se enseña el idioma de forma no interactiva, al menos: definir un bloque principal llamado "programa", como se puede hacer en fortran (es el igual que el "dejar ... terminar" anterior, solo que con una notación más natural):

prueba del programa
...
fin

se podría enseñar el idioma sin entrar en los detalles del alcance y solo eventualmente discutir ese punto.

Otro caso problemático es una sesión REPL de larga duración. ¿Prueba un ejemplo de algún lugar en línea? Falla porque tiene una variable global con el mismo nombre que el ejemplo usa para una variable local en un bucle for o una construcción similar.

¿Cuántas quejas de listas de correo y problemas de github se han presentado sobre esto por usuarios molestos? Cero, según mi cuenta. ¿Por qué? Probablemente porque este comportamiento no es fundamentalmente sorprendente para las personas: si trabaja en un ámbito global, depende del estado global.

Entonces, la noción de que el nuevo comportamiento es el único que puede causar confusión definitivamente no es precisa.

Creo que esta es una falsa equivalencia: hay una gran disparidad en el nivel de confusión potencial aquí. En Julia 0.6, podría explicar su ejemplo a un estudiante en segundos: "Oh, mira que este ciclo depende de a , que cambiaste aquí". En Julia 1.0, honestamente me preocupa lo que haré si estoy en medio de una conferencia de álgebra lineal y tengo que escribir misteriosamente una palabra clave global frente a estudiantes que nunca han escuchado la palabra "alcance" en el sentido de CS.

deberíamos contemplar seriamente cambiar las reglas de determinación del alcance en unos pocos años.

Absolutamente no. ¿De verdad quieres volver al mundo anterior a la v0.2 (ver # 1571 y # 330) del alcance del bucle?

En realidad, nunca hemos admitido completamente copiar y pegar código de una función línea por línea en el REPL. Entonces podemos ver esto como una oportunidad para hacer que eso funcione. Específicamente, aunque "funcionó" para for bucles, no funcionó para funciones internas:

x = 0
f(y) = (x=y)

Dentro de una función, f mutará el x de la primera línea. En el REPL no lo hará. Pero con una transformación como esa en SoftGlobalScope.jl podría funcionar. Por supuesto, probablemente no querríamos que eso estuviera activado de forma predeterminada, ya que entonces pegar definiciones de funciones independientes no funcionaría. Lo primero que me viene a la mente es un modo REPL para la depuración de funciones línea por línea.

¿De verdad quieres volver al mundo anterior a la versión 0.2?

No, quiero volver al mundo 0.6. 😉

Supongo que estaba respondiendo más a:

Otra solución que no se menciona aquí es simplemente dejar de hacer 'para' definir un bloque de alcance

En realidad, nunca hemos admitido completamente copiar y pegar código de una función línea por línea en el REPL. Entonces podemos ver esto como una oportunidad para hacer que eso funcione.

Aprecio mucho este sentimiento y para mis casos de uso sería de gran ayuda. Desde mi perspectiva, se trata realmente de hacer que el REPL sea lo más útil posible en lugar de cambiar las reglas de alcance del lenguaje directamente.

Dicho esto, cuanto más pienso en este problema, más veo las opiniones contradictorias que tengo (personalmente) sobre lo que debería hacer el REPL.

Para ser concreto, me gustaría mucho que el REPL coincidiera con las reglas de alcance de un cuerpo de función; es decir, las variables son locales en lugar de globales y puede copiar y pegar código directamente desde una función y saber que funcionará. Me imagino que una implementación ingenua sería algo así como dejar-bloquear el ajuste (como se mencionó anteriormente) del formulario

julia> b = a + 1

siendo transformado en

let a = _vars[:a]::Float64 # extract the variables used from the backing store
    # Code from the REPL
    b = a + 1
    # Save assigned variables back to the backing store
   _vars[:b] = b
end

Realizado correctamente (es decir, por alguien que sepa lo que está haciendo), imagino que esto tendría una serie de beneficios sobre el REPL existente. 1. Los flujos de trabajo anteriores con análisis / cálculo de datos interactivos simplemente funcionan. 2. muchas menos publicaciones en Discourse donde la respuesta básica es "dejar de hacer evaluaciones comparativas con variables globales" - ¡todo sería local y, con suerte, rápido! :) 3. Copiar y pegar en / desde el cuerpo de una función funciona como se esperaba. 4. una función similar a workspace() es trivial si la tienda de respaldo es una especie de Dict; solo límpialo. 5. los globales se vuelven explícitos: las cosas son locales a menos que pida específicamente que sean globales; esto es una gran ventaja desde mi perspectiva, no me gusta crear implícitamente globales. Un punto final muy menor (¡y dudo en agregar esto!), Esto coincidiría con el comportamiento de Matlab, lo que facilitaría la transición de las personas: en Matlab REPL, todas las variables parecen ser locales a menos que se anoten explícitamente como globales.

Hasta hace unas horas esta historia me sonaba genial. Pero después del comentario de Jeff sobre las funciones, pensé en pegar definiciones de funciones independientes y cómo este enfoque básicamente evitaría eso, ya que las definiciones de funciones deberían ir en el ámbito global (al menos, eso es probablemente lo que se pretende); pero entonces lo que si estuvieran destinados a entrar en el ámbito local (una función interna)? No hay información para eliminar la ambigüedad de las dos posibilidades. Parecería que se necesitan dos modos REPL, uno con alcance local y otro con alcance global. Por un lado, puede resultar muy confuso (imagina las publicaciones del Discurso ...) pero, por otro, puede resultar de gran utilidad. (Tener ambos modos REPL también sería ininterrumpido ya que solo está introduciendo una nueva funcionalidad :))

Ir a la casa intermedia de SoftGlobalScope.jl podría terminar siendo el compromiso menos confuso, pero mi preocupación es que es solo otro conjunto de reglas para recordar (qué cosas funcionan en el REPL pero no en mi cuerpo de función / alcance global y viceversa).

Disculpas por la publicación larga, pero creo que esto es importante para la usabilidad (¡y me ayudó a pensarlo bien!).

¿Cuántas quejas de listas de correo y problemas de github se han presentado sobre esto por usuarios molestos? Cero, según mi cuenta. ¿Por qué? Probablemente porque este comportamiento no es fundamentalmente sorprendente para las personas: si trabaja en un ámbito global, depende del estado global.

Hmm, ¿realmente hiciste un estudio sistemático de esto? Debo haber perdido eso. Sin embargo, esto no significa que este comportamiento no sea una fuente de errores o resultados inesperados; solo que después de que el usuario lo haya descubierto, se reconoció como un comportamiento correcto y, por lo tanto, no generó un problema / queja.

En Julia 1.0, estoy honestamente preocupado por lo que haré si estoy en medio de una conferencia de álgebra lineal y tengo que escribir misteriosamente una palabra clave global

Simpatizo con este problema. Cuando enseñé sobre programación simple a estudiantes de economía necesaria para un curso, generalmente sugería que iban y venían entre el código envolvente en funciones y simplemente comentando function y end y ejecutando cosas en el ámbito global, para que puedan inspeccionar lo que está sucediendo. Esto prácticamente compensó la falta de infraestructura de depuración en ese momento en Julia.

Parece que este enfoque ya no es factible. Pero me pregunto si realmente fue la forma correcta de hacerlo de todos modos, y mientras tanto, varias cosas han mejorado mucho (# 265 se corrigió, Revise.jl y recientemente Rebugger.j han mejorado considerablemente el flujo de trabajo / depuración).

Parece que este tema no molesta mucho a los usuarios experimentados, la principal preocupación es la confusión en un entorno pedagógico. Todavía no he experimentado con esto, pero me pregunto si podríamos adaptar nuestros enfoques a la enseñanza en su lugar, por ejemplo, introducir funciones antes de los bucles, evitar los bucles en el alcance global. De todos modos, estos son elementos de buen estilo y beneficiarían a los estudiantes.

Solo una pequeña nota: mientras que el alcance global del REPL se aplica en mayúsculas y minúsculas, permitirá copiar y pegar código en y desde funciones, no permitirá copiar y pegar en / desde el alcance global de otro módulo.

Me pregunto si podríamos adaptar nuestros enfoques a la enseñanza en su lugar, por ejemplo, introducir funciones antes de los bucles, evitar los bucles en el ámbito global.

Esto es totalmente impráctico en una clase que no se centra en la enseñanza de la programación. También podría no usar a Julia en mis clases si no puedo usarla de forma interactiva y / o tengo que escribir funciones para todo primero.

(Y no es solo pedagógico. Los bucles en el alcance global son útiles para el trabajo interactivo. Y una de las principales razones por las que a las personas les gustan los lenguajes dinámicos para la informática técnica es su facilidad para la exploración interactiva. No toda la codificación está orientada al rendimiento).

Ha habido docenas de hilos y problemas a lo largo de los años en los que las personas se confunden o se quejan de la antigua distinción de "alcance suave / duro", por lo que afirmar que nadie se ha confundido ni se ha quejado nunca del comportamiento anterior es simplemente ... no es verdad. Podría desenterrar algunos de ellos, pero tú estabas cerca, @stevengj , así que puedes desenterrarlos con la misma facilidad y me cuesta creer que no hayas notado o no recuerdes estas quejas y conversaciones.

@StefanKarpinski , me refiero específicamente a las personas que se quejan de que un bucle global depende del estado global. No recuerdo que nadie se quejara de que se trataba de un mal comportamiento, ni puedo encontrar ningún ejemplo de esto.

Estoy de acuerdo en que la gente ha estado confundida sobre cuándo y dónde la asignación define nuevas variables, pero generalmente ha sido en la otra dirección: querían que los ámbitos locales actuaran de manera más global (en lugar de viceversa), o que no tuvieran una distinción entre begin y let . IIRC, la queja nunca fue que la asignación a una variable global en un bucle global tuviera el sorprendente efecto secundario de modificar un global.

Todo el tema del alcance es confuso para los nuevos usuarios, y seguirá siéndolo. Pero la parte confusa no fueron los casos en los que la asignación a un nombre de variable global afectó al estado global. El comportamiento actual empeora esto, no mejora.

@StefanKarpinski : Tengo la sensación de que anteriormente, la confusión con el alcance suave / duro era más de naturaleza teórica (de personas que leían el manual) que de práctica (de personas que obtenían resultados inesperados). Definitivamente así fue para mí y lo que, por ejemplo, los resultados de búsqueda aquí respaldan esto; Encontré un contraejemplo aquí .

Por otro lado, este nuevo comportamiento no confundirá a las personas al leer el manual, sino al usar REPL. Podría decirse que este último es peor.

SoftGlobalScope.jl ahora es un paquete registrado. Mi intención es habilitarlo de forma predeterminada (exclusión voluntaria) para IJulia, al menos este semestre.

@ mauro3 , incluso tu "contraejemplo" se trata de alguien confundido por el alcance

Me gustaría señalar que IJulia tiene la interesante posibilidad de hacer bloques locales de variables por defecto. Es decir, si haces esto en un solo bloque, entonces funciona:

t = 0
for i = 1:n
    t += i
end
t

... y t solo es visible dentro de este bloque de evaluación. Si quisiera que fuera visible en el exterior, tendría que hacer esto:

global t = 0
for i = 1:n
    global t += i
end
t

También he considerado un enfoque similar para Julia donde los bloques son archivos en lugar de módulos. En otras palabras, simplemente hacer t = 0 en el alcance superior crea una variable que es local de archivo en lugar de global. Para declarar una variable verdaderamente global, necesitaría escribir global t = 0 que luego sería visible en todo el módulo. Quizás demasiado extraño, pero se me ha ocurrido muchas veces a lo largo de los años.

IJulia tiene la interesante posibilidad de hacer que las variables locales hagan bloques por defecto

@StefanKarpinski , creo que esto sería aún más confuso y sería contrario a cómo se usan normalmente los portátiles. Es común que la misma variable se use / modifique en múltiples celdas, por lo que requerir una palabra clave global para todas las variables entre celdas no es un principio para mí; requeriría aún más discusión sobre los conceptos de alcance que el problema con for bucles que hemos estado discutiendo aquí.

Creo que mientras todos estemos de acuerdo, como parece que estamos, en que esto es mayor o totalmente un tema de interacción, entonces tenemos un camino a seguir. Si hacemos un caso especial en el REPL (como se está haciendo para IJulia), el único caso malo es desarrollar algo en el REPL y luego moverlo al código de script de nivel superior. Podría decirse que ese es el punto en el que debería introducir funciones, así que no creo que sea tan malo. El código de copiar y pegar entre el REPL y los cuerpos de las funciones funcionará (en su mayoría), lo que probablemente sea lo suficientemente bueno.

Luego también tenemos la opción de justificar / aclarar aún más la distinción haciendo que las variables REPL de alguna manera sean locales al REPL --- es decir, no variables globales normales, no disponibles como Main.x . Esto es muy similar a lo que @StefanKarpinski acaba de proponer anteriormente, pero compartido entre todos los bloques / celdas de entrada.

Desde un punto de vista práctico, conseguir esto "arreglado" en el REPL no es
solo es importante para usuarios docentes / no programadores. Este comportamiento también
hace que la depuración interactiva a través de REPL (copiando y pegando partes) sea muy
unpráctico. Este modo de depuración a veces puede ser preferible (incluso para un
buen depurador e) incluso para programadores experimentados (y a menudo es uno de los
las razones para preferir un lenguaje dinámico). Por supuesto para los experimentados
programadores, ser opcional no debería ser un problema; Para usuarios novatos
sería preferiblemente el predeterminado.

@StefanKarpinski
Como programador ingenuo, realmente no veo qué hay de malo en ver el
alcance global como un tipo divertido que encierra el alcance local, especialmente en
Idiomas. Entiendo que desde el punto de vista del compilador no es
necesariamente correcto (en Julia), pero es un modelo agradable, fácil y útil
para un programador (ingenuo). (También sospecho que podría implementarse de esa manera en
algunos idiomas).
Julia también parece presentárselo así al programador:
La siguiente función función dará el error "no definido", que
no funcionará si se coloca a = 1 antes del ciclo for.

prueba de funcionamiento()
para i = 1:10
a = a + i
fin
a = 1
@show a
fin

que, a menos que no haya entendido completamente, parece estar en desacuerdo con "Si un
La variable local externa existe, por diseño, no depende del orden de
aparición o ejecución de las expresiones en el ámbito local externo ".

Estoy muy de acuerdo con evitar "acciones espeluznantes a distancia", y mucho
prefiero una definición explícita para usar globales en la función / pila de llamadas
nivel y personalmente también me gustaría tener algo como cargar desde un archivo en
su propio alcance y requiere una definición explícita para el uso de variables globales.
Sin embargo, en el nivel de los bucles va demasiado lejos para mí, ya que
las definiciones / contexto suelen estar bastante cerca.
El ejemplo de 3 archivos es un poco artificial (y falla con el esperado "no
definido "error): normalmente colocaría la definición inicial en el mismo
expediente.
Hay un peligro aterrador real en esto (y me ha mordido
en otros idiomas) que incluye se ejecutan en el ámbito global, por lo que
inadvertidamente definen una variable global que puede interferir con otras
código. Sin embargo, tener que usar global in the loop no es una solución para
este problema.

wrt a la sesión REPL de larga duración:
El comportamiento actual reemplaza un modo de falla muy raro y fácil de detectar.
para ejecutar un ejemplo en línea en el REPL (se pierde copiar / pegar el
definición inicial de la variable antes del bucle, y ya tiene la
misma variable definida globalmente a partir de algo anterior) sin ser
capaz de ejecutar un ejemplo en línea correctamente si es parte de una función
(sin agregar global en todas partes), y no resolver el problema si es
no (si global ya está allí en el código en línea, aún usará el
valor incorrecto en la variable global ya existente)

Debería haberme sintonizado con esto antes, pero después de un breve momento de preocupación, todo parece ir bien.

En realidad, nunca hemos admitido completamente copiar y pegar código de una función línea por línea en el REPL ... Lo primero que me viene a la mente es un modo REPL para la depuración de funciones línea por línea.

De hecho, Rebugger (que es exactamente eso) funciona correctamente en 1.0 solo porque carece de la depreciación del alcance de 0.7, y nunca se podría hacer que funcione en 0.6. Sin embargo, me complace poder verificar que SoftGlobalScope.jl parece no romper eso. Por ejemplo, si te adentras lo suficiente en show([1,2,4]) , obtienes aquí:

show_delim_array(io::IO, itr::Union{SimpleVector, AbstractArray}, op, delim, cl, delim_one) in Base at show.jl:649
  io = IOContext(Base.TTY(RawFD(0x0000000d) open, 0 bytes waiting))
  itr = [1, 2, 4]
  op = [
  delim = ,
  cl = ]
  delim_one = false
  i1 = 1
  l = 3
rebug> eval(softscope(Main, :(<strong i="10">@eval</strong> Base let (io, itr, op, delim, cl, delim_one, i1, l) = Main.Rebugger.getstored("bbf69398-aac5-11e8-1427-0158b103a88c")
       begin
           print(io, op)
           if !(show_circular(io, itr))
               recur_io = IOContext(io, :SHOWN_SET => itr)
               if !(haskey(io, :compact))
                   recur_io = IOContext(recur_io, :compact => true)
               end
               first = true
               i = i1
               if l >= i1
                   while true
                       if !(isassigned(itr, i))
                           print(io, undef_ref_str)
                       else
                           x = itr[i]
                           show(recur_io, x)
                       end
                       i += 1
                       if i > l
                           delim_one && (first && print(io, delim))
                           break
                       end
                       first = false
                       print(io, delim)
                       print(io, ' ')
                   end
               end
           end
           print(io, cl)
       end
       end)))
[1, 2, 4]

Entonces funciona bien en 1.0 (con o sin softscope ). En 0.7, evaluar esto (con o sin softscope ) dará como resultado

┌ Warning: Deprecated syntax `implicit assignment to global variable `first``.
│ Use `global first` instead.
└ @ none:0
┌ Warning: Deprecated syntax `implicit assignment to global variable `first``.
│ Use `global first` instead.
└ @ none:0
[ERROR: invalid redefinition of constant first
Stacktrace:
 [1] top-level scope at ./REBUG:9 [inlined]
 [2] top-level scope at ./none:0
 [3] eval(::Module, ::Any) at ./boot.jl:319
 [4] top-level scope at none:0
 [5] eval at ./boot.jl:319 [inlined]
 [6] eval(::Expr) at ./client.jl:399
 [7] top-level scope at none:0

Entonces 0.7 / 1.0 son definitivamente un paso adelante, y si softscope facilita ciertas cosas sin romper una funcionalidad importante, eso es genial.

La mayor preocupación, por lo tanto, es simplemente cómo interceptar esto apropiadamente sin hundir otros paquetes (https://github.com/stevengj/SoftGlobalScope.jl/issues/2).

@timholy , SoftScope no toca los argumentos de las llamadas de macro (ya que no hay forma de saber cómo la reescribiría la macro), por lo que :(<strong i="6">@eval</strong> ...) está protegido.

parece estar en desacuerdo con "Si un
La variable local externa existe, por diseño, no depende del orden de
aparición o ejecución de las expresiones en el ámbito local externo ".

La variable local (externa) a existe, pero aún no se ha asignado. Si el bucle intentó asignar a antes de leerlo, la asignación también sería visible en el exterior.

En general, crear un enlace de variable y asignarle un valor son pasos separados.

¿Cuál es el cronograma de esto? Parece que sería una gran mejora para la usabilidad del usuario. Y en este momento "crítico" de Julia con 1.0 fuera, parecería ventajoso arreglar esto lo antes posible (de la manera sugerida por Jeff arriba) y etiquetar una nueva versión de Julia o una versión REPL. (¡Perdón por este comentario de sillón, ya que ciertamente no lo arreglaré!)

@JeffBezanson
Iba a argumentar que si bien esto es cierto (para la implementación / compilador), el ingenuo programador de julia no puede ver ningún comportamiento diferente de su modelo conceptual más simple (una variable comienza a existir en el momento en que se define). Desafortunadamente, tiene razón, el siguiente código no dará un error, mientras que sí dará un error si omite la a = 2 al final.
prueba de funcionamiento()
para i = 1:10
a = 1
fin
println (a)
a = 2
fin
Explicaré lo desafortunadamente: puedo entender el comportamiento (porque he trabajado con lenguajes compilados antes) pero aún lo encuentro confuso e inesperado. ¿Qué tan malo debe ser para alguien que solo tiene experiencia en scripting o es nuevo en programación? Además, encontré un código que muestra el comportamiento, no veo una aplicación útil (tal vez puedas ayudarme allí)

En el REPL:
Me convencí más de que cambiar el alcance de nuevo a "normal" al menos en el REPL (no es necesario agregar bucles globales) es de alta prioridad: estaba probando algunas cosas en el REPL hoy y (nuevamente) me mordió, tomándose un tiempo para darse cuenta de ello. Dado que sigo a Julia desde hace algún tiempo, me gusta mucho, incluso estoy siguiendo este hilo sobre el problema, incluso lo llamaría un espectáculo: es muy probable que un novato (para Julia) que pruebe el idioma no encuentre resolver el problema y darse por vencido.

@jeffbezanson y yo estamos de vacaciones tan esperadas (no debería estar leyendo esto). Podemos averiguar qué hacer en una semana más o menos.

@derijkp , aunque se agradece la retroalimentación, las reglas de alcance no están

@derijkp Una respuesta corta es que creo que es más fácil si el alcance de una variable corresponde a alguna construcción de bloque (por ejemplo, el cuerpo de una función o bucle). Con su sugerencia, el alcance de una variable sería un subconjunto de un bloque, que creo que en última instancia es más complejo y confuso: no puede apuntar a una forma sintáctica que corresponda al alcance de la variable.

Sí, puedo creer que esto es un desajuste para la intuición de algunas personas. Pero solo puede optimizar durante los primeros diez minutos de uso de un idioma hasta cierto punto. La verdadera pregunta es, ¿qué tan difícil es enseñar / aprender cómo funciona y qué diseño ahorrará tiempo a largo plazo (simplificando el lenguaje, facilitando el desarrollo de herramientas, etc.)?

(de acuerdo con gran parte de lo anterior sobre la modificación del comportamiento del REPL)
Me gustaría ver el REPL de una manera que no conduzca a esta pregunta de stackoverflow
y antes sería mejor ya que muchos ojos nuevos están mirando a Julia

Estoy de acuerdo ... Y también creo que las reglas de alcance no deberían cambiar necesariamente, solo todas las interfaces interactivas (es decir, el control REPL, Jupyter y Juno ingresan)

No se trata solo de que los principiantes aprendan una nueva regla. Si no puede copiar y pegar fragmentos de código en REPL, jupyter, etc. y también en funciones, también es una gran molestia para los programadores intermedios.

Por supuesto, también estoy de acuerdo con los otros carteles ... con los principiantes, tomarán fragmentos de código que ven dentro de las funciones, copiarán I en scripts y se confundirán por completo cuando no tenga el mismo comportamiento cuando se copien dentro de una función. , en juno, el repl, y jupyter. Habrá 100 preguntas de intercambio de pilas que se reducirán al mismo problema. Los programadores intermedios van a tener todo tipo de soluciones de cosecha propia con encapsulado en bloques let , etc., lo que confundirá aún más las cosas.

Habrá 100 preguntas de intercambio de pilas que se reducirán al mismo problema. Los programadores intermedios van a tener todo tipo de soluciones de cosecha propia con encapsulado en bloques let , etc., lo que confundirá aún más las cosas.

Posiblemente, pero en esta etapa esto es hipotético (también el OP de la pregunta vinculada es preguntar sobre el fundamento de la regla de alcance, en lugar de estar confundido al respecto).

Además, aunque respeto la experiencia docente de todos los que tienen preocupaciones sobre esto, si esto resulta ser un gran problema en el aula es algo que el tiempo dirá.

el interrogador parece haber sido confundido por esto: "Me pregunto si esto es intuitivo para los usuarios principiantes de julia. No fue intuitivo para mí ..."

el interrogador parece haber sido confundido por esto:

Sin mencionar que se trata de alguien que claramente sabe lo suficiente sobre lenguajes de programación para comprender los matices del alcance. ¿Qué pasa con todos los usuarios de tipo matlab que son completamente ignorantes de estos temas ..., y probablemente nunca invertirán el tiempo suficiente para comprender los matices?

Posiblemente, pero en esta etapa esto es hipotético.

Ya respondí varias preguntas relacionadas con esto en stackoverflow, en su mayoría por nuevos usuarios, e incluso más en la vida real (la última de ayer, de un usuario de Matlab, que vio esto como un no ir).

Habrá 100 preguntas de intercambio de pilas que se reducirán al mismo problema.

En mi "tiempo libre", he estado agregando etiquetas scope , scoping y global-variables a las preguntas de SE. Solo paro por falta de tiempo, no porque no haya más.

Conclusión después de mucha discusión, incluido el triaje: vamos a incluir algo como SoftGlobalScope en Base y lo usaremos en el REPL y en todos los demás contextos de evaluación interactiva. @JeffBezanson ha señalado que la forma en que esto se implementa es en realidad esencialmente la misma que la forma en que se implementó previamente el alcance suave, por lo que, hasta cierto punto, estamos dando la vuelta al círculo completo. La diferencia es que ahora no hay un comportamiento de alcance en módulos o scripts, solo en contextos similares a REPL. También creo que _explicar_ el alcance suave como una reescritura de la fuente es más claro que tratar de distinguir entre alcances duros y suaves (que nunca sabemos cómo Jeff lo explicó, debo señalar).

Estas dos afirmaciones me confunden un poco porque parecen un poco contradictorias:

y utilizarlo en REPL y todos los demás contextos de evaluación interactiva

no hay comportamiento de alcance en scripts, [...] solo en contextos similares a REPL.

¿Significa esto que el módulo Main tiene a veces un alcance flexible (por ejemplo, en el indicador REPL) y, a veces, un alcance estricto (por ejemplo, cuando julia -L script.jl )? ¿No tendría sentido decir que Main siempre tiene un alcance flexible? ¿Y un módulo puede optar por el alcance flexible por using SoftGlobalScope ?

(Supongo) que las reglas de alcance no se pueden cambiar en los scripts porque serían incompatibles hacia atrás, es decir, romperían la promesa de que cualquier código escrito para 1.0 se ejecutará en cualquier versión 1. *. Sin embargo, tiene razón en que el mismo problema con el alcance del REPL también se aplica a los scripts (el usuario ingenuo no sabe por qué su código no funciona correctamente cuando se ejecuta como un script). Una forma de resolver / aliviar este problema sin mayor incompatibilidad sería agregar una opción a la línea cmd de julia para usar softscope (o una alternativa), por ejemplo, julia -f programfile, y mostrar esta opción en cualquier descripción / tutorial que probablemente un principiante cruzar.
También veo una alternativa potencial para el softscope que puede tener algunas ventajas (aunque probablemente estoy pasando por alto las desventajas): ¿Qué pasaría si un archivo (un script llamado) siempre introdujera su propio alcance local: las reglas de alcance estarían en completa coherencia con las de funciones, y con las expectativas de muchos usuarios. También eliminaría muchas de las responsabilidades de rendimiento con los nuevos usuarios:
No más globales innecesarios (los globales deberían definirse explícitamente), y el código podría compilarse
(¿Cuántas veces ha tenido que decir poner todo en una función y evitar el uso de globales?)

Acabo de llegar a esto y, para ser honesto, me quedé completamente atónito, nunca antes lo había visto en ningún otro idioma. Estoy planeando introducir un curso opcional de Julia para usuarios avanzados de R en mi universidad a finales de este año, una vez que las cosas se hayan calmado, y mis estudiantes llegarán a esto el día 0 cuando comiencen a escribir cosas al azar en el REPL. Y el hecho de que los bucles for se comporten de manera diferente a las declaraciones if simplemente frota sal en la herida, por más lógico que esto pueda ser en términos de alcance. El alcance dentro de las funciones es lo suficientemente difícil para que los estudiantes de biología lo comprendan, la idea de tener que explicar _aunque percibidas_ inconsistencias evidentes en el REPL / en un script / en un bucle for / en una declaración if (porque eso es lo que estamos hablando por aquí) de una manera que es diferente a cualquier otro idioma en la tierra me pone muy triste.

Entiendo la promesa de compatibilidad con versiones anteriores que se hizo, pero tener este trabajo _como lo esperan todas las personas que no son CS en el planeta (y la mayoría de las personas que sospecho que CS) _ parece una corrección de errores en lugar de un problema de compatibilidad con versiones anteriores, no estamos diciendo que cada error se reproducirá para siempre, ¿verdad? La solución REPL es obviamente esencial, por lo que es genial que lo propongas, pero luego tener que explicar que no puedes copiar un script en REPL y esperar que el mismo comportamiento parezca tan malo o peor que el problema original.

Por favor, por favor, piense en tratar esto como una corrección de errores y distribuirlo con scripts y REPL, incluso si hay un cambio para cambiar al comportamiento "antiguo", y hacerlo lo antes posible en 1.0.1.

Un colega que estaba tratando de aprender a Julia también se encontró con esto. Tener que explicar todo el asunto de la variable global vs.local en los primeros pasos no es ideal ...

No creo que tratar esto como una "corrección de errores" esté en las cartas, porque rompería el contrato de estabilidad 1.0. Sin embargo, me parece razonable usar softscope para scripts que se ejecutan con julia -i (es decir, en modo "interactivo").

(Es decir, habría una bandera --softscope={yes|no} y tendría el valor predeterminado de isinteractive .)

Tendremos que considerar la elección del modo de secuencia de comandos.

Para el caso, no es una locura para mí establecer de forma predeterminada --softscope=yes para cualquier "script", es decir, para julia foo.jl , y solo activar las reglas de alcance "estrictas" para los módulos y include (momento en el que debería poner la mayor parte del código en funciones).

Por lo demás, no es una locura para mí establecer de forma predeterminada --softscope = yes para cualquier "script",

Ese. El otro a considerar seriamente es Juno. Recuerde que las personas <shift-enter> través de su código para realizar un desarrollo interactivo (especialmente cuando se trabaja con las pruebas de regresión) y luego esperan poder ejecutar el mismo archivo. ¿Debería importar si el código está en un @testset o no (lo que creo que podría introducir un alcance)? Sería muy confuso para el usuario si el mismo texto cambia cuando está en un @testset cuando se usa la integración de Atom, y es inconsistente con hacer ] test también.

Seguro que me parece que la mejor solución es que el alcance duro es simplemente una opción, donde si todos los demás usos (incluido include dentro de los scripts) usan softscope menos que diga lo contrario .

diferente de cualquier otro idioma en la tierra

¿Quieres escribir var x = 0 para introducir todas las variables? Eso también "arreglaría" esto, y sería más como otros idiomas.

no estamos diciendo que todos los errores se reproducirán para siempre, ¿verdad?

No es así como funciona esto. No puede obtener ningún cambio en el idioma que desea simplemente llamando al comportamiento actual un error.

Realmente no creo que deba haber una opción de línea de comando para esto. Luego, cada parte del código julia tendrá que venir con un comentario o algo que le diga qué opción usar. Algún tipo de directiva de analizador en un archivo fuente sería un poco mejor, pero aún mejor sería tener una regla fija. Por ejemplo, el alcance estricto dentro de los módulos solo puede tener sentido.

Permítanme intentar nuevamente brindar una explicación de esto que podría ser útil para evitar la manía, la histeria y la carnicería que la gente está viendo en el aula:

"
Julia tiene dos tipos de variables: locales y globales. Las variables que introduce en el REPL o en el nivel superior, fuera de cualquier otra cosa, son globales. Las variables introducidas dentro de funciones y bucles son locales. Actualizar variables globales en un programa es generalmente malo, por lo que si está dentro de un ciclo o función y desea actualizar un global, debe ser explícito al escribir nuevamente la declaración global .
"

Quizás eso pueda mejorarse; sugerencias bienvenidas. Lo sé, preferiría no necesitar ningún tipo de explicación. Lo entiendo. Pero no me parece tan malo.

Realmente no creo que deba haber una opción de línea de comando para esto. Luego, cada parte del código julia tendrá que venir con un comentario o algo que le diga qué opción usar. Algún tipo de directiva de analizador en un archivo fuente sería un poco mejor, pero aún mejor sería tener una regla fija

Estoy de acuerdo. Me suena como un dolor de cabeza en la enseñanza y la comunicación.

Por ejemplo, el alcance estricto dentro de los módulos solo puede tener sentido.

Solo para que lo entiendo: si tuviera un script corto (¡no en un módulo!) En un archivo .jl que había copiado de un cuaderno IJulia, entonces si ejecuté ese código en el REPL directamente o shift- ingrese en Juno, entonces se comportaría consistentemente como soft-scope ... pero si lo copiara en lugar de un bloque module , ¿me gritaría sobre globales? Pero si copié ese código dentro de funciones dentro de un módulo, entonces debería funcionar.

Si es así, tiene mucho sentido, es muy fácil de enseñar y coherente. Los scripts de nivel superior son una interfaz interactiva para la exploración, etc. pero nunca pondría ese tipo de código en un módulo. Los módulos son algo que debe llenar con funciones que se consideran globales con mucho cuidado. Sería fácil contarle a la gente sobre esas reglas.

¿Quieres escribir var x = 0 para introducir todas las variables? Eso también "arreglaría" esto, y sería más como otros idiomas.

¡No, preferiría no hacerlo! Pero los lenguajes de secuencias de comandos que tienen un REPL rara vez hacen eso (por ejemplo, ruby, python, R, ...), se comportan como lo hizo Julia v0.6.

Julia tiene dos tipos de variables: locales y globales. Las variables que introduce en el REPL o en el nivel superior, fuera de cualquier otra cosa, son globales. Las variables introducidas dentro de funciones y bucles son locales. Actualizar variables globales en un programa es generalmente malo, por lo que si está dentro de un bucle o función y desea actualizar un global, debe ser explícito al escribir la declaración global nuevamente.

Entiendo completamente lo que estás diciendo aquí, y no volveré a cometer este error (¡tocar madera!). Pero todo el problema que me preocupa no soy yo. Me resultó relativamente fácil introducir el alcance (sin mencionarlo directamente) cuando explico que las variables dentro de las funciones no pueden ver las de fuera y viceversa (¡aunque eso es más una aspiración que un hecho en R!), Porque las funciones en sí mismos son ya un concepto _relativamente_ avanzado. Pero esto golpea mucho antes en la curva de aprendizaje aquí, donde no queremos que nada ni remotamente tan complicado como el alcance afecte a las personas ...

Tenga en cuenta también que no es sólo "_variables que introduce en el REPL o en el nivel superior, fuera de cualquier otra cosa, son globales_" y "_variables introducidas dentro de funciones y bucles son locales_", también es que las variables en declaraciones if en el REPL o en el nivel superior es global pero las variables en un @testset son locales. Terminamos en una madriguera de "simplemente pruébalo y trabaja por ti mismo, ya sea local o global, buena suerte".

Sin embargo, estoy de acuerdo con @jlperla : ¡la propuesta de que "el alcance estricto dentro de los módulos solo podría tener sentido" me parece completamente bien! Los módulos son un concepto suficientemente avanzado nuevamente ... si el alcance suave funciona para REPL y scripts, está absolutamente bien.

no queremos que nada ni remotamente tan complicado como el alcance afecte a las personas ...
en el nivel superior son globales pero las variables en un @testset son locales

A lo que estoy tratando de llegar es a que creo que una descripción simple de global versus local es suficiente para la enseñanza en una etapa temprana; ni siquiera es necesario decir la palabra "alcance" (no ocurre en absoluto en mi explicación anterior). Cuando solo muestra algunas expresiones simples y bucles en el REPL, no está enseñando a las personas sobre conjuntos de pruebas y no necesita una lista exhaustiva del comportamiento de alcance de todo en el idioma.

Mi único punto es que este cambio no hace que de repente sea necesario enseñar muchos detalles sobre el idioma desde el principio. Aún puede ignorar la gran mayoría de las cosas sobre alcances, conjuntos de pruebas, etc., y una simple línea sobre global frente a local debería ser suficiente.

y una simple línea sobre global vs. local debería ser suficiente.

En un mundo en el que todo el mundo empezó a escribir todo su código desde cero, estaría completamente de acuerdo.

El problema es que debe enseñar a los estudiantes no solo sobre el alcance, sino también sobre la comprensión del alcance de dónde copiaron y pegaron el código que obtuvieron. Debe enseñarles que si copian y pegan código que está en stackexchange dentro de una función o un bloque let, deben escanearlo y encontrar dónde agregar "global" si lo están pegando en el REPL o en un .jl archivo. Pero si están copiando ese código dentro de una función o en el cuaderno de Jupyter. no deberían. Y si encuentran código dentro de un intercambio de pila o una página de tutorial que tiene variables globales, pero quieren copiar y modificar ese código dentro de su propia función, entonces necesitan eliminar el global.

Y luego los estudiantes comienzan a preguntarse por qué for crea este alcance del que deben preocuparse, pero no otras cosas ...

Terminamos en una madriguera de "simplemente pruébalo y trabaja por ti mismo, ya sea local o global, buena suerte".

Examen sorpresa: en julia 0.6, es x global o local:

for i = 1:10
    x = i
end

La respuesta es que no hay forma de saberlo, porque depende de si se ha definido antes un x global. Ahora, puedes estar seguro de que es local.

Amigos, esta discusión está a punto de dejar de ser productiva. Jeff sabe muy bien que el comportamiento anterior era bueno en REPL. ¿Quién crees que lo diseñó e implementó en primer lugar? Ya nos hemos comprometido a cambiar el comportamiento interactivo. Aún debe tomarse una decisión sobre si un "guión" es interactivo o no. Suena interactivo cuando lo llama "un script", pero suena mucho menos interactivo cuando lo llama "un programa"; sin embargo, son exactamente lo mismo. Por favor, mantenga las respuestas breves y constructivas y centradas en las cosas que aún deben decidirse. Si hay comentarios que se desvían de esto, pueden estar ocultos y el hilo puede estar bloqueado.

Un pensamiento que tenía, pero lo descartamos por ser "demasiado molesto" y "que probablemente haría que los aldeanos sacaran sus horcas" fue que en contextos no interactivos, podríamos requerir un local o global anotación en "alcance suave". Eso garantizaría que el código de un módulo funcionaría igual si se pegara en el REPL. Si aplicamos eso a "scripts" / "programas", lo mismo sería cierto para ellos.

Cuando conocí a Julia por primera vez (no hace mucho tiempo, y vengo principalmente de Fortran), me enseñaron que "Julia es compilada y rápida a nivel de función, por lo que todo lo que debe ser eficiente debe hacerse dentro de funciones. . En el 'programa' principal se comporta como un lenguaje de scripting ". Encontré eso bastante justo, ya que no puedo imaginar a nadie haciendo algo demasiado exigente computacionalmente sin entender esa afirmación. Por lo tanto, si hay algún sacrificio en el rendimiento del programa principal por usar la misma notación y construcciones que en las funciones, lo encuentro totalmente aceptable, mucho más aceptable que tratar de comprender y enseñar estas reglas de alcance y no poder copiar y pegar códigos de un lugar a otro.

Por cierto, todavía soy un novato en Julia, y lo elegí para enseñar a algunos estudiantes de secundaria y pregrado algunos conceptos básicos de simulaciones de sistemas físicos. Y ya estoy esperando que este problema vuelva al comportamiento 'normal' de versiones anteriores, porque nos da bastante dolor de cabeza.

Esta conversación está bloqueada ahora y solo los confirmadores de Julia pueden comentar.

@JeffBezanson , ¿cuál sería el plan para implementar la semántica que sugirió en este hilo del discurso , inicialmente solo en el REPL y opt-in en otros lugares?

Parece que está planeando poner eso directamente en el código de reducción ( julia-syntax.scm ), en lugar de como una reescritura de sintaxis ala SoftScope.jl? ¿O preferiría tenerlo como reescritura de sintaxis primero (modificando SoftScope a la regla propuesta y convirtiéndolo en un stdlib), y diferir ponerlo en el código de reducción para una versión posterior de Julia?

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

tkoolen picture tkoolen  ·  3Comentarios

iamed2 picture iamed2  ·  3Comentarios

TotalVerb picture TotalVerb  ·  3Comentarios

wilburtownsend picture wilburtownsend  ·  3Comentarios

Keno picture Keno  ·  3Comentarios