Julia: Remova exigir, importar e talvez importar e mesclar usando

Criado em 14 ago. 2014  ·  72Comentários  ·  Fonte: JuliaLang/julia

Da lista de discussão:

Stefan:

Acho que deveríamos acabar com a importação e apenas ter

usando Foo
usando Foo: bar

O primeiro carregaria e criaria uma ligação para Foo, tornaria suas exportações disponíveis como ligações "soft" (o que using faz agora). O segundo também carregaria e criaria uma ligação para Foo e tornaria a barra disponível como uma ligação "difícil" (o que a importação faz agora)."

Parece que isso ainda é um ponto de confusão para os recém-chegados.

breaking design modules speculative

Comentários muito úteis

Eu gosto da simetria de import e export . (Como alguém apontou em algum lugar.)

Todos 72 comentários

Também precisaríamos da funcionalidade import Foo de alguma forma, onde você apenas obtém o Foo e nada mais

using Foo: ?

using Foo: Foo ?

Para vincular Foo ao módulo Foo (e nada mais):
import Foo
Para vincular Foo ao módulo Foo, x a Foo.x, y a Foo.y
import Foo: x, y
Para vincular Foo ao módulo Foo, e todos os nomes exportados de Foo são vinculados sem qualificação:
import Foo: *

Isso poderia ser using , mas eu sinto que isso está mais no espírito de import .

Isso também remove a distinção entre trazer algo para o escopo e disponibilizá-lo para extensão de método. Pessoalmente, acho que isso é uma coisa boa e torna o sistema de módulos mais compreensível, mas queria ter certeza de que trouxemos isso à tona.

Há um forte argumento a ser feito de que uma construção que disponibiliza todas as ligações exportadas de um módulo deve torná-las ligações suaves ( using ) e não ligações rígidas ( importall ). Suponha que o módulo A use o módulo B e defina foo(::Any) . Se uma versão posterior do módulo B também definir e exportar foo(::Any) , você não quer que eles se sobreponham. Você também não quer que o módulo B defina foo(::Int) e tenha o módulo A às vezes chamando esse método em vez do método definido porque é mais específico, ou ter que listar todos os identificadores que você deseja do módulo B para evitar a importação de um único identificador conflitante.

Mas se você listou explicitamente os identificadores que deseja importar, nunca há uma razão para fornecer uma ligação suave. Ou você não vai definir novos métodos de um identificador, caso em que hard e soft binding têm comportamento idêntico, ou você vai definir novos métodos dele, nesse caso um soft binding é equivalente a nenhum binding e se você quiser esse comportamento, você deve apenas remover o identificador da lista.

Então, resumindo, eu gosto da proposta de @StefanKarpinski . Conceitualmente, precisamos de ligações rígidas e flexíveis, mas podemos obter todos os comportamentos úteis com uma única palavra-chave.

Eu vejo o seu ponto. Nesse caso, gosto da sua proposta de que using Foo: (com os dois pontos, mas sem itens) parece conceitualmente consistente. Os dois-pontos indicam que você vai limitar quais símbolos usar, mas você simplesmente não lista nenhum.

O : vazio à direita parece um pouco engraçado, mas acho que as pessoas se acostumariam com isso

O problema com os dois pontos vazios à direita é que atualmente procuramos um nome na próxima linha – em outras palavras using Foo: é considerado incompleto.

Relacionado: #4600

Eu sei que a prática atual de Julia é importar tudo para o
namespace do módulo atual, mas eu realmente acho que ainda deve haver
ser uma maneira simples e natural de importar apenas o nome de um módulo.

Eu também acho que a sobrecarga atual de usar Foo e usar Foo.Bar é
problemático; ele parece dentro do Foo mas não dentro do Foo.Bar (a menos que o Foo.Bar seja
um módulo?)

Acho que na proposta de Stefan, isso é abordado com

usando Foo: Foo

Na quinta-feira, 14 de agosto de 2014, toivoh [email protected] escreveu:

Eu sei que a prática atual de Julia é importar tudo para o
namespace do módulo atual, mas eu realmente acho que ainda deve haver
ser uma maneira simples e natural de importar apenas o nome de um módulo.

Eu também acho que a sobrecarga atual de usar Foo e usar Foo.Bar é
problemático; ele parece dentro do Foo mas não dentro do Foo.Bar (a menos que o Foo.Bar seja
um módulo?)


Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/8000#issuecomment -52202142.

@kmsquire , mas o que acontece se houver um módulo Foo dentro do módulo Foo? Isso é, infelizmente, ambíguo.

