Julia: casi d'uso per "import" e "using"

Oct 08 2020

Quindi ho letto la documentazione per usingeimport in Julia. Tuttavia ciò che questo non mi dice è come dovrei usare queste due affermazioni nella pratica (e, data la mancanza di ortogonalità, questo non è troppo facile).

Caso in questione: inseriamo il seguente codice banale in "myfile.jl":

module MyModule
f() = 1
export f
end
import .MyModule # or: using .MyModule
  • se uso importsull'ultima riga, fnon viene esportato nello Mainspazio dei nomi. Tuttavia, quando cambio "myfile.jl"(ad esempio modificando il valore di ritorno di f) e poi lo reinserisco include, la funzione viene sostituita (questo è il comportamento che desidero). (Nota che potrei esplicitamente import .MyModule: f, ma questo introduce ridondanza non necessaria; inoltre, il caso della vita reale coinvolgerà un lungo elenco di funzioni con nomi lunghi. OK, potrei anche scrivere una macro che usa names(Main.MyModule), ma in qualche modo sento che dovrebbe essere più semplice.)

  • se sostituisco importcon using, allora viene invertito: fora viene esportato, ma la modifica di qualsiasi cosa nel modulo ora richiede il riavvio dell'interprete Julia.

  • utilizzando entrambi imported usingesporta solo la prima versione di f()nello spazio dei nomi principale: quando aggiorno il codice, viene utilizzato solo il primo valore restituito.

Quindi la mia domanda non riguarda il comportamento di entrambe le affermazioni importe using, che sono documentate (se non spiegate ) nella pagina collegata, ma dell'intento alla base di queste. Perché due affermazioni quando una sarebbe sufficiente? Perché uno di questi ignora tutte le exportdirettive? In quale caso dovrei, in pratica, utilizzare ciascuna affermazione?

(la versione è 1.1.0. Inoltre, funziona su un sistema senza un facile Pkgaccesso, quindi non l'ho Reviseancora provato .)

Risposte

9 PrzemyslawSzufel Oct 08 2020 at 18:17

La convenzione in Julia è quella di utilizzare a using PackageNamemeno che tu non abbia qualche motivo specifico per fare diversamente.

Questa decisione di progettazione del linguaggio è motivata semplicemente dal comfort degli utenti. Julia utilizza più invii e in genere i pacchetti esporta funzioni che operano su argomenti digitati (quelli sono tuttavia di solito abstracttipi, quindi la funzionalità delle funzioni non è limitata).

Con questo design, tutte le funzioni ei metodi che li implementano possono trovarsi nello stesso spazio dei nomi poiché differiscono per i tipi di parametri specifici per i loro pacchetti. Quindi, uno scenario in cui usingporta un pacchetto nello spazio dei nomi che sovrascrive le implementazioni di metodi esistenti è molto raro (e quindi Julia riporta un avviso). Questo processo è inoltre controllato dagli autori del pacchetto tramite l'utilizzo della exportparola chiave.

Penso che ti sembri strano perché sei abituato a linguaggi come Python. Per un esempio numpyha esattamente gli stessi nomi di funzione delle funzioni predefinite e non c'è un buon modo per nominarle in modo diverso (ad esempio, prova a pensare quale sarebbe una buona alternativa per una funzione chiamata sin:-)). Quindi, in Python è sempre necessario importare le funzioni nei propri spazi dei nomi magari con un alias come import numpy as np. Dopo aver avuto diversi anni di esperienza con entrambe le lingue, direi che il modo in cui Julia lo fa è naturale e il modo Pytonish è strano :-)

Per quanto riguarda le importpossibilità di portare singole funzioni da un pacchetto al tuo spazio dei nomi, lo vedo come una soluzione alternativa per le situazioni in cui si verifica un conflitto di nomi. Inoltre è necessario farlo in modo esplicito se si desidera aggiungere nuove implementazioni di metodi a qualche altro pacchetto (come definire un nuovo significato per l' +operatore). Oltre a questo, digiti usinge non guardi mai indietro.

2 CameronBieganek Oct 08 2020 at 23:12

Una cosa da aggiungere, oltre alla risposta di Przemyslaw Szufel. Quando estendi un metodo, è possibile farlo senza utilizzare importper la funzione che stai estendendo. Ad esempio, se voglio estendere fdal modulo Foo, posso fare using Fooe quindi definire un nuovo metodo per Foo.f. Ecco un esempio 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

Il vantaggio di questo approccio è che durante la lettura del codice è più facile vedere l'origine della funzione che si sta estendendo.