Julia: As regras de escopo de variável global levam a um comportamento não intuitivo no REPL / notebook

Criado em 21 ago. 2018  ·  98Comentários  ·  Fonte: JuliaLang/julia

Exemplo 1

Isso surgiu com um aluno que atualizou de 0,6 para 1.0 diretamente, então nunca teve a chance de ver um aviso de reprovação, muito menos encontrar uma explicação para o novo comportamento:

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 

Exemplo 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

Eu "entendo" por que isso acontece no sentido que acho que posso explicar, com referência suficiente aos arcanos no manual sobre o que introduz escopos e o que não, mas acho que isso é problemático para uso interativo.

No exemplo um, você obtém uma falha silenciosa. No exemplo dois, você obtém uma mensagem de erro que é muito inexistente. Isso é mais ou menos comparável a algum código Python que escrevi em um notebook no trabalho hoje.

Não tenho certeza de quais são as regras em Python, mas sei que geralmente você não pode atribuir a coisas no escopo global sem invocar global. Mas no REPL funciona, presumivelmente porque no REPL as regras são diferentes ou a mesma lógica como se todas estivessem no escopo da função é aplicada.

Eu não posso defender as regras o suficiente para propor a mudança concreta que eu gostaria e, com base no Slack, isso nem é necessariamente percebido como um problema por algumas pessoas, então não sei para onde ir com isso, exceto para sinalize-o.

Referências 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

Comentários muito úteis

@JeffBezanson , lembre-se de que muitos de nós gostariam de usar Julia como substituto do Matlab etc. em cursos técnicos como álgebra linear e estatística. Estes não são cursos de programação e os alunos geralmente não têm experiência em programação. Nunca fazemos programação estruturada - é quase tudo interativo com pequenos trechos e variáveis ​​globais.

Além disso, o motivo pelo qual estou usando uma linguagem dinâmica em primeiro lugar é para alternar com fluidez entre a exploração interativa e a programação mais disciplinada. A incapacidade de usar o mesmo código em um contexto global e de função é um obstáculo para esse fim, mesmo para alguém que está acostumado a definir o escopo de conceitos, e é muito pior para alunos sem formação em ciência da computação.

Todos 98 comentários