Isso é um erro de design (e causa um aviso), então não importa

Eu gosto da versão proposta por @ssfrr acima do melhor.

Existe uma convenção subjacente de que os pacotes têm um e apenas um ponto de entrada de módulo? -- Para import Foo significa importar o módulo Foo do pacote Foo . Qualquer outro módulo deve ser um submódulo de Foo . O que significa que há uma correspondência implícita entre o pacote Foo, Foo.jl no pacote e o module Foo dentro dele.

Eu pergunto b/c estou querendo saber se import também poderia ser usado para importação local/relativa. Digamos que um projeto tenha src/foo.jl e src/bar.jl então em foo.jl :

import bar

importaria de src/bar.jl . Esta é uma melhoria em relação ao uso include porque opera dentro do sistema de módulos.

Sim, espera-se que os pacotes, pontos de entrada de pacotes e módulos sejam um para um. Você pode quebrar essa convenção se precisar, mas não há muita necessidade disso, pois os módulos são apenas para nomear e não são unidades funcionais. A ideia que você está trazendo é #4600, exceto que a sintaxe para uma importação relativa é import .bar enquanto import bar é uma importação absoluta.

@StefanKarpinski O import .bar realmente procura por "bar.jl" no diretório local? Fiquei com a impressão de que import .Bar se referia apenas a um submódulo de um módulo pai já atual.

ATUALIZAÇÃO: Duh, isso é o que você propõe em #4600. Desculpe.

:+1:. Se vamos fazer isso, 0,4 seria um bom momento para fazê-lo.

:+1: Por favor, vá em frente e limpe um pouco disso (para 0,4!)

em vez de ter dois mecanismos de importação para distinguir entre estender ou não, por que não sinalizar a extensão no próprio site de extensão por meio de uma palavra-chave ou anotação na definição da função, por exemplo, substituir palavra-chave antes da função? Semelhante à anotação @Override em Java.

Isso tem a vantagem de ver claramente que a função está substituindo outra (e, portanto, é um método)

Isso já é possível, @ssagaert. Você faz isso escrevendo explicitamente o nome do módulo na definição da função (por exemplo, Base.print(…) = … ), e parece ser um estilo para o qual muitas pessoas estão convergindo. O único ponto de discórdia é que a sintaxe não funciona para todos os nomes possíveis (como .+ , etc.).

(Como um aparte, tenha cuidado ao colocar macros com backticks `` para evitar incomodar outros usuários do GitHub).

:+1: para @ssagaert sugestão de usar @override , a mensagem de erro original quando se tenta estender um método sem importá-lo primeiro é assim:

ERROR: error in method definition: function Foo.x must be explicitly imported to be extended

Então talvez @extend poderia ser mais adequado? Eu não sou um falante nativo de inglês, mas meu entendimento é que _override_ significa algo como: cancelar, anular, invalidar, negar, anular, anular, descontinuar, etc.

Acho que isso é mais explícito:

<strong i="13">@extend</strong> Base.show(...) = ...

Do que:

import Base: show

# ... several lines here

Base.show(...) = ...

@Ismael-VC Base.show(...) = ... já funciona sem importar nada. import só é necessário se você quiser que show(...) = ... estenda Base.show .

A palavra de substituição @Ismael-VC foi apenas uma sugestão. Pode ser estender ou qualquer outra coisa significativa. Também não deve haver @ já que em julia isso significa que é uma macro ( @Override se refere a Java onde é uma anotação).

@simonster obrigado eu não sabia disso!

@ssagaert então você quer dizer uma palavra-chave? Eu tentei algo assim, mas ainda sou péssimo em macro-foo:

module Extend

export <strong i="9">@extend</strong>


macro extend(x)
    mod = x.args[1].args[1].args[1]
    met = x.args[1].args[1].args[2]
    imp = :(Expr(:import, $mod, $met))
    :(Expr(:toplevel, $imp, $(esc(x))))
end


end

Eu sei que isso não é geral, ainda assim não consigo fazer uma expressão que retorne o que o parse faz:

julia> using Extend

julia> type Foo end

julia> <strong i="13">@extend</strong> Base.show(x::Foo) = Foo
:($(Expr(:toplevel, :($(Expr(:import, Base, :show))), show)))

julia> parse("import Base.show; Base.show(x::Foo) = Foo")
:($(Expr(:toplevel, :($(Expr(:import, :Base, :show))), :(Base.show(x::Foo) = begin  # none, line 1:
            Foo
        end))))

