Julia: casos de uso para `import` e` using`

Oct 08 2020

Então li a documentação para usingeimport em Julia. No entanto, o que isso não me diz é como devo usar essas duas afirmações na prática (e, dada a falta de ortogonalidade, isso não é muito fácil).

Caso em questão: vamos colocar o seguinte código trivial em "myfile.jl":

module MyModule
f() = 1
export f
end
import .MyModule # or: using .MyModule
  • se eu usar importna última linha, fnão será exportado para o Mainnamespace. No entanto, quando eu mudo "myfile.jl"(por exemplo, modificando o valor de retorno de f) e, em seguida, reinicio include, a função é substituída (este é o comportamento que desejo). (Observe que eu poderia explicitamente import .MyModule: f, mas isso introduz redundância desnecessária; além disso, o caso da vida real envolverá uma longa lista de funções com nomes longos. OK, também posso escrever uma macro que usa names(Main.MyModule), mas de alguma forma sinto que isso deveria seja mais simples.)

  • se eu substituir importpor using, isso é invertido: fagora é exportado, mas para alterar qualquer coisa no módulo agora é necessário reiniciar o interpretador Julia.

  • usando importe usingexporta apenas a primeira versão de f()para o namespace principal: quando eu atualizo o código, apenas o primeiro valor de retorno é usado.

Portanto, minha pergunta não é sobre o comportamento de ambas as declarações importe using, que estão documentadas (se não explicadas ) na página vinculada, mas sobre a intenção por trás delas. Por que duas declarações quando uma seria suficiente? Por que um desses ignora todas as exportdiretivas? Em que caso devo, na prática, usar cada afirmação?

(a versão é 1.1.0. Além disso, funciona em um sistema sem fácil Pkgacesso, então não tentei Reviseainda.)

Respostas

9 PrzemyslawSzufel Oct 08 2020 at 18:17

A convenção em Julia deve using PackageNameser usada, a menos que você tenha algum motivo específico para fazer o contrário.

Esta decisão de design de linguagem é motivada simplesmente pelo conforto dos usuários. Julia está usando várias funções de envio e, normalmente, pacotes de exportação de funções que operam em argumentos digitados (no entanto, geralmente são abstracttipos, portanto a funcionalidade das funções não é limitada).

Com esse design, todas as funções e métodos que os implementam podem estar no mesmo namespace, uma vez que diferem nos tipos de parâmetro que são específicos para seus pacotes. Portanto, um cenário em que usingtraz um pacote para o namespace que substitui as implementações de método existentes é muito raro (e então Julia relata um aviso). Esse processo é controlado adicionalmente pelos autores do pacote por meio do uso da exportpalavra - chave.

Acho que parece estranho para você porque está acostumado com linguagens como Python. Por exemplo, numpytem exatamente os mesmos nomes de função que as funções padrão e não há uma boa maneira de nomeá-los de forma diferente (por exemplo, tente pensar qual seria uma boa alternativa para uma função chamada sin:-)). Portanto, em Python, você sempre precisa importar funções para seus próprios namespaces, talvez com um alias como import numpy as np. Depois de ter vários anos de experiência com os dois idiomas, eu diria que a maneira como Julia faz isso é natural e a maneira Pytoniana é estranha :-)

Em relação às importpossibilidades de trazer funções únicas de um pacote para o seu namespace, vejo isso como uma solução alternativa para situações em que ocorre um conflito de nomes. Além disso, você precisa fazer isso explicitamente se quiser adicionar novas implementações de método a algum outro pacote (como definir um novo significado para o +operador). Fora isso, basta digitar usinge nunca mais olhar para trás.

2 CameronBieganek Oct 08 2020 at 23:12

Uma coisa a acrescentar, além da resposta de Przemyslaw Szufel. Quando você está estendendo um método, é possível fazer isso sem usar importpara a função que você está estendendo. Por exemplo, se eu quiser estender a fpartir do módulo Foo, posso fazer using Fooe definir um novo método para Foo.f. Aqui está um exemplo completo:

module Foo
    struct A
        x::Int
    end

    f(a::A) = a.x + 1

    export A, f
end

using .Foo

struct B
    x::Int
end

Foo.f(b::B) = b.x + 2

A vantagem dessa abordagem é que, ao ler o código, é mais fácil ver a origem da função que você está estendendo.