(Por @mlubin , esta é a alteração relevante https://github.com/JuliaLang/julia/pull/19324)

Stefan sugeriu aqui que uma possibilidade de resolver esse problema é o empacotamento automático de entradas REPL em blocos let

Mas isso não seria confuso por você não poder fazer

a = 1

e usar a depois disso? A menos que global seja inserido para todas as atribuições de nível superior, eu acho?

O comportamento não seria apenas embrulhar tudo em um bloco let - é mais complicado do que isso. Você precisa deixar vincular qualquer global atribuído dentro da expressão e, em seguida, extrair o valor let-bound para um global no final da expressão.

Então você transformaria a = 1 em algo como a = let a; a = 1; end . E algo como

for i in 1:2
    before = false
end

seria transformado neste:

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

Francamente, estou muito aborrecido com o fato de as pessoas estarem apenas dando esse feedback agora. Esta mudança está no master há dez meses.

Eu sou culpado de não ter seguido o mestre muito fechado até recentemente, então este feedback está realmente um pouco atrasado. Mais do que uma preocupação para os programadores (a maioria dos loops for estará dentro de uma função no código da biblioteca), infelizmente esta é uma preocupação para o ensino. Freqüentemente, os loops for são ensinados antes das funções ou escopos (é claro que você precisa entender os escopos para realmente entender o que está acontecendo, mas as coisas de ensino são geralmente simplificadas).

Aqui fica um pouco difícil ensinar um iniciante a somar números de 1 a 10 sem explicar funções ou variáveis ​​globais.

Francamente, estou muito aborrecido com o fato de as pessoas estarem apenas dando esse feedback agora. Esta mudança está no master há dez meses.

Para ser justo, Julia 0.7 foi lançado 13 dias atrás. Esta é uma nova mudança para a maioria dos usuários Julia.

Francamente, estou muito aborrecido com o fato de as pessoas estarem apenas dando esse feedback agora. Esta mudança está no master há dez meses

Infelizmente, para aqueles de nós que não conseguem lidar com a vida no limite, é novo em nossa perspectiva.

Francamente, estou muito aborrecido com o fato de as pessoas estarem apenas dando esse feedback agora. Esta mudança está no master há dez meses.

E para aqueles de nós que foram encorajados a ficar fora dos ramos de desenvolvimento, "é totalmente novo em nossa perspectiva."

Podemos, por favor, voltar a focar no problema em questão agora, em vez de ter uma meta discussão sobre quanto tempo as pessoas tiveram para testar isso. É o que é agora, então vamos olhar para frente.

Eu sou culpado de não ter seguido o mestre muito fechado até recentemente, então este feedback está realmente um pouco atrasado. Mais do que uma preocupação para os programadores (a maioria dos loops for estará dentro de uma função no código da biblioteca), temo que seja uma preocupação para o ensino. Freqüentemente, os loops for são ensinados antes das funções ou escopos (é claro que você precisa entender os escopos para realmente entender o que está acontecendo, mas no ensino as coisas costumam ser simplificadas).

Aqui fica um pouco difícil ensinar um iniciante a somar números de 1 a 10 sem explicar funções ou variáveis ​​globais.

Este é um ponto importante. Depois de descobrir qual é realmente o problema, é surpreendente quão pouco ele realmente aparece. É menos problemático com muitos códigos de Julia à solta e em testes, e revelou muitas variáveis ​​que foram acidentalmente globais (em ambos os testes de Julia Base de acordo com o PR original, e notei isso na maioria dos Testes de DiffEq). Na maioria dos casos, parece que o comportamento sutilmente errado não é o que você obtém (esperando uma mudança em um loop), mas sim esperar ser capaz de usar uma variável em um loop é o que descobri ser a grande maioria dos onde isso aparece na atualização de scripts de teste para v1.0. Portanto, o bom é que, na maioria dos casos, o usuário é apresentado com um erro e não é difícil de corrigir.

O ruim é que é um pouco prolixo ter que colocar global x dentro dos loops, e agora seu código REPL também é diferente do código de função. Se é ou não um comportamento mais intuitivo do que antes é uma opinião difícil porque definitivamente houve alguns casos extremos no escopo local hard / soft e, portanto, isso é claramente mais fácil de explicar. Mas, ao mesmo tempo, embora tenha uma explicação muito mais sucinta do que o comportamento de antes, agora é mais fácil atingir os casos extremos em que a compreensão das regras de escopo é importante. 🤷‍♂️.

Eu, pelo menos, gostaria de ver os experimentos com bloqueio de let . Isso manteria o aspecto "você realmente não queria tantos globais", junto com a explicação de escopo simplificada, enquanto ao mesmo tempo faria o código REPL se comportar como interiores de função (que é aparentemente o que sempre desejamos). Ou, inversamente, fazer com que as pessoas especifiquem variáveis ​​que desejam atuar como globais

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

poderia ser uma boa maneira de manter a clareza e tornar o "código REPL lento por causa dos globais" muito mais óbvio. A desvantagem é que, mais uma vez, jogar coisas em uma função não exigiria os marcadores global .

Mas dado como isso tende a aparecer, não é realmente um desafio ou um empecilho. Eu classificaria como uma verruga que deveria ser mencionada em qualquer workshop, mas não é como se a v1.0 fosse inutilizável por causa disso. Espero que a mudança desse comportamento não seja classificada como falha e exija a v2.0.

Não tenho certeza se gosto da ideia de que o REPL deve se comportar como um interior de função. Claramente não é, então espero que se comporte como um escopo global. Para mim, o REPL não se comportando como escopo global seria potencialmente ainda mais confuso do que a discrepância que causa esse problema.

Independentemente disso, pelo menos eu acho que a documentação deveria ser um pouco mais explícita sobre esse assunto. Lendo casualmente os documentos, eu presumiria que você precisaria usar a palavra-chave local para fazer com que o comportamento ocorresse no escopo global por padrão.

Eu, pelo menos, gostaria de ver os experimentos com bloqueio de let . Isso manteria o aspecto "você realmente não queria tantos globais", junto com a explicação de escopo simplificada, enquanto ao mesmo tempo faria o código REPL se comportar como interiores de função (que é aparentemente o que sempre desejamos)

Se estamos indo para "REPL é o mesmo que o interior de uma função", também devemos pensar em outer :

julia> i = 1
1

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

contra:

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

julia> f()
10

Francamente, estou muito aborrecido com o fato de as pessoas estarem apenas dando esse feedback agora. Esta mudança está no master há dez meses.

As pessoas não têm usado o master para uso interativo ou para ensino, eles têm usado para atualizar pacotes, que são apenas minimamente afetados por isso e são em sua maioria escritos por programadores experientes.

(Eu fui uma das poucas pessoas que deram feedback no nº 19324, no entanto, onde defendi o antigo comportamento .)

Uma maneira ininterrupta de sair disso seria voltar ao comportamento antigo (de preferência não inserindo blocos let implícitos ou qualquer coisa - apenas restaure o código antigo em julia-syntax.scm como uma opção) em o REPL. Ou melhor, para torná-lo disponível em ambientes como IJulia que pode querer, adicione um sinalizador soft_global_scope=false a include , include_string e Core.eval para restaurar o comportamento antigo.

(Eu fui uma das poucas pessoas que deram feedback no nº 19324, no entanto, onde defendi o antigo comportamento.)

Sim, e agradeço muito. Não importa muito agora, já que fizemos a escolha, deixá-lo assar por dez meses e agora o lançamos com um compromisso de longo prazo com a estabilidade. Portanto, a única coisa a fazer agora é se concentrar no que fazer daqui para frente.

Ter a opção de escolher entre o comportamento antigo e o novo é interessante, mas parece muito estranho. Isso significa que não apenas às vezes temos um comportamento de definição de escopo que todos aparentemente acham incrivelmente confuso, mas nem sempre o temos e, se o temos ou não, depende de um sinalizador global. Isso parece muito insatisfatório, infelizmente.

Ter a opção de escolher entre o comportamento antigo e o novo é interessante, mas parece muito estranho.

Se alguém implementar uma transformação AST de escopo flexível "unbreak me", será muito tentador usá-la em IJulia, OhMyREPL etc., ponto em que você obterá uma situação ainda mais problemática em que o REPL padrão será visto como quebrado.

Não é isso que estou dizendo. É claro que devemos usar a mesma solução em todos esses contextos. Mas implementá-lo como duas variações diferentes nas regras de escopo parece menos claro do que implementá-lo como uma transformação de código com um conjunto de regras de escopo. Mas talvez sejam funcionalmente equivalentes. No entanto, parece mais fácil explicar em termos das novas regras de escopo mais simples + uma transformação que pega a entrada no estilo REPL e a transforma antes de avaliá-la.

Isso poderia ser feito como Meta.globalize(m::Module, expr::Expr) que transforma uma expressão anotando automaticamente quaisquer globais que existam no módulo como globais se forem atribuídos dentro de qualquer escopo de não função de nível superior. Claro, acho que é equivalente ao que o analisador antigo fazia, mas um pouco mais transparente, já que você pode chamar Meta.globalize você mesmo e ver o que o REPL avaliará.

Isso poderia ser feito como Meta.globalize(m::Module, expr::Expr) que transforma uma expressão anotando automaticamente quaisquer globais que existam no módulo como globais se forem atribuídos dentro de qualquer escopo de não função de nível superior.

Na verdade, comecei a pensar em implementar algo assim há alguns minutos. No entanto, parece que seria muito mais fácil implementar como uma opção em julia-syntax.jl :

  • Escrever uma transformação AST externa é possível, mas parece que existem muitos casos complicados - você basicamente tem que reimplementar as regras de escopo - embora já tenhamos o código para acertar em julia-syntax.scm .
  • É ainda mais complicado para algo como IJulia que atualmente usa include_string para avaliar um bloco inteiro de código e obter o valor da última expressão. Não apenas teríamos que alternar para a análise sintática de expressão por expressão, mas algum truque pode ser necessário para preservar os números de linha originais (para mensagens de erro, etc.). (Embora eu tenha encontrado um hack para ChangePrecision.jl para esse tipo de coisa que pode funcionar aqui também.)
  • Sem falar no caso de pessoas que include arquivos externos, que não seriam capturados por sua transformação AST.

No entanto, parece mais fácil explicar em termos das novas regras de escopo mais simples + uma transformação que pega a entrada no estilo REPL e a transforma antes de avaliá-la.

Duvido seriamente que isso seja mais fácil de explicar para novos usuários do que apenas dizer que as regras são menos exigentes para uso interativo ou para include com um determinado sinalizador.

Aqui está um rascunho de uma implementação globalize(::Module, ast) : https://gist.github.com/stevengj/255cb778efcc72a84dbf97ecbbf221fe

Ok, descobri como implementar uma função globalize_include_string que preserva as informações do número da linha e adicionei-a à minha essência .

Um caminho possível (ininterrupto) a seguir, se as pessoas gostarem desta abordagem:

  1. Libere um pacote SoftGlobalScope.jl com as funções globalize etc.
  2. Use SoftGlobalScope em IJulia (e possivelmente Juno, vscode e OhMyREPL).
  3. Inclua as funções do SoftGlobalScope em uma versão futura do pacote REPL stdlib e use-o no REPL.

Ou é prático implementá-lo no REPL.jl imediatamente? Não estou completamente certo de como as atualizações stdlib funcionam no 1.0.

Por favor, dê uma olhada em minha implementação, caso esteja faltando alguma coisa que a torne frágil.

Não podemos tê-lo como um recurso não padrão do REPL em 1.1?

Duplicado de # 28523 e # 28750. Para aqueles que dizem que não querem ensinar às pessoas sobre variáveis ​​globais, sugiro ensinar funções primeiro, antes de for loops. As funções são mais fundamentais de qualquer maneira, e isso ajudará a definir a expectativa de que o código deve ser escrito em funções. Embora eu entenda o inconveniente, esse comportamento de definição do escopo pode ser transformado em uma vantagem pedagógica: "Na verdade, as variáveis ​​globais são uma ideia tão ruim, especialmente usá-las em loops, que a linguagem faz você se dobrar para usá-las."

Adicionar um recurso não padrão ao REPL para isso parece ok para mim.

@JeffBezanson , lembre-se de que muitos de nós gostariam de usar Julia como substituto do Matlab etc. em cursos técnicos como álgebra linear e estatística. Estes não são cursos de programação e os alunos geralmente não têm experiência em programação. Nunca fazemos programação estruturada - é quase tudo interativo com pequenos trechos e variáveis ​​globais.

Além disso, o motivo pelo qual estou usando uma linguagem dinâmica em primeiro lugar é para alternar com fluidez entre a exploração interativa e a programação mais disciplinada. A incapacidade de usar o mesmo código em um contexto global e de função é um obstáculo para esse fim, mesmo para alguém que está acostumado a definir o escopo de conceitos, e é muito pior para alunos sem formação em ciência da computação.

lembre-se de que muitos de nós gostariam de usar Julia como substituto do Matlab etc. em cursos técnicos como álgebra linear e estatística. Estes não são cursos de programação e os alunos geralmente não têm experiência em programação. Nunca fazemos programação estruturada - é quase toda interativa com pequenos trechos e variáveis ​​globais.

Muitos de nós, usuários de Julia, temos absolutamente 0 background de ciência da computação (inclusive eu), mas me parece que a atitude adequada ( especialmente para os alunos) é a disposição de aprender, em vez de exigir que as coisas mudem para pior, a fim de acomodar nossa ingenuidade.

Agora, não estou necessariamente sugerindo que essa mudança em particular seria para pior, pois só tenho uma compreensão limitada do que está acontecendo aqui, mas se for o caso, isso é uma complicação significativa ou torna excessivamente fácil escrever desnecessariamente código com desempenho ruim, não parece valer a pena fazer uma mudança para ter um exemplo de aula melhor. Você não pode mudar as leis da física para que os exemplos de eletrostática que você mostra aos calouros sejam mais aplicáveis ​​à vida real.

Portanto, minha pergunta como um não usuário de CS que também se preocupa com o desempenho é como eu provavelmente erraria se esse fosse o comportamento padrão. É literalmente apenas o tipo de exemplo que estamos vendo aqui que é um problema (do qual eu já estava ciente) ou é provável que muitas vezes estrague tudo de maneiras mais sutis?

Pelo que vale a pena, concordo que ter o código se comportando de maneira diferente dependendo de seu escopo é um recurso geralmente indesejável.

Tornar o código mais difícil de escrever interativamente, forçando os iniciantes a escreverem seus primeiros loops a entender regras de escopo obscuras e fazer com que o código colado de funções não funcione em escopos globais não ajuda os programadores a escrever código rápido em funções. Isso apenas torna mais difícil usar Julia interativamente e mais difícil para iniciantes.

Não podemos tê-lo como um recurso não padrão do REPL em 1.1?

Tornar a opção "unbreak me" o padrão parece mais sensato, especialmente uma opção que visa diretamente a usuários iniciantes. Se for uma opção não padrão, então exatamente as pessoas que mais precisam dela serão aquelas que não a ativaram (e não sabem que ela existe).

O que o modo REPL proposto faria com os scripts include ed? A avaliação das declarações globais dependeria se o modo REPL está ativado? Se assim for, a IMO estaria em desacordo com a promessa de estabilidade 1.0.

Se fizermos algo assim, pode fazer sentido para o módulo determinar como ele funciona. Portanto, Main seria um módulo de "escopo flexível" enquanto, por padrão, outros módulos seriam módulos de "escopo rígido".

Eu estava interessado em ver se era possível fazer um monkey patch no REPL para usar a função globalize @stevengj e parece que é sem muito esforço (embora bastante hacky). Veja a essência . Isso não funciona com o Juno (ou qualquer outra coisa que chame Core.eval diretamente).

Eu não vou recomendar este para as pessoas, mas é muito útil para mim ao fazer a análise de dados rápida e suja. Eu gostaria muito de ver uma solução (melhor pensada), pois é realmente muito confuso para programadores inexperientes e muitas vezes relutantes (ou seja, meus alunos) quando você não pode copiar e colar o código de uma função no REPL para ver o que faz e vice-versa.

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                                                                          

(Aliás: o que foi dito acima é o máximo de testes que já teve!)

O que o modo REPL proposto faria com os scripts incluídos?

Nada. Basicamente, a proposta é que isso seja apenas para o código inserido em um prompt interativo. Assim que você começar a colocar coisas em arquivos, precisará aprender as regras do "escopo rígido". Esperançosamente, quando você começar a colocar código em arquivos, você deve começar a usar funções.

Não é ideal que haja regras de escopo mais exigentes para código global em arquivos do que no prompt. Mas eu acho que o # 19324 combinado com a promessa de estabilidade Julia 1.0 nos deixa sem opções ideais.

@stevengj :

@JeffBezanson , lembre-se de que muitos de nós gostariam de usar Julia como substituto do Matlab etc. em cursos técnicos como álgebra linear e estatística. Estes não são cursos de programação e os alunos geralmente não têm experiência em programação. Nunca fazemos programação estruturada - é quase toda interativa com pequenos trechos e variáveis ​​globais.

Tendo ministrado cursos utilizando o Julia para alunos com exposição anterior ao Matlab / R / ..., simpatizo com essa preocupação. Mas, ao mesmo tempo, não acho que usar Julia apenas como um substituto do Matlab etc. seja uma abordagem viável: como demonstrado inúmeras vezes por perguntas no Discourse e StackOverflow, isso pode levar a armadilhas de desempenho que são difíceis de consertar e entender, possivelmente acarretando um custo ainda maior do que investir para entender como Julia é diferente dessas outras linguagens (consulte os posts com os tópicos "Traduzi este código do Matlab e é 10x mais lento").

Acho que a questão principal é o fracasso silencioso ; o problema em si é fácil de entender e corrigir. Eu sugeriria manter o novo comportamento, mas dando um aviso em Main (por padrão; deve ser possível desativá-lo).

Para mim, o problema maior é a inconsistência percebida. Ou seja, estou bem com Julia fazendo as coisas de forma diferente, mas:

  • Por que o código colado de uma função não funciona em um REPL?
  • Nenhuma outra linguagem que eu já usei tem esse comportamento, e é outra barreira para a adoção
  • Por que os blocos for se comportam de maneira diferente dos blocos begin e if ? ( if Eu meio que entendo, mas um bloco é [deveria ser] um bloco.).

Em relação ao ponto 2, acho que isso é algo mais importante do que nós, que usamos a Julia há algum tempo (e estamos comprometidos com o idioma), podemos entender. Posso dizer que estou 0 em 7 para convencer meu grupo a escrever código em Julia; dois deles eram devidos a este problema de loop for que eu não poderia explicar porque não tinha sido exposto a ele antes. O restante, acho que podemos atribuir à minha falta de carisma.

Minha preferência seria garantir que o código colado de uma função em um REPL se comporte de forma idêntica à função e que for loops façam a coisa esperada ao usá-los para analisar dados interativamente; isto é, especificamente, que eles alteram variáveis ​​externas / globais quando direcionados sem nenhuma palavra-chave especial.

Não acho que usar Julia apenas como um substituto do Matlab etc. seja uma abordagem viável: como demonstrado inúmeras vezes por perguntas no Discourse e StackOverflow, isso pode levar a armadilhas de desempenho que são difíceis de consertar e entender, possivelmente acarretando um custo ainda maior do que investir em entender como Julia é diferente dessas outras línguas.

Desculpe, mas esse argumento é ridículo para mim. Não estou falando de aulas em que ensino programação. Há um lugar para cálculos interativos simples e, em classes não CS, é comum ser apresentado às linguagens de programação como uma "calculadora glorificada" para começar. Ensinar computação de desempenho em Julia é um processo totalmente diferente - mas não faz mal se eles já estão usando Julia como sua "calculadora".

Se você começar apresentando aos alunos o Matlab como sua "calculadora", será muito mais difícil fazer a transição para a programação "real", porque seu primeiro instinto é fazer o máximo possível com o Matlab antes de abandonar o barco, ponto em que seus maus hábitos estão arraigados e relutam em aprender um novo idioma. Em contraste, se você começar com Julia como sua calculadora glorificada, quando chegar a hora de fazer uma programação mais séria, você terá uma gama muito maior de opções disponíveis. Você não precisa treiná-los para amontoar tudo em operações "vetoriais" ou forçá-los a fazer as coisas mal antes de fazerem certo.

Você está dizendo que eu não deveria usar Julia no meu curso de álgebra linear ? Ou que só devo usá-lo se estiver preparado para ensinar ciência da computação e álgebra linear?

Concordo com @stevengj tanto no problema (ensinar a não programadores se torna muito mais difícil) quanto na solução (fazer as coisas funcionarem no REPL e nos vários IDEs). Incluir um script ainda teria as regras de escopo Julia 1.0, mas isso não é tão preocupante, basta ter cuidado para que a classe "podemos colocar nosso loop for em uma função e, em seguida, chamar a função" antes da classe "podemos colocar nosso loop for em um arquivo e incluir a classe de arquivo ".

Isso soa como um bom compromisso, já que a depuração interativa no REPL não se torna mais dolorosa do que o necessário (ou mais confusa para novos usuários), enquanto o código normal em scripts tem que seguir regras de escopo estritas e está a salvo de bugs que substituem alguns variáveis ​​acidentalmente.

Você está dizendo que eu não deveria usar Julia no meu curso de álgebra linear? Ou que só devo usá-lo se estiver preparado para ensinar ciência da computação e álgebra linear?

Você pode ter entendido mal o que eu estava dizendo (ou não o expressei claramente). Eu estava falando sobre cursos que usam Julia para ensinar algo específico de um domínio (por exemplo, ensinei métodos numéricos para alunos de graduação em economia), não cursos de ciência da computação (com os quais não tenho experiência).

O que eu estava tentando mostrar é que é razoável esperar um certo nível de diferença entre Julia e a linguagem X (que pode ser Matlab); inversamente, ignorar isso pode (e leva) a problemas.

Pessoalmente, ao aprender um novo idioma, prefiro enfrentar esses problemas desde o início; Além disso, acho que a simplicidade e a consistência da semântica da linguagem são mais importantes do que a semelhança com outras linguagens no longo prazo. Mas reconheço essas preferências como subjetivas, e pessoas razoáveis ​​podem ter preferências diferentes.

Eu criei o pacote (não registrado) https://github.com/stevengj/SoftGlobalScope.jl

Se isso parecer razoável, posso prosseguir e registrar o pacote e usá-lo por padrão em IJulia (e talvez enviar PRs para Juno etc.).

O que eu estava tentando mostrar é que é razoável esperar um certo nível de diferença entre Julia e a linguagem X (que pode ser Matlab)

Obviamente. Quando digo "use Julia em vez de Matlab", não quero dizer que estou tentando ensinar a sintaxe do Matlab em Julia, nem estou visando especificamente os ex-usuários do Matlab.

Eu prefiro enfrentar esses problemas logo no início

Não se trata de diferenças do Matlab em si. Eu realmente prefiro não falar sobre escopo global versus local e a utilidade de uma palavra-chave global para análise estática na primeira vez que escrevo um loop na frente de alunos que não são de ciência da computação ou na primeira vez que eles colam o código de um função no REPL para experimentá-lo interativamente. Prefiro me concentrar na matemática que estou tentando usar para expressar o loop.

Ninguém aqui está defendendo o escopo interativo flexível apenas porque é isso que os usuários do Matlab esperam. digressões no conceito desconhecido de "escopo" certamente atrapalharão qualquer aula que não seja de CS em que você esteja mostrando um loop pela primeira vez. (E mesmo para usuários experientes, é bastante inconveniente ser forçado a adicionar global palavras-chave quando estamos trabalhando interativamente.)

Uma outra correção não mencionada aqui é simplesmente parar de fazer 'para' definir um bloco de escopo (apenas funcione e deixe criar um novo escopo)

@vtjnash , prefiro focar esta discussão em coisas que podemos fazer antes de Julia 2.0. Eu concordo que ter o modo interativo se comportando de maneira diferente é apenas um paliativo, e devemos pensar seriamente em mudar as regras de escopo em alguns anos.

Bom ponto, isso também precisa de import Future.scope 😀

(Acho que este módulo / namespace / efeito comportamental já está reservado / existe)

Como um lembrete aqui, a mudança foi para garantir que o código se comportasse da mesma forma em todos os ambientes de escopo global, independentemente do que mais havia sido avaliado anteriormente naquele módulo. Antes dessa mudança, você poderia obter respostas completamente diferentes (resultantes de diferentes atribuições de escopo) simplesmente executando o mesmo código duas vezes ou movendo-o em um arquivo.

Antes dessa mudança, você poderia obter respostas completamente diferentes (resultantes de diferentes atribuições de escopo) simplesmente executando o mesmo código duas vezes ou movendo-o em um arquivo.

O número de reclamações que vi sobre isso na prática (zero) certamente será diminuído pelo número de reclamações e confusão que você verá (e já está vendo) sobre o comportamento atual.

Antes dessa mudança, você poderia obter respostas completamente diferentes (resultantes de diferentes atribuições de escopo) simplesmente executando o mesmo código duas vezes

Você quer dizer que no código a seguir, a muda entre o primeiro e o segundo for loops? Em minha mente, esse é o comportamento esperado, não um bug.

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

for i = 1:5
  a += 1
end

O que o modo REPL proposto faria com os scripts incluídos?

@ mauro3 @stevengj Suponho que adicionar uma função (digamos, exec("path/to/script.jl") ) pode ser feito com apenas um pequeno aumento na versão. Também podemos avisar exec 'ing outro arquivo do script exec ' ed e então colocar algumas mensagens pedagógicas lá para incentivá-los a usar include .

Alguns pensamentos que escrevi ontem à noite enquanto tentava envolver minha cabeça em torno desse problema (mais uma vez) para tentar descobrir qual poderia ser o melhor curso de ação. Nenhuma conclusão, mas acho que isso expõe o problema de forma bastante clara. Depois de ter pensado sobre essa questão por alguns anos, não acho que haja uma "solução ideal" - esse pode ser um daqueles problemas em que existem apenas escolhas abaixo do ideal.


As pessoas ingenuamente veem o escopo global como um tipo engraçado que inclui o escopo local. É por isso que os escopos globais funcionavam da maneira que funcionavam no Julia 0.6 e anteriores:

  • Se um escopo local externo criar uma variável local e um escopo local interno for atribuído a ela, essa atribuição atualizará a variável local externa.
  • Se um escopo global externo criar uma variável global e um escopo local interno atribuir a ela, essa atribuição atualizou previamente a variável global externa.

A principal diferença, entretanto, é:

  • Se uma variável local externa existe, por design, não depende da ordem de aparecimento ou execução das expressões no escopo local externo.
  • A existência de uma variável global, entretanto, não pode ser independente da ordem, uma vez que avaliamos as expressões no escopo global, uma de cada vez.

Além disso, uma vez que os escopos globais são frequentemente muito longos - não raramente espalhados por vários arquivos - ter o significado de uma expressão dependendo de outras expressões a uma distância arbitrária dela, é um efeito de "ação fantasmagórica à distância" e, como tal, bastante indesejável .


Esta última observação mostra por que ter as duas versões diferentes de um loop for no escopo global se comportando de maneira diferente é problemático:

# 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")

Observe também que os conteúdos de file1.jl e file3.jl são idênticos e poderíamos simplificar o exemplo incluindo o mesmo arquivo duas vezes com um significado e comportamento diferentes a cada vez.

Outro caso problemático é uma sessão REPL de longa duração. Tentar um exemplo de algum lugar online? Falha porque aconteceu de você ter uma variável global com o mesmo nome que o exemplo usa para uma variável local em um loop for ou construção semelhante. Portanto, a noção de que o novo comportamento é o único que pode causar confusão definitivamente não é exata. Concordo que o novo comportamento é um problema de usabilidade no REPL, mas quero apenas moderar a conversa e apresentar o outro lado claramente aqui.

Minha pequena sugestão, que não trata do problema repl, mas seria útil para fins didáticos ao ensinar a língua não-interativamente, pelo menos: definir um bloco principal denominado "programa", como pode ser feito em fortran (é o mesmo que "deixar ... terminar" acima, apenas com uma notação mais natural):

teste de programa
...
fim

pode-se ensinar o idioma sem entrar nos detalhes do escopo e apenas eventualmente discutir esse ponto.

Outro caso problemático é uma sessão REPL de longa duração. Tentar um exemplo de algum lugar online? Falha porque aconteceu de você ter uma variável global com o mesmo nome que o exemplo usa para uma variável local em um loop for ou construção semelhante.

Quantas reclamações de listas de e-mails e problemas no github foram feitas sobre isso por usuários irritados? Zero, pelas minhas contas. Porque? Provavelmente porque esse comportamento não surpreende as pessoas - se você trabalha em âmbito global, depende do estado global.

Portanto, a noção de que o novo comportamento é o único que pode causar confusão definitivamente não é exata.

Acho que é uma falsa equivalência - há uma grande disparidade no nível de confusão potencial aqui. Em Julia 0.6, eu poderia explicar seu exemplo para um aluno em segundos: "Oh, veja que este loop depende de a , que você alterou aqui." Em Julia 1.0, estou honestamente preocupado com o que farei se estiver no meio de uma aula de álgebra linear e tiver que digitar misteriosamente uma palavra-chave global na frente de alunos que nunca ouviram a palavra "escopo" no sentido de CS.

devemos pensar seriamente em mudar as regras de escopo em alguns anos.

Absolutamente não. Você realmente deseja voltar ao mundo pré-v0.2 (consulte # 1571 e # 330) do escopo do loop?

Na verdade, nunca oferecemos suporte total para copiar e colar código de uma função linha por linha no REPL. Portanto, podemos ver isso como uma oportunidade de fazer esse trabalho. Especificamente, embora tenha "funcionado" para for loops, não funcionou para funções internas:

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

Dentro de uma função, f mudará x da primeira linha. No REPL isso não acontecerá. Mas com uma transformação como essa em SoftGlobalScope.jl poderia funcionar. Claro, provavelmente não quereríamos isso por padrão, uma vez que colar as definições de função autônoma não funcionaria. A primeira coisa que vem à mente é um modo REPL para depuração de função linha por linha.

Você realmente deseja voltar ao mundo pré-v0.2

Não, eu quero voltar ao mundo 0,6. 😉

Acho que estava respondendo mais a:

Uma outra correção não mencionada aqui é simplesmente parar de fazer 'para' definir um bloco de escopo

Na verdade, nunca oferecemos suporte total para copiar e colar código de uma função linha por linha no REPL. Portanto, podemos ver isso como uma oportunidade de fazer esse trabalho.

Agradeço muito esse sentimento e, para meus casos de uso, seria de grande ajuda. Do meu ponto de vista, trata-se realmente de tornar o REPL o mais útil possível, em vez de alterar as regras de escopo da linguagem diretamente.

Dito isso, quanto mais penso sobre esse problema, mais vejo as visões conflitantes que (pessoalmente) tenho sobre o que o REPL deve fazer.

Para ser concreto, eu gostaria muito que o REPL correspondesse às regras de escopo de um corpo de função; ou seja, as variáveis ​​são locais em vez de globais e você pode simplesmente copiar e colar o código diretamente de uma função e saber que funcionará. Eu imagino que uma implementação ingênua seria algo como quebra automática de blocos (como foi mencionado anteriormente) do formulário

julia> b = a + 1

sendo transformado em

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

Feito corretamente (ou seja, por alguém que sabe o que está fazendo), imagino que isso teria vários benefícios em relação ao REPL existente. 1. fluxos de trabalho anteriores com análise / computação interativa de dados simplesmente funcionam. 2. muito menos postagens no Discourse onde a resposta básica é "pare de benchmarking com variáveis ​​globais" - tudo seria local e, portanto, esperançosamente rápido! :) 3. copiar e colar de / para o corpo de uma função funciona conforme o esperado. 4. uma função similar a workspace() é trivial se o armazenamento de apoio for algum tipo de Dict; apenas limpe-o. 5. os globais tornam-se explícitos - as coisas são locais, a menos que você peça especificamente que sejam globais; esta é uma grande vantagem da minha perspectiva, eu não gosto de criar globais implicitamente. Um ponto final muito menor (e eu hesito em adicionar isso!), Isso corresponderia ao comportamento do Matlab tornando mais fácil para as pessoas em transição - no REPL do Matlab todas as variáveis ​​parecem ser locais, a menos que explicitamente anotadas como globais.

Até algumas horas atrás, essa história parecia ótima para mim. Mas, depois do comentário de Jeff sobre funções, pensei em colar definições de funções autônomas e como essa abordagem basicamente impediria isso, uma vez que as definições de funções deveriam ir no escopo global (pelo menos, provavelmente é isso o que se pretende); mas, então, e se eles tivessem a intenção de entrar no escopo local (uma função interna)? Não há informações para eliminar a ambigüidade das duas possibilidades. Parece que dois modos REPL são necessários, um com escopo local e outro com escopo global. Por um lado isso pode ser muito confuso (imagine os posts do Discurso ...), mas por outro pode ser extremamente útil. (Ter os dois modos REPL também seria ininterrupto, já que você está apenas introduzindo uma nova funcionalidade :))