Acho que uma macro @extend geral e funcional não mudaria a semântica do mecanismo de importação.

@Ismael-VC eu fiz, mas também gosto do 'truque' do @mbauman .

Eu acho que pode ser bom poder usar . sozinho para significar "isto". para que você possa escrever expressões como:
import Base: . (significa importar Base.Base )
ou
using ..

tenho certeza de que isso exigiria https://github.com/JuliaLang/julia/pull/11891#issuecomment -116098481 no entanto. talvez seja suficiente permitir espaços antes do . , mas não depois dele, para resolver o caso de ambiguidade?

Eu acredito que require já está obsoleto. Pode ser bom adicionar uma notação de ponto mais flexível na importação de módulos por 1.0, mas duvido que vamos mudar alguma coisa aqui pelo congelamento de recursos 0.6

Depois de muita discussão sobre isso ontem, estou inclinado a algo como as propostas em https://github.com/JuliaLang/julia/issues/8000#issuecomment -52142845 e https://github.com/JuliaLang/julia/ issues/8000#issuecomment -52143609:

using A: x, y    # hard imports x and y from A

using A: A       # hard imports just the identifier `A`

using A: ...     # soft imports all of A's exports

using A     # equivalent to `using A: A, ...`

using A.B   # A.B must be a module. equivalent to `using A.B: B, ...`

using A: ..., thing1, thing2    # import all exports plus some non-exported things

A alternativa seria manter import em vez de using :

import A             # hard binding for the module `A`
import A: ...        # soft bindings for all names exported by `A`
import A: x, y       # hard bindings for `x` and `y` from `A`
import A: x, y, ...  # equivalent to doing both of the previous two

A regra geral para determinar se uma vinculação é hard ou soft seria simples: qualquer nome explicitamente solicitado é uma hard binding, qualquer vinculação dada implicitamente é soft.

Edit: há algum desejo de adicionar a este esquema algum atalho para import A; import A: ... que é aproximadamente o que using A faz atualmente (a única diferença é que using A atualmente importa A ); isso pode continuar a ser using A ou import A... foi proposto.

Sim, acho que também é uma boa proposta. Basicamente se resume a se alguém prefere using ou import .

Adendo, poderíamos manter using A como um atalho para import A; import A: ... - juntamente com a eliminação do comportamento atual de que cada módulo tem uma ligação exportada para si mesmo, e é por isso que using A causa que haja uma ligação suave para A disponível.

Eu ficaria muito desapontado se ainda tivéssemos várias palavras-chave depois de tudo isso.

Eu gosto da simetria de import e export . (Como alguém apontou em algum lugar.)

Sempre fazer "hard bindings" não parece o padrão mais seguro em termos de extensão de método. Havia a proposta relacionada, mas um tanto separada, de exigir qualificação de módulo para extensão de método que poderia remover a necessidade de "vinculações rígidas".

Ligações rígidas também são necessárias para esta finalidade:

import Package: x
x = 1   # gives an error

E, crucialmente, essa proposta nem sempre faria ligações rígidas --- apenas para coisas que você lista explicitamente. Exigir que se diga import em vez de using não adiciona muita segurança.

A segurança vem da maioria dos lugares que não estão fazendo extensão de método que pode sair bem com uma ligação suave. Acho que ainda devemos ter uma maneira de solicitar uma ligação suave específica sem ter que importar todas as exportações de um pacote (ou obter uma ligação suave específica que não é exportada).

Ainda temos o aviso para substituir uma ligação importada?

Acho que exigir a qualificação do módulo é uma boa ideia. Agora mesmo para saber se uma função está sendo introduzida ou um método estendido você tem que olhar todo o conteúdo do pacote para uma instrução import A: func .

Ainda temos o aviso para substituir uma ligação importada?

Eu acredito que é realmente um erro agora.

Há outra proposta circulando que mantém as duas palavras-chave, mas ainda simplifica um pouco as coisas:

  1. Adicione a sintaxe import A: ...
  2. Descontinuar using A:
  3. Em using A.B , exija que A.B seja um módulo e documente que é uma abreviação de import A.B; import A.B: ... .

Dessa forma, tudo pode ser feito com apenas import , mas using X está disponível por conveniência. Isso também seria especialmente fácil de fazer a transição.

BTW, using parece inconsistente como postei aqui . Se mantivermos using , ele deve ser renomeado para use se possível.
Acho que devemos exigir a quantificação do módulo ao estender funções, pois seu significado é muito mais claro do que o padrão de importação e extensão.