Indo para a casa de recuperação de SoftGlobalScope.jl pode acabar sendo o compromisso menos confuso, mas minha preocupação é que é apenas outro conjunto de regras para lembrar (quais coisas funcionam no REPL, mas não no meu corpo de função / escopo global e vice-versa).

Desculpas pela longa postagem, mas acho que isso é importante para a usabilidade (e me ajudou a pensar nisso!).

Quantas reclamações de listas de e-mails e problemas no github foram feitas sobre isso por usuários irritados? Zero, pelas minhas contas. Porque? Provavelmente porque esse comportamento não surpreende as pessoas - se você trabalha em âmbito global, depende do estado global.

Hmm, você realmente fez um estudo sistemático disso? Eu devo ter perdido essa, No entanto, isso não significa que esse comportamento não seja uma fonte de bugs ou resultados inesperados; apenas que, depois que o usuário descobriu, foi reconhecido como um comportamento correto e, portanto, não gerou um problema / reclamação.

Em Julia 1.0, estou honestamente preocupado com o que farei se estiver no meio de uma aula de álgebra linear e precisar digitar misteriosamente uma palavra-chave global

Eu simpatizo com este problema. Quando ensinei uma programação simples para alunos de economia necessários para um curso, geralmente sugeria que eles voltassem e voltassem entre envolver o código em funções e simplesmente comentar function e end e executar as coisas no âmbito global, para que pudessem inspecionar o que está acontecendo. Isso praticamente compensou a falta de infraestrutura de depuração na época em Julia.