Minha abordagem favorita:

  • Exigir um prefixo de módulo explícito para extensão
  • Se você quiser apenas o nome do módulo e não seus símbolos, use using A: A
  • Remova import e o tipo de vinculação que ele cria

Coisas que precisariam acontecer para implementar https://github.com/JuliaLang/julia/issues/8000#issuecomment -327512355:

  • alterar o comportamento de using A para hard import A
  • remover suporte para using A: x
  • remova o suporte para using A.x onde x não é um submódulo
  • remova o suporte para import A.x onde x não é um submódulo
  • adicionar suporte para a sintaxe ... para import

using A: x é usado com frequência e é muito útil. Você está dizendo que deseja x em seu namespace, mas não deseja estendê-lo. Em import A: x , você está dizendo que deseja estender x . Há uma distinção significativa entre ter uma função disponível para uso e ser capaz de estendê-la.

Pensando bem, eu diria que o maior problema aqui é que using A.B faz duas coisas: se B é um módulo, ele importa suavemente todas as suas exportações e, caso contrário, apenas importações suaves B . Acho que devemos apenas corrigir isso, e tornar using A.B permitido apenas para módulos, e ter using A: a, b para importação suave de ligações específicas, uma de cada vez.

Eu preferiria que houvesse uma maneira de escrever import A: x em vez de ser equivalente a import A.x .

Eu voto em import A: x já que também podemos; import A: x, y, @z mas import A.x, A.y, a.@z ficaria feio.

Isso ser removido de 1.0 significa que ficaremos com using e import para 1.0? Isso é um pouco lamentável na minha opinião.

E quanto a:

  • Forçar o prefixo do módulo para extensão. Isso tornará o código mais claro que uma extensão é pretendida em vez de ser acidental de uma importação em algum outro arquivo.
  • using A torna-se import A: ...
  • using A.X ( X é um módulo) torna-se importação A.X: ...
  • using A: X ( X não é um módulo) torna-se import A: X
  • import A: X não é alterado, mas você não pode estender automaticamente X (veja o primeiro ponto)
  • exclua a palavra-chave using

Estou perdendo algum caso de uso? Talvez isso já tenha sido sugerido...

O que eu gosto de ser explícito sobre o módulo ao estender é que a extensão se torna muito mais local. Neste momento, quando um método é estendido, é comum colocar o import bem próximo ao topo do módulo (que pode estar em um arquivo completamente diferente!). Quando o método estendido é excluído, a instrução de importação geralmente é esquecida.

Acho que é essencialmente o mesmo que a minha proposta acima, que teve bastante apoio. @JeffBezanson realmente quer manter using A pelo menos para facilitar o uso e using A: x porque aparentemente (não estou convencido disso), é um caso de uso importante poder importar uma ligação em de tal forma que você não pode estendê-lo. Existem algumas propostas para ir na outra direção e substituir import por using mas nenhuma delas realmente teve muita tração ( import parece mais fundamental).

Acho que a diferença está em:

  • import A: x, y # hard bindings para x e y de A

Assumindo que o significado de "hard binding" é tal que você pode estendê-lo sem prefixo de módulo, na minha versão não há hard bindings. Se você quiser estender, seu módulo prefixa exatamente onde você o estende. Nenhuma instrução import assustadora em outros arquivos alterando o significado se algo é uma extensão ou não.

e using A: x porque aparentemente (não estou convencido disso), é um caso de uso importante poder importar uma ligação de forma que você não possa estendê-la.

Forçar o prefixo do módulo não está lidando com isso? Ou estamos falando de não módulos como:

module M
    x = 1
end

Ambos import M: x; x = 2 e using M: x; x = 2 dão a mesma mensagem de aviso, então não vejo qual é o problema...

Manter using A para facilitar mais de import A: ... parece um pouco excessivo na minha opinião.

Forçar o prefixo do módulo não está lidando com isso?

Sim; se você tivesse que qualificar funções para estendê-las, esse ponto seria irrelevante.

Continuar usando A para facilitar a importação A: ... parece um pouco excessivo na minha opinião.

Eu vejo de maneira oposta; fazer as pessoas mudarem de using A (que é bom e curto e estamos todos acostumados) para import A: ... apenas para satisfazer um requisito artificial de que haja apenas 1 palavra-chave é excessivo.

A partir da leitura do thread, parece que o principal valor em ter duas palavras-chave é diferenciar as ligações que podem ser estendidas ou não (ligação rígida). Com isso em mente, acho que existem duas soluções viáveis:

  • 1 palavra-chave + requer prefixo para extensão
  • duas palavras-chave, uma para uso normal (sem extensão), e uma segunda para uso estendido, sugiro using e extending neste caso. import é bom, mas extending torna óbvia a razão para a existência de uma segunda palavra-chave

Em ambos os casos, sugiro que using seja como está agora com a adição de um dos seguintes para vincular apenas o módulo Foo :

  • using Foo: nothing (funciona agora)
  • using Foo: Foo (funciona agora)
  • using Foo: (pode ser adicionado mais tarde)

Então extending deve se comportar de forma idêntica a using com a única diferença é que você pode estender ligações trazidas com extending e possivelmente não permitir extending Foo para que ele tenha ser explícito.

Comportamento atual

| | disponibilizar (usando) | tornar extensível (importação)|
| ------------------- | -------------------------- | ---------------------- |
| único módulo | using module: module ou using module: nothing | import module |
| tudo exportado | using module (efeito colateral: age como import module também) | ? |
| coisas particulares | using module: x,y | import module: x,y |

Sugestão

| | disponibilizar (usando) | tornar extensível (importação) |
| ----------------- | ------------------------ | -------------------------- |
| único módulo | using module | import module |
| tudo exportado | using module: * | import module: * |
| coisas particulares | using module: x,y | import module: x,y |

O bom disso é que importar mais corresponde a escrever mais. Ou seja, você começa com using module e se quiser importar uma variável diretamente para o namespace você adiciona um : x em vez de remover um nothing ou module . Isso também significa que a coisa mais curta que você digita inclui o mínimo.

Você também pode fazer using: *,x para disponibilizar tudo o que foi exportado e x que não foi exportado.

Compromisso para compatibilidade com versões anteriores:

| | disponibilizar (usando) | tornar extensível (importação) |
| ----------------- | ------------------------ | -------------------------- |
| único módulo | using module: | import module: |
| tudo exportado | using module: * | import module: * |
| coisas particulares | using module: x,y | import module: x,y |

mantenha em torno using module e import module com o comportamento atual para compatibilidade com versões anteriores, mas deprecie-o.

@FelixBenning : import Module atualmente não (por si só) torna nada extensível mais do que using Module , apenas carrega o código e traz Module (e nada mais) para o namespace .

Apenas para espelhar o que eu disse no slack e para que ele não desapareça no slack hole:

Eu não acho que tornar using X: * o padrão para disponibilizar todas as coisas exportadas versus apenas using X tornará as pessoas necessariamente mais cautelosas com o que importam. Eu sei, apontar para como os outros fazem isso é considerado ruim, mas o Python basicamente tem essa semântica com seus import X e import X: * , mas seu ecossistema está repleto dessas importações de estrelas 🤷‍♂️ (e eles estão notoriamente odiando isso) Eu não acho que o texto marginalmente mais longo que alguém tem que digitar impede as pessoas de fazerem o que elas consideram ser o mais conveniente: apenas importar/usar tudo e deixar o compilador descobrir. É por isso que desconfio da bala mágica de fazer as pessoas escreverem essa estrela explicitamente.

Além disso, import module: * e using module: * não estão disponíveis para o significado proposto. Já tem um significado, pois * é um identificador Julia válido e pode ser importado/usado como + ou a palavra mul .