Parece que essa abordagem não é mais viável. Mas eu me pergunto se era realmente a maneira certa de fazer isso de qualquer maneira, e enquanto isso várias coisas melhoraram muito (# 265 foi corrigido, Revise.jl e recentemente Rebugger.j melhoraram o fluxo de trabalho / depuração consideravelmente).

Parece que essa questão não incomoda muito os usuários experientes, a principal preocupação é a confusão no ambiente pedagógico. Ainda não experimentei isso, mas me pergunto se poderíamos adaptar nossas abordagens de ensino, por exemplo, introduzir funções antes de loops, evitar loops em escopo global. De qualquer forma, esses são elementos de bom estilo e beneficiariam os alunos.

Apenas uma pequena nota: embora abranja o escopo global do REPL, permitirá copiar e colar o código de e para as funções, não permitirá copiar e colar de / para o escopo global de outro módulo.

Eu me pergunto se poderíamos adaptar nossas abordagens de ensino, por exemplo, introduzir funções antes de loops, evitar loops em escopo global.

Isso é totalmente impraticável em uma aula que não seja focada no ensino de programação. Também posso não usar Julia em minhas aulas se não puder usá-la interativamente e / ou tiver que escrever funções para tudo primeiro.

(E não é apenas pedagógico. Loops em escopo global são úteis para trabalho interativo. E um dos principais motivos pelos quais as pessoas gostam de linguagens dinâmicas para computação técnica é sua facilidade de exploração interativa. Nem toda a codificação é orientada para o desempenho.)

Tem havido dezenas de tópicos e problemas ao longo dos anos em que as pessoas estão confusas ou reclamando da velha distinção "escopo flexível / difícil", portanto, alegar que ninguém nunca foi confundido ou reclamado sobre o comportamento antigo é apenas ... Não é verdade. Eu poderia desenterrar alguns deles, mas você estava por perto, @stevengj , então pode desenterrá-los com a mesma facilidade e tenho dificuldade em acreditar que você não percebeu ou não se lembra dessas reclamações e conversas.

@StefanKarpinski , estou me referindo especificamente às pessoas que reclamam que um loop global depende do estado global. Não me lembro de ninguém reclamando que isso foi mau comportamento, nem consigo encontrar nenhum exemplo disso.

Eu concordo que as pessoas ficaram confusas sobre quando e onde a atribuição define novas variáveis, mas geralmente tem sido na outra direção - eles queriam que os escopos locais agissem de forma mais global (em vez de vice-versa) ou não fizessem distinção entre begin e let . IIRC, a reclamação nunca foi que atribuir a uma variável global em um loop global teve o efeito colateral surpreendente de modificar um global.

Toda a questão do escopo é confusa para os novos usuários e continuará sendo. Mas a parte confusa não eram os casos em que a atribuição de um nome de variável global afetava o estado global. O comportamento atual torna isso pior, não melhor.

@StefanKarpinski : Tenho a sensação de que, anteriormente, a confusão com escopo soft / hard era mais de natureza teórica (de pessoas lendo o manual) do que de natureza prática (de pessoas obtendo resultados inesperados). Foi definitivamente assim para mim e o que, por exemplo, os resultados da pesquisa aqui suportam isso; Encontrei um contra-exemplo aqui .

Por outro lado, este novo comportamento não confundirá as pessoas ao ler o manual, mas sim ao utilizar o REPL. Indiscutivelmente, o último é pior.

SoftGlobalScope.jl agora é um pacote registrado. Minha intenção é habilitá-lo por padrão (opt-out) para IJulia, pelo menos neste semestre.

@ mauro3 , até mesmo o seu "contra-exemplo" é sobre alguém confuso com escopo rígido , não com escopo flexível. Tornar mais escopos "difíceis" no 0.7 certamente criará mais esse tipo de confusão.

Gostaria de salientar que IJulia tem a possibilidade interessante de tornar as variáveis ​​locais do blocos por padrão. Ou seja, se você fizer isso em um único bloco, funcionará:

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

... e t só é visível dentro deste bloco de avaliação. Se você quisesse que fosse visível do lado de fora, você teria que fazer o seguinte:

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

Também considerei uma abordagem semelhante para Julia, em que os blocos são arquivos em vez de módulos. Em outras palavras, apenas fazer t = 0 no escopo superior cria uma variável que é local de arquivo ao invés de global. Para declarar uma variável verdadeiramente global, você precisa escrever global t = 0 que ficará visível em todo o módulo. Talvez muito estranho, mas me ocorreu muitas vezes ao longo dos anos.

IJulia tem a possibilidade interessante de tornar as variáveis ​​locais do bloco por padrão

@StefanKarpinski , acho que isso seria ainda mais confuso e iria contra a forma como os notebooks são normalmente usados. É comum que a mesma variável seja usada / modificada em várias células, portanto, exigir uma palavra-chave global para todas as variáveis ​​entre células é um obstáculo para mim - exigiria ainda mais discussão de conceitos de escopo do que o problema com for loops que discutimos aqui.

Acho que, contanto que todos concordemos - como parece que concordamos - que isso é principalmente ou inteiramente um problema de interação, então temos um caminho a seguir. Se fizermos um caso especial no REPL (como está sendo feito para IJulia), o único caso ruim será desenvolver algo no REPL e, em seguida, movê-lo para o código de script de nível superior. Sem dúvida, esse é o ponto em que você deve introduzir funções, então não acho que seja tão ruim. Copiar e colar o código entre o REPL e os corpos de função funcionará (principalmente), o que provavelmente é bom o suficiente.

Então, também temos a opção de justificar / esclarecer ainda mais a distinção, tornando as variáveis ​​REPL de alguma forma locais para o REPL - isto é, variáveis ​​globais não normais, não disponíveis como Main.x . Isso é muito semelhante ao que @StefanKarpinski acabou de propor acima, mas compartilhado entre todos os blocos / células de entrada.

De um ponto de vista prático, obter isso "consertado" no REPL não é
importante apenas para usuários de ensino / não-programadores. Este comportamento também
torna a depuração interativa por meio do REPL (copiando e colando partes) muito
pouco prático. Este modo de depuração às vezes pode ser preferível (mesmo para um
bom depurador e) mesmo para programadores experientes (e muitas vezes é um dos
razões para preferir uma linguagem dinâmica). Claro para os experientes
programadores, ser opcional não deve ser um problema; Para usuários novatos,
seria preferencialmente o padrão.

@StefanKarpinski
Como um programador ingênuo, eu realmente não vejo o que há de tão errado em ver o
escopo global como um tipo engraçado que abrange o escopo local, especialmente em
línguas. Eu entendo que, do ponto de vista do compilador, não é
necessariamente correto (em Julia), mas é um modelo bom, fácil e útil
para um programador (ingênuo). (Eu também suspeito que pode ser implementado dessa forma em
alguns idiomas).
Julia também parece apresentar isso ao programador:
A função de função a seguir dará o erro "um não definido", que
não funcionará se a = 1 for colocado antes do loop for.

teste de funcionamento()
para i = 1:10
a = a + i
fim
a = 1
@show a
fim

que, a menos que eu seja completamente mal-entendido, parece estar em desacordo com "Se um
variável local externa existe, por design, não depende da ordem de
aparecimento ou execução das expressões no âmbito local externo ".

Eu concordo plenamente em evitar "ação assustadora à distância", e muito
prefira uma definição explícita para usar globais na função / pilha de chamadas
nível e pessoalmente também gostaria de ter algo como carregar de um arquivo em
seu próprio escopo e exigindo definição explícita para o uso de variáveis ​​globais.
No que diz respeito aos loops, estou indo um pouco longe demais para mim, pois o
definições / contexto geralmente estão bem próximos.
O exemplo dos 3 arquivos é um pouco artificial (e falha com o esperado "a não
definido "erro): Você normalmente colocaria a definição inicial no mesmo
Arquivo.
Há um verdadeiro perigo assustador nisso (e fui mordido por ele
em outros idiomas) em que os includes são executados no escopo global, então você
estão inadvertidamente definindo uma variável global que pode interferir com outras
código. No entanto, ter que usar global no loop não é uma solução para
este problema.

escrito para a sessão REPL de longa duração:
O comportamento atual substitui um modo de falha muito raro e fácil de detectar
para executar um exemplo online no REPL (você não consegue copiar / colar o
definição inicial da variável antes do loop, e já tem o
mesma variável definida globalmente a partir de algo anterior) com não ser
capaz de executar um exemplo online corretamente se for parte de uma função
(sem adicionar global em todos os lugares), e não resolver o problema se for
não (se global já estiver lá no código online, você ainda usará o
valor errado na variável global já existente)

Eu deveria ter sintonizado isso antes, mas depois de um breve momento de preocupação, tudo parece estar bem.

Na verdade, nunca oferecemos suporte total para copiar e colar código de uma função linha por linha no REPL ... A primeira coisa que vem à mente é um modo REPL para depuração de função linha por linha.

Na verdade, o Rebugger (que é exatamente isso) funciona corretamente em 1.0 apenas porque não tem a depreciação de escopo de 0.7 e nunca poderia funcionar em 0.6. No entanto, estou satisfeito em poder verificar que SoftGlobalScope.jl parece não quebrar isso. Por exemplo, se você entrar profundamente em show([1,2,4]) obterá aqui:

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]

Portanto, funciona bem no 1.0 (com ou sem softscope ). Em 0.7, avaliar isso (com ou sem softscope ) resultará

┌ 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

Portanto, 0.7 / 1.0 são definitivamente um passo em frente, e se softscope tornar certas coisas mais fáceis sem quebrar funcionalidades importantes, isso é ótimo.

A maior preocupação, portanto, é simplesmente como interceptar isso de forma adequada sem armazenar outros pacotes (https://github.com/stevengj/SoftGlobalScope.jl/issues/2).

@timholy , o SoftScope não toca nos argumentos das chamadas de macro (já que não há como saber como a macro iria reescrevê-la), então :(<strong i="6">@eval</strong> ...) está protegido.

parece estar em desacordo com "Se um
variável local externa existe, por design, não depende da ordem de
aparecimento ou execução das expressões no âmbito local externo ".

A variável local (externa) a existe, mas ainda não foi atribuída. Se o loop tentasse atribuir a a antes de lê-lo, a atribuição também seria visível do lado de fora.

Em geral, criar uma vinculação de variável e atribuir um valor a ela são etapas separadas.

Qual é o prazo para isso? Parece que seria uma grande melhoria na usabilidade do usuário. E neste momento "crítico" de Julia com 1.0 liberado, seria vantajoso consertar isso o mais rápido possível (da maneira sugerida por Jeff acima) e marcar uma nova versão de Julia ou REPL. (Desculpe por este comentário sobre a poltrona, pois certamente não vou consertar isso!)

@JeffBezanson
Eu ia argumentar que embora isso seja verdade (para a implementação / compilador), o programador julia ingênuo não consegue ver nenhum comportamento diferente de seu modelo conceitual mais simples (uma variável começa a existir no momento em que é definida). Infelizmente você está certo, o código a seguir não apresentará um erro, mas apresentará um erro se você omitir a = 2 no final
teste de funcionamento()
para i = 1:10
a = 1
fim
println (a)
a = 2
fim
Vou explicar o infelizmente: posso entender o comportamento (porque já trabalhei com linguagens compiladas antes), mas ainda acho confuso e inesperado. Deve ser ruim para alguém com apenas experiência em scripts ou novo em programação. Além disso, encontrei um código que mostra o comportamento, não vejo um aplicativo útil (talvez você possa me ajudar aí)

No REPL:
Acabei de ficar mais convencido de que alterar o escopo de volta para "normal" pelo menos no REPL (não há necessidade de adicionar loops globais) é de alta prioridade: Eu estava testando algumas coisas no REPL hoje e fui (novamente) mordido por ele, levando algum tempo para perceber isso. Como já sigo a Julia há algum tempo, gosto muito disso, estou até acompanhando esse tópico sobre o problema, eu até chamaria de empecilho: Um newbee (para a Julia) testando o idioma muito provavelmente não encontrará resolver o problema e simplesmente desistir.

@jeffbezanson e eu estamos de férias há muito aguardadas (eu não deveria estar lendo isso). Podemos descobrir o que fazer em uma semana ou assim.

@derijkp , embora o feedback seja apreciado, as regras de definição do escopo não devem ser

@derijkp Uma resposta curta é que acho mais fácil se o escopo de uma variável corresponder a alguma construção de bloco (por exemplo, o corpo de uma função ou loop). Com sua sugestão, o escopo de uma variável seria algum subconjunto de um bloco, o que eu acho que é mais complexo e confuso - você não pode apontar para uma forma sintática que corresponda ao escopo da variável.

Sim, posso acreditar que isso é uma incompatibilidade para a intuição de algumas pessoas. Mas você só pode otimizar os primeiros dez minutos de uso de um idioma até certo ponto. A verdadeira questão é: quão difícil é ensinar / aprender como funciona e qual design economizará tempo no longo prazo (tornando a linguagem mais simples, facilitando o desenvolvimento de ferramentas, etc.).

(de acordo com muitos dos itens acima sobre a modificação do comportamento do REPL)
Eu gostaria de ver o REPL de uma forma que não levasse a esta questão de stackoverflow
e mais cedo seria melhor já que muitos olhos novos estão olhando para Julia

Eu concordo ... E também acho que as regras de escopo não devem necessariamente mudar, apenas todas as interfaces interativas (ou seja, o REPL, Jupyter e o controle Juno entram)

Não se trata apenas de iniciantes aprendendo uma nova regra. Se você não pode copiar e colar fragmentos de código no REPL, jupyter etc e também em funções, é um grande aborrecimento para programadores intermediários também.

Claro, eu também concordo com os outros postadores ... com os iniciantes eles vão pegar fragmentos de código que vêem dentro de funções, copiar I para scripts e ficar completamente confusos quando não tem o mesmo comportamento quando copiado dentro de uma função , em junho, o repl e jupyter. Haverá 100 perguntas de troca de pilha que resultarão no mesmo problema. Os programadores intermediários vão ter todos os tipos de soluções caseiras embaladas em blocos let , etc, o que confundirá ainda mais as coisas

Haverá 100 perguntas de troca de pilha que resultarão no mesmo problema. Os programadores intermediários vão ter todos os tipos de soluções caseiras embaladas em blocos let , etc, o que confundirá ainda mais as coisas

Possivelmente, mas neste estágio isso é hipotético (também o OP da questão associada está perguntando sobre a justificativa para a regra de escopo, em vez de ficar confuso sobre isso).

Além disso, embora eu respeite a experiência de ensino de todos que se preocupam com isso, o tempo dirá se isso será um grande problema na sala de aula.

o questionador parece ter ficado confuso com isso: "Eu me pergunto se isso é intuitivo para usuários iniciantes em Julia. Não foi intuitivo para mim ..."

o questionador parece ter ficado confuso com isso:

Sem mencionar que é alguém que claramente sabe o suficiente sobre linguagens de programação para entender as nuances do escopo. E quanto a todos os usuários do tipo matlab que são completamente ignorantes desses tópicos ... e provavelmente nunca vão investir tempo suficiente para entender as nuances.

Possivelmente, mas neste estágio isso é hipotético

Já respondi várias perguntas relacionadas a isso no stackoverflow, principalmente por novos usuários, e ainda mais na vida real (a última ontem, de um usuário Matlab, que viu isso como um impedimento).

Haverá 100 perguntas de troca de pilha que resultarão no mesmo problema.

Em meu "tempo livre", tenho adicionado as tags scope , scoping e global-variables às perguntas SE. Só paro por falta de tempo, não porque não haja mais.

Conclusão após muita discussão, incluindo triagem: vamos incluir algo na linha de SoftGlobalScope no Base e usá-lo no REPL e em todos os outros contextos de avaliação interativa. @JeffBezanson apontou que a maneira como isso é implementado é, na verdade, essencialmente a mesma como o escopo flexível foi implementado anteriormente, então, até certo ponto, estamos dando uma volta completa. A diferença é que agora não há comportamento de escopo em módulos ou scripts, apenas em contextos do tipo REPL. Eu também acho que _explicar_ o soft scope como uma reescrita de fonte é mais claro do que tentar distinguir entre hard scopes e soft (que nunca como Jeff explicou, devo apontar).

Essas duas afirmações me confundem um pouco, pois parecem um pouco contraditórias:

e usá-lo no REPL e em todos os outros contextos de avaliação interativa

não há comportamento de escopo em scripts, [...] apenas em contextos do tipo REPL.

Isso significa que o módulo Main tem às vezes um escopo flexível (digamos no prompt REPL) e às vezes um escopo rígido (digamos quando julia -L script.jl )? Não faria sentido dizer que Main sempre tem escopo flexível? E um módulo pode aceitar o escopo flexível de using SoftGlobalScope ?

(Eu acho) as regras de escopo não podem ser alteradas em scripts porque seriam incompatíveis com versões anteriores, ou seja, quebrariam a promessa de que qualquer código escrito para 1.0 será executado em qualquer versão 1. *. Você está correto, porém, que o mesmo problema com o escopo para o REPL também se aplica a scripts (usuário ingênuo perdido por completo porque seu código não funciona corretamente quando executado como um script). Uma maneira de resolver / aliviar este problema sem grandes incompatibilidades seria adicionar uma opção ao cmdline julia para usar softscope (ou alternativa), por exemplo, julia -f programfile, e mostrar esta opção em qualquer descrição / tutorial que um iniciante provavelmente irá deparar-se com.
Também vejo uma alternativa potencial para o softscope que pode ter algumas vantagens (embora eu provavelmente esteja negligenciando as desvantagens): E se um arquivo (um script chamado) sempre introduzisse seu próprio escopo local: as regras de escopo estariam em total consistência com aquelas em funções e com as expectativas de muitos usuários. Isso também removeria muitos passivos de desempenho com novos usuários:
Chega de globais desnecessários (os globais teriam que ser definidos explicitamente), e o código pode ser compilado
(Quantas vezes você já disse para colocar tudo em uma função e evitar o uso de globais?)

Acabei de acertar isso e estava completamente perplexo para ser honesto, nunca tinha visto isso antes em qualquer outro idioma. Estou planejando introduzir um curso opcional de Julia para usuários avançados de R em minha universidade ainda este ano, assim que as coisas se acalmarem, e meus alunos chegarão a isso no dia 0, quando começarem a digitar coisas aleatoriamente no REPL. E o fato de que os loops for se comportam de maneira diferente das instruções if apenas irrita a ferida, por mais lógico que seja em termos de escopo. O escopo dentro das funções é suficientemente difícil de fazer com que os alunos de biologia entendam, a ideia de ter que explicar _ainda que seja percebida_ inconsistências gritantes nele no REPL / em um script / em um loop for / em uma instrução if (porque é isso que estamos falando mais ou menos aqui) de uma forma diferente de todas as outras línguas do mundo me deixa muito triste.

Eu entendo a promessa de compatibilidade com versões anteriores, mas ter este trabalho _como esperado por todas as pessoas não cs no planeta (e a maioria das pessoas cs, eu suspeito) _ parece mais uma correção de bug do que um problema de compatibilidade com versões anteriores - não estamos dizendo que cada bug será reproduzido para sempre, certo? A correção do REPL é obviamente essencial, então é ótimo que você esteja propondo isso, mas então ter que explicar que você não pode copiar um script para o REPL e esperar que o mesmo comportamento pareça tão ruim ou pior do que o problema original.

Por favor, por favor, pense em tratar isso como uma correção de bug e enviá-lo com scripts, bem como o REPL - mesmo se houver uma mudança para ir para o comportamento "antigo" - e fazê-lo o mais rápido possível no 1.0.1.

Um colega que eu estava tentando aprender Julia também acabou de se deparar com isso. Ter que explicar toda a coisa da variável global vs. local nas primeiras etapas não é o ideal ...

Não acho que tratar isso como uma "correção de bug" está nos cartões, porque quebraria o contrato de estabilidade 1.0. No entanto, parece razoável para mim usar softscope para scripts executados com julia -i (ou seja, modo "interativo").

(Ou seja, haveria um sinalizador --softscope={yes|no} e o valor padrão de isinteractive .)

Teremos que considerar a escolha do modo de script.

Por falar nisso, não é loucura para mim usar como padrão --softscope=yes para qualquer "script", ou seja, julia foo.jl , e apenas ativar as regras de escopo "rígidas" para módulos e include (nesse ponto você realmente deve colocar a maior parte do código em funções).

Por falar nisso, não é loucura para mim padronizar para --softscope = yes para qualquer "script",

Este. O outro a considerar seriamente é Juno. Lembre-se de que as pessoas irão <shift-enter> por meio de seu código para fazer o desenvolvimento interativo (especialmente ao trabalhar com os testes de regressão) e, mais tarde, esperar ser capaz de executar o mesmo arquivo. Importa se o código está em @testset ou não (o que eu acho que pode introduzir um escopo)? Seria muito confuso para o usuário se o mesmo texto mudasse quando em um @testset vs. não quando usando a integração do Atom, e fosse inconsistente com ] test também.

Com certeza me parece que a melhor solução é que o escopo rígido é simplesmente uma opção, onde se todos os outros usos (incluindo include em scripts) usarem softscope menos que você diga o contrário .

diferente de todas as outras línguas do planeta

Você quer escrever var x = 0 para introduzir todas as variáveis? Isso também "consertaria" isso e seria mais parecido com outras linguagens.

não estamos dizendo que cada bug será reproduzido para sempre, estamos

Não é assim que funciona. Você não pode obter nenhuma mudança no idioma que deseja apenas chamando o comportamento atual de um bug.

Eu realmente não acho que deveria haver uma opção de linha de comando para isso. Então, cada parte do código julia terá que vir com um comentário ou algo informando qual opção usar. Algum tipo de diretiva de analisador em um arquivo de origem seria um pouco melhor, mas ainda melhor seria ter uma regra fixa. Por exemplo, o escopo rígido dentro dos módulos só pode fazer sentido.

Deixe-me tentar novamente fornecer uma explicação para isso que pode ser útil para evitar a mania, a histeria e a carnificina que as pessoas estão vendo na sala de aula:

"
Julia possui dois tipos de variáveis: local e global. As variáveis ​​que você introduz no REPL ou no nível superior, fora de qualquer outra coisa, são globais. Variáveis ​​introduzidas dentro de funções e loops são locais. Atualizar variáveis ​​globais em um programa geralmente é ruim, então se você estiver dentro de um loop ou função e quiser atualizar uma global, você deve ser explícito sobre isso escrevendo a declaração global novamente.
"

Talvez isso possa ser melhorado; sugestões bem-vindas. Eu sei, você prefere não precisar de nenhum tipo de explicação. Entendi. Mas não parece tão ruim para mim.

Eu realmente não acho que deveria haver uma opção de linha de comando para isso. Então, cada parte do código julia terá que vir com um comentário ou algo informando qual opção usar. Algum tipo de diretiva de analisador em um arquivo de origem seria um pouco melhor, mas ainda melhor seria ter uma regra fixa

Eu concordo. Parece uma dor de cabeça de ensino e comunicação para mim.

Por exemplo, o escopo rígido dentro dos módulos só pode fazer sentido.

Só para eu entender: se eu tivesse um script curto (não em um módulo!) Em um arquivo .jl que copiei de um bloco de notas IJulia, se eu executasse esse código diretamente no REPL ou shift- entrar no Juno, então ele se comportaria consistentemente como soft-scope ... mas se eu o copiasse em vez de um bloco module , ele gritaria comigo sobre globais? Mas se eu copiei esse código dentro de funções dentro de um módulo, ele deve funcionar.

Nesse caso, isso faz todo o sentido, é muito ensinável e coerente. Os scripts de nível superior são uma interface interativa para exploração etc., mas você nunca colocaria esse tipo de código em um módulo. Módulos são algo que você deve preencher com funções que são consideradas globais com muito cuidado. Seria fácil contar às pessoas sobre essas regras.

Você quer escrever var x = 0 para apresentar todas as variáveis? Isso também "consertaria" isso e seria mais parecido com outras linguagens.

Não, prefiro não! Mas as linguagens de script que têm um REPL raramente fazem isso (por exemplo, ruby, python, R, ...), elas se comportam como Julia v0.6.

Julia possui dois tipos de variáveis: local e global. As variáveis ​​que você introduz no REPL ou no nível superior, fora de qualquer outra coisa, são globais. Variáveis ​​introduzidas dentro de funções e loops são locais. Atualizar variáveis ​​globais em um programa geralmente é ruim, então se você estiver dentro de um loop ou função e quiser atualizar uma global, você deve ser explícito sobre isso escrevendo a declaração global novamente.

Eu entendo completamente o que você está dizendo aqui, e não irei (tocar na madeira!) Cometer esse erro novamente. Mas todo o problema que me preocupa não sou eu. Achei relativamente fácil introduzir o escopo (sem mencioná-lo diretamente) quando explico que as variáveis ​​dentro das funções não podem ver as externas e vice-versa (embora isso seja mais uma aspiração do que um fato em R!), Porque as funções em si já são um conceito _relativamente_ avançado. Mas isso atinge muito mais cedo na curva de aprendizado aqui, onde não queremos nada nem remotamente tão complicado quanto o alcance para afetar as pessoas ...

Observe também que não são apenas "_variáveis ​​que você introduz no REPL ou no nível superior, fora de qualquer outra coisa, são globais_" e "_variáveis ​​introduzidas dentro de funções e loops são locais_", é também que as variáveis ​​em instruções if no REPL ou em o nível superior é global, mas as variáveis ​​em @testset são locais. Acabamos caindo em uma toca de coelho de "apenas experimente e faça você mesmo, seja local ou global, boa sorte".

No entanto, eu concordo com @jlperla - a proposta de que "escopo rígido dentro de módulos apenas pode fazer sentido" parece completamente adequada para mim! Módulos são um conceito suficientemente avançado novamente ... se o escopo suave funciona para REPL e scripts, tudo bem.

não queremos nada nem remotamente tão complicado quanto o alcance para afetar as pessoas ...
no nível superior são globais, mas as variáveis ​​em um @testset são locais

O que estou tentando chegar é que sinto que uma descrição simples de global x local é suficiente para o ensino em estágio inicial --- você nem precisa dizer a palavra "escopo" (ela não ocorre de forma alguma na minha explicação acima). Quando você está apenas mostrando algumas expressões simples e loops no REPL, você não está ensinando as pessoas sobre conjuntos de testes e não precisa de uma lista exaustiva do comportamento de escopo de tudo na linguagem.

Meu único ponto é que essa mudança não torna repentinamente necessário ensinar muitos detalhes sobre o idioma desde o início. Você ainda pode ignorar a grande maioria das coisas sobre escopos, conjuntos de teste, etc., e uma linha simples sobre global x local deve ser suficiente.

e uma linha simples sobre global x local deve ser suficiente.

Em um mundo onde todos começaram a escrever todo o seu código do zero, eu concordaria totalmente.

O problema é que você precisa ensinar os alunos não apenas sobre o escopo, mas também sobre o escopo de onde copiaram e colaram o código de onde tiraram. Você precisa ensiná-los que se eles copiarem e colarem o código que está em stackexchange dentro de uma função ou bloco let, eles precisam examiná-lo e encontrar onde adicionar "global" se estiverem colando no REPL ou em um .jl arquivo. Mas se eles estão copiando esse código dentro de uma função ou no bloco de notas Jupyter. eles não deveriam. E se eles encontrarem código dentro de uma stackexchange ou página de tutorial que contenha variáveis ​​globais, mas eles quiserem copiar e modificar esse código dentro de suas próprias funções, então eles precisam remover o global.

E então os alunos começam a perguntar por que for cria esse escopo com o qual eles precisam se preocupar, mas não outras coisas ...

Acabamos caindo em uma toca de coelho de "apenas experimente e faça você mesmo, seja local ou global, boa sorte".

Questionário pop: em julho de 0,6, é x global ou local:

for i = 1:10
    x = i
end

A resposta é que não há como saber, porque depende se um x global foi definido antes. Agora, você pode dizer com certeza que é local.

Gente, essa discussão está prestes a não ser mais produtiva. Jeff sabe muito bem que o comportamento antigo era bom no REPL. Quem você acha que o projetou e implementou em primeiro lugar? Já nos comprometemos a mudar o comportamento interativo. Uma decisão ainda precisa ser tomada sobre se um "script" é interativo ou não. Parece interativo quando você o chama de "um script", mas soa muito menos interativo quando você o chama de "um programa" - no entanto, eles são exatamente a mesma coisa. Por favor, mantenha as respostas curtas e construtivas e focadas nas coisas que ainda precisam ser decididas. Se houver comentários que desviem disso, eles podem ser ocultados e o tópico pode ser bloqueado.

Um pensamento que eu tinha, mas rejeitamos por ser "muito chato" e "provável de fazer com que os aldeões peguem seus forcados" foi que, em contextos não interativos, poderíamos exigir um local ou global anotação em "soft scope". Isso garantiria que o código de um módulo funcionaria da mesma forma se colado no REPL. Se aplicássemos isso a "scripts" / "programas", o mesmo aconteceria com eles.

Quando fui apresentado a Julia pela primeira vez (não muito tempo atrás, e eu venho de uma formação em Fortran principalmente), fui ensinado que "Julia é compilada e rápida no nível da função, portanto, tudo que deve ser eficiente deve ser feito dentro das funções . No 'programa' principal, ele se comporta como uma linguagem de script ". Achei isso justo, já que não consigo imaginar alguém fazendo algo muito exigente do ponto de vista computacional sem entender essa afirmação. Portanto, se houver qualquer sacrifício no desempenho do programa principal por usar a mesma notação e construções que nas funções, acho isso totalmente aceitável, muito mais aceitável do que tentar entender e ensinar essas regras de escopo e não ser capaz de copiar e cole códigos de um lugar para outro.

Aliás, ainda sou um novato na Julia, tendo escolhido ela para ensinar alguns alunos do ensino médio e de graduação noções básicas de simulação de sistemas físicos. E já estou esperando que esse problema volte ao comportamento 'normal' das versões anteriores, porque nos dá uma grande dor de cabeça.

Esta conversa está bloqueada agora e apenas os committers de Julia podem comentar.

@JeffBezanson , qual seria o plano para implementar a semântica que você sugeriu neste thread de discurso , inicialmente apenas no REPL e opt-in em outro lugar?

Parece que você está planejando colocar isso diretamente no código de redução ( julia-syntax.scm ), em vez de reescrever a sintaxe ala SoftScope.jl? Ou você prefere reescrever a sintaxe primeiro (modificando o SoftScope para a regra proposta e convertendo-o em um stdlib) e adiar colocá-lo no código de redução para uma versão posterior do Julia?

Esta página foi útil?
0 / 5 - 0 avaliações