@tpapp ou eu entendi mal a documentação novamente, ou import Module torna Module.x extensível. Enquanto using Module: x não torna Module.x extensível. Portanto import Module torna algo disponível para extensão e using Module também, e é por isso que observei que usar tem esse efeito colateral.
grafik
(de https://docs.julialang.org/en/v1/manual/modules/)

Realmente não importa qual de nós está certo - em ambos os casos, a situação atual é obviamente uma bagunça se não conseguirmos descobrir o que tudo faz.

@mbauman bom ponto - eu esqueci disso. Eu realmente não me importo com o * apenas a estrutura de ter using espelhando as coisas que import faz com a diferença de importar vs usar sendo se as coisas se tornam extensíveis ou não. Então, se houver um símbolo mais apropriado - all , __all__ , everything , exported , ...? Eu sou a favor. Eu só acho que importar mais provavelmente deve ser refletido digitando mais.

Mas se você não quer isso, você também pode ir para

| | disponibilizar (usando) | tornar extensível (importação) |
| ----------------- | ------------------------ | -------------------------- |
| único módulo | using module: module | import module: module |
| tudo exportado | using module | import module |
| coisas particulares | using module: x,y | import module: x,y |

ou

| | disponibilizar (usando) | tornar extensível (importação) |
| ----------------- | ------------------------ | -------------------------- |
| único módulo | using module | import module |
| tudo exportado | using module: module | import module: module |
| coisas particulares | using module: x,y | import module: x,y |

Mas seja qual for o resultado final, ele deve ser consistente. E no momento simplesmente não é.

using A torna A.f extensível, _não_ f por si só. Para estender _just_ f sem declarar de qual módulo você quer que ele seja estendido, você tem que import A: f explicitamente. Caso contrário, você ainda terá que qualificá-lo.

Verifique o seguinte para a semântica de using

julia> module A
        export f
        f() = "no args in A"
       end
Main.A

julia> f()
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at REPL[2]:1

julia> using .A

julia> f()
"no args in A"

julia> f(1)
ERROR: MethodError: no method matching f(::Int64)
Closest candidates are:
  f() at REPL[1]:3
Stacktrace:
 [1] top-level scope at REPL[5]:1

julia> f(x) = "one arg where?"
ERROR: error in method definition: function A.f must be explicitly imported to be extended
Stacktrace:
 [1] top-level scope at none:0
 [2] top-level scope at REPL[6]:1

julia> A.f(x) = "one arg where?"

julia> f(1)
"one arg where?"

E isso para a semântica de import :

julia> module A
        export f
        f() = "no args in A"
       end
Main.A

julia> f()
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at REPL[2]:1

julia> import .A

julia> f()
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at REPL[4]:1

julia> A.f()
"no args in A"

julia> f(1)
ERROR: UndefVarError: f not defined
Stacktrace:
 [1] top-level scope at REPL[6]:1

julia> A.f(1)
ERROR: MethodError: no method matching f(::Int64)
Closest candidates are:
  f() at REPL[1]:3
Stacktrace:
 [1] top-level scope at REPL[7]:1

julia> f(x) = "one arg where?"
f (generic function with 1 method)

julia> f(1)
"one arg where?"

julia> A.f(1)
ERROR: MethodError: no method matching f(::Int64)
Closest candidates are:
  f() at REPL[1]:3
Stacktrace:
 [1] top-level scope at REPL[10]:1

julia> A.f(x) = "one arg where in A"

julia> A.f(1)
"one arg where in A"

@FelixBenning : sim, acho que você não entendeu. FWIW, eu acho que o "... torna Foo.x extensível" é uma maneira confusa de abordar a distinção --- você sempre pode definir métodos para nomes de funções totalmente qualificados. O que acontece com using Foo: x é que o próprio Foo não será trazido para o namespace.

Aliás, relendo este tópico, estou me perguntando se #25306 nos trouxe a uma espécie de ótimo local, e a única decisão que resta é se precisamos de importações não extensíveis para o namespace (atualmente using Foo: f ). Mas como isso está quebrando, o capítulo do manual talvez se beneficie de uma reescrita nesse meio tempo, muitos usuários acham tudo confuso.

É assim que eu abordaria o status quo nos documentos:

  1. using M carrega o módulo e traz M e seus símbolos exportados para o namespace. Este é o único caso em que as exportações importam.
  2. uma vez que você tenha M , você pode usar nomes qualificados como M.y para (a) acessar símbolos não exportados e (b) adicionar métodos a funções, independentemente de serem exportados ou não
  3. Julia impede que você adicione métodos a funções, a menos que você use nomes qualificados como M.f ou ...
  4. ... import M: f , então você pode simplesmente usar f ao definir métodos.
  5. import M e using M: x são para trazer apenas M ou x (e não M ), respectivamente, para o namespace, respectivamente. Algumas pessoas gostam de usar esses formulários no código do pacote para garantir que seus namespaces sejam mantidos limpos (link para guias de estilo relevantes aqui).

A ordem reflete mais ou menos os casos de uso encontrados com uso mais avançado. Todos os itens acima se aplicam a submódulos, com M.A no lugar de M .

O que dizer disso:

  • using M traz M para o escopo
  • using M: x traz M.x para o escopo (como x )
  • using M: ... traz todos os símbolos exportados de M para o escopo
  • using M: x, ... traz todos os símbolos exportados de M no escopo e também x (que podem não ser exportados)
  • Não há import . Você precisa usar o nome qualificado para estender uma função. (Ou using M; const foo = M.foo como já se pode fazer agora.)
  • Em todos os itens acima, M também pode ser um submódulo, por exemplo, Foo.Bar e x também podem ser x as y com o significado atual.

Ou usamos import em vez de using , o que torna isso igual a https://github.com/JuliaLang/julia/issues/8000#issuecomment -355960915.

Um uso muito comum (especialmente em "scripts", mas também em pacotes para certos estilos é)

using Foo, Bar, Baz

e apenas confiando em símbolos exportados. Haveria algum benefício em manter isso o mais simples, possivelmente tão simples quanto é agora.

@tpapp

Acho que você entendeu errado. FWIW, eu acho que o "... torna o Foo.x extensível" é uma maneira confusa de abordar a distinção --- você sempre pode definir métodos para nomes de funções totalmente qualificados.

Ok, então posso estender Foo.x depois using Foo: x ? Porque se sim, então a documentação não está completa (veja minha captura de tela). Se não, então eu entendi perfeitamente como essas declarações funcionam e obviamente import Foo fez algo para tornar Foo.x extensível. Portanto, ele literalmente torna Foo.x disponível para extensão. Em todos os sentidos dessas palavras. Claro que não disponibiliza x para extensão, mas é para isso que serve import Foo: x

Ok, então posso estender Foo.x depois using Foo: x ?

Não, a menos que você traga Foo para o namespace de alguma forma (por exemplo using Foo ).

Eu entendi perfeitamente como essas declarações funcionam

Não tenho certeza sobre isso - no entanto, se você tiver dúvidas sobre módulos e namespaces, por favor, use o fórum do Discourse.

obviamente import Foo fez algo para tornar Foo.x extensível

Apenas no sentido de que você deve poder se referir a uma função de alguma forma usando um nome qualificado antes de adicionar métodos a ela.

então a documentação não está completa

Eu acho que é, de uma forma meio peculiar. Nesse exemplo em particular, se tudo que você faz é using MyModule: x, p ; então nenhum método está disponível para extensão, então a tabela está correta.

Concordo que poderia ser melhor escrito, como disse acima. Muitas pessoas não acostumadas com namespaces acham confuso. E, TBH, todo o circo using / import é um pouco confuso, daí esse problema.

@tpapp

Ok, então aqui está a coisa: não é absolutamente óbvio que você possa estender todas as funções com o nome completo se o módulo estiver no namespace. Eu sei que esse é um comportamento atual, mas não acho que alguém que já não saiba disso assumiria isso. Especialmente porque estar no namespace nem sempre significa que ele também é extensível. Se eu fizer using module:x não posso estender x . Embora eu possa estender x , se eu usar import module:x . Portanto, é uma suposição razoável, que a diferença entre using e import é se você pode ou não estender as funções importadas.

Usando essa suposição, faria sentido, se using module permitisse apenas o uso de module.x , mas não permitisse a extensão de module.x . Enquanto import module permitiria a extensão de module.x . Portanto, se você tomar essa suposição e ler a documentação, descobrirá que duas coisas estão erradas.

  1. using module permite estender module.x . Portanto, do ponto de vista dos alunos, using module tem o efeito colateral , que também age como import module - ou seja, torna module.x extensível. E tornar as coisas extensíveis é uma coisa import , não uma coisa de using
  2. em contraste com import , using não apenas disponibiliza module , mas sim tudo nele.

Então é isso que eu tentei representar com a minha mesa. Se duas palavras diferentes forem usadas (importar/usar), elas devem fazer duas coisas diferentes e isso deve ser claro. Se using às vezes permite extensão e outras vezes não, isso não é um comportamento previsível.

Uma boa alternativa é remover completamente a importação como sugere @martinholters . Você simplesmente não pode ter duas palavras que são usadas aleatoriamente para certas coisas. Se import significa tornar as coisas extensíveis ao importar certas funções, enquanto using module: foo não permite isso, então o mesmo comportamento deve acontecer quando você inclui apenas module no namespace.

Só porque você pode estender tudo pelo nome completo agora se o módulo estiver no namespace, não significa que isso seja uma coisa óbvia ou direta.

Ou estar no namespace é suficiente, então eu também deveria poder estender x se eu o incluísse no namespace com using module:x ou estar no namespace não é suficiente, e então eu também deveria não poderá estender module.x ao usar o comando using .
Ou terceira opção: não há import e você simplesmente tem que estender funções com seu nome completo o tempo todo.

Não é absolutamente óbvio que você possa estender cada função com o nome completo se o módulo estiver no namespace.

Eu concordo, e é por isso que defendo uma reescrita dos documentos. No entanto, isso é tangencial à questão atual. Seria melhor manter (1) propostas para melhorar a documentação da API atual e (2) propostas para redesenhá-la separadas, mesmo que as duas estejam um pouco relacionadas. Eu pretendo fazer um PR para (1) em breve.

@tpapp Você não pode documentar algo que inerentemente não faz sentido. Nenhuma dessas documentações está correta:

  1. "Você pode estender qualquer coisa que esteja no namespace" (porque using module:x não permite estender x )
  2. "Estar no namespace não é suficiente para extensão, é por isso que existem palavras separadas using e import " (falso porque para nomes completos é de fato suficiente estar no namespace - parando isso ser suficiente foi minha sugestão)
  3. "Você pode estender qualquer coisa no Namespace pelo nome completo" (o que exigiria que import module:x deixasse de existir para ser a verdade completa - proposta @martinholters )

Qualquer um desses seria aceitável. Mas nada disso é realmente realidade. A realidade é que atualmente existem duas palavras diferentes usadas para "importar coisas", mas elas não têm um caso de uso distinto. É como um loop for às vezes se comporta como um loop while se você iterar sobre booleanos. Nenhuma quantidade de documentação fará com que isso não seja confuso

Você não pode documentar algo que inerentemente não faz sentido.

A API de módulos atual é bem definida, então pode ser documentada (já é, só acho que deveria ser melhor).

Nenhuma dessas documentações está correta:

Por favor, tenha a gentileza de esperar pelo meu PR (ou de outra pessoa) e comentar sobre o texto real que estará lá. Postar documentação hipotética e depois alegar que ela está incorreta é apenas adicionar ruído a essa discussão.

@tpapp talvez eu devesse ter dito,

você não pode documentar algo de uma maneira não confusa que não faça sentido inerentemente

Quero dizer, em certo sentido, o código é toda a documentação que você precisa, certo? O que há de errado com isso? O tempo que leva para digerir. E atualmente não vejo uma maneira breve de descrever como funciona, porque está cheio de exceções. Exceções que não deveriam estar lá em primeiro lugar.

Você realmente não vê o que estou tentando transmitir?

Acho que há um consenso geral de que a situação atual é desnecessariamente complexa e inadequadamente documentada. E, sem dúvida, simplificar o maquinário facilitará a documentação. Mas tal simplificação estará quebrando, então não pode ser feita antes de 2.0. Então, seu ponto de vista é que a melhoria da documentação anterior não faria sentido? Então eu discordo. Eu vejo dois problemas separados: Simplificação a ser feita com 2.0 (sobre o qual este problema é) que (espero) incluirá as atualizações de documentação necessárias e melhorará a documentação do funcionamento atual que, lendo este tópico, parece muito necessário, mas é outro problema .

Depois de fazer #38271, acho que as questões pendentes são

Como lidar com a adição de métodos a funções em outros módulos

  1. sempre exigem nomes totalmente qualificados ( Foo.bar() = ... ),
  2. também apenas permita quando o símbolo estiver no escopo ( using Foo: bar; bar() = ... )
  3. (2), mas coloque no escopo de uma maneira especial ( status quo , import Foo: bar; bar() = ... )

Qual sintaxe lida com listas de exportação e apenas o nome do módulo

  1. using Foo traz todos os símbolos exportados em Foo para o escopo, import Foo apenas o módulo ( status quo )
  2. using Foo: ... ou alguma outra sintaxe semelhante, então using Foo apenas o módulo.

(1|2) & 2 permitiriam a unificação em uma única palavra-chave, using ou import , ao custo de perder uma única linha

using LinearAlgebra, Random, StaticArrays

Não tenho certeza se vale a pena, mesmo sem considerar a mudança de ruptura.

Este não é um daqueles problemas que oferecem uma solução limpa que o projeto original simplesmente deixou passar; há trocas. Eu esperaria e veria se uma documentação melhor pode melhorar a experiência do usuário, mantendo a configuração atual (1.0).

Para o 2.0, acho que apenas uma limpeza da sintaxe para ser mais consistente e descritiva do que realmente está acontecendo seria bom. Algo como:

| Antes | Depois |
|-|-|
| using Foo | useall from Foo |
| import Foo | use Foo |
| using Foo: a | use a from Foo |
| import Foo: a e import Foo.a | extend a from Foo |

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

Questões relacionadas

TotalVerb picture TotalVerb  ·  3Comentários

StefanKarpinski picture StefanKarpinski  ·  3Comentários

omus picture omus  ·  3Comentários

manor picture manor  ·  3Comentários

m-j-w picture m-j-w  ·  3Comentários