Modules

モジュールは、Juliaでコードを一貫した単位に整理するのに役立ちます。モジュールは、module NameOfModule ... endの中で構文的に区切られ、以下の特徴を持っています:

  1. モジュールは別々の名前空間であり、それぞれ新しいグローバルスコープを導入します。これは便利で、異なる関数やグローバル変数に同じ名前を使用しても衝突がないため、別々のモジュールにある限り問題ありません。
  2. モジュールは詳細な名前空間管理のための機能を持っています:各モジュールは、exportしてpublicとしてマークされた名前のセットを定義し、他のモジュールから名前をusingおよびimportでインポートすることができます(これについては以下で説明します)。
  3. モジュールは、より高速な読み込みのために事前コンパイルされることができ、ランタイム初期化のためのコードを含む場合があります。

通常、大きなJuliaパッケージでは、モジュールコードがファイルに整理されているのを見かけます。例えば、

module SomeModule

# export, public, using, import statements are usually here; we discuss these below

include("file1.jl")
include("file2.jl")

end

ファイルとファイル名はモジュールとはほとんど関係がなく、モジュールはモジュール式にのみ関連しています。モジュールごとに複数のファイルを持つことができ、ファイルごとに複数のモジュールを持つことができます。includeは、ソースファイルの内容が含まれているモジュールのグローバルスコープで評価されたかのように振る舞います。この章では、短く簡略化された例を使用するため、includeは使用しません。

モジュールの本体をインデントしないことが推奨されます。なぜなら、それは通常、ファイル全体がインデントされることにつながるからです。また、モジュール名には UpperCamelCase を使用することが一般的であり(型と同様に)、特にモジュールに同様の名前の識別子が含まれている場合は、名前の衝突を避けるために複数形を使用することが推奨されます。例えば、

module FastThings

struct FastThing
    ...
end

end

Namespace management

名前空間管理とは、モジュール内の名前を他のモジュールで利用できるようにするために言語が提供する機能を指します。関連する概念と機能について、以下で詳細に説明します。

Qualified names

グローバルスコープ内の関数、変数、型の名前は、常に親モジュールに属し、これは parentmodule を使ってインタラクティブに見つけることができます。

julia> parentmodule(UnitRange)
Base

これらの名前は、親モジュールの名前を接頭辞として付けることで、親モジュールの外部でも参照できます。例えば、Base.UnitRangeのようにです。これは修飾名と呼ばれます。親モジュールは、Base.Math.sinのようにサブモジュールのチェーンを使用してアクセスでき、Base.Mathモジュールパスと呼ばれます。構文の曖昧さのために、演算子のような記号のみを含む名前を修飾するにはコロンを挿入する必要があります。例えば、Base.:+のようにです。少数の演算子は、さらに括弧を必要とします。例えば、Base.:(==)のようにです。

名前が修飾されている場合、それは常に アクセス可能 であり、関数の場合は、修飾名を関数名として使用することでメソッドを追加することもできます。

モジュール内では、変数名を global x と宣言することで、値を割り当てることなく「予約」することができます。これにより、ロード時以降に初期化されたグローバル変数との名前の衝突を防ぎます。構文 M.x = y は、別のモジュールでグローバル変数に割り当てるためには機能しません。グローバル変数の割り当ては常にモジュールローカルです。

Export lists

名前(関数、型、グローバル変数、および定数を指す)は、exportを使用してモジュールのエクスポートリストに追加できます:これらは、モジュールをusingしたときにインポートされるシンボルです。通常、ソースコードの読者が簡単に見つけられるように、モジュール定義の最上部またはその近くに配置されます。

julia> module NiceStuff
       export nice, DOG
       struct Dog end      # singleton type, not exported
       const DOG = Dog()   # named instance, exported
       nice(x) = "nice $x" # function, exported
       end;

しかし、これは単なるスタイルの提案です — モジュールは任意の場所に複数の export ステートメントを持つことができます。

API(アプリケーションプログラミングインターフェース)の一部を形成する名前をエクスポートすることは一般的です。上記のコードでは、エクスポートリストがユーザーに niceDOG を使用することを示唆しています。しかし、修飾名は常に識別子をアクセス可能にするため、これはAPIを整理するためのオプションに過ぎません。他の言語とは異なり、Juliaにはモジュール内部を真に隠すための機能はありません。

また、一部のモジュールは名前をまったくエクスポートしません。これは通常、derivativeのような一般的な単語をAPIで使用している場合に行われ、他のモジュールのエクスポートリストと簡単に衝突する可能性があります。名前の衝突を管理する方法については、以下で説明します。

名前を公開としてマークするには、using NiceStuffを呼び出す人々の名前空間にエクスポートせずに、exportの代わりにpublicを使用できます。これにより、公開名が公開APIの一部としてマークされますが、名前空間には影響を与えません。publicキーワードはJulia 1.11以降でのみ利用可能です。Julia 1.10以前との互換性を維持するためには、Compatパッケージの@compatマクロを使用してください。

Standalone using and import

インタラクティブな使用のために、モジュールをロードする最も一般的な方法は using ModuleName です。この loadsModuleName に関連付けられたコードであり、持ってきます。

  1. モジュール名
  2. およびエクスポートリストの要素を周囲のグローバル名前空間に。

技術的には、using ModuleNameという文は、ModuleNameというモジュールが必要に応じて名前を解決するために利用可能であることを意味します。現在のモジュールに定義がないグローバル変数が見つかると、システムはそれをModuleNameによってエクスポートされた変数の中から探し、見つかった場合はそれを使用します。これは、現在のモジュール内でそのグローバル変数のすべての使用がModuleName内のその変数の定義に解決されることを意味します。

パッケージからモジュールをロードするには、using ModuleNameという文を使用できます。ローカルで定義されたモジュールからモジュールをロードするには、モジュール名の前にドットを追加する必要があります。例えば、using .ModuleNameのようにします。

例を続けるために、

julia> using .NiceStuff

上記のコードを読み込むと、NiceStuff(モジュール名)、DOG、および nice が利用可能になります。Dog はエクスポートリストには含まれていませんが、モジュールパス(ここではモジュール名のみ)で名前を修飾すればアクセスできます。つまり、NiceStuff.Dog としてアクセス可能です。

重要なことに、using ModuleName は、エクスポートリストが重要となる唯一の形式です

対照的に、

julia> import .NiceStuff

モジュール名のみをスコープに持ち込みます。ユーザーはその内容にアクセスするために NiceStuff.DOGNiceStuff.Dog、および NiceStuff.nice を使用する必要があります。通常、import ModuleName はユーザーが名前空間をクリーンに保ちたい場合に使用されます。次のセクションで見るように、import .NiceStuffusing .NiceStuff: NiceStuff と同等です。

複数の using および import ステートメントを同じ種類でカンマ区切りの式にまとめることができます。例えば、

julia> using LinearAlgebra, Random

using and import with specific identifiers, and adding methods

using ModuleName: または import ModuleName: の後にカンマ区切りの名前のリストが続く場合、モジュールはロードされますが、その特定の名前のみが名前空間に取り込まれます。例えば、

julia> using .NiceStuff: nice, DOG

niceDOG の名前をインポートします。

重要なことに、モジュール名 NiceStuff名前空間 に含まれません。アクセス可能にしたい場合は、明示的にリストする必要があります。

julia> using .NiceStuff: nice, DOG, NiceStuff

2つ以上のパッケージ/モジュールが名前をエクスポートし、その名前が各パッケージで同じものを指さない場合、明示的な名前のリストなしにusingを介してパッケージがロードされると、その名前を修飾なしで参照することはエラーになります。したがって、依存関係やJuliaの将来のバージョンとの互換性を考慮したコード、例えばリリースされたパッケージのコードは、各ロードされたパッケージから使用する名前をリストすることが推奨されます。例えば、using Foo: Foo, fのようにするべきであり、using Fooのようにするべきではありません。

ジュリアには、一見同じことのための2つの形式があります。なぜなら、import ModuleName: fのみが、モジュールパスなしで fにメソッドを追加することを許可するからです。言い換えれば、以下の例はエラーを返します:

julia> using .NiceStuff: nice

julia> struct Cat end

julia> nice(::Cat) = "nice 😸"
ERROR: invalid method definition in Main: function NiceStuff.nice must be explicitly imported to be extended
Stacktrace:
 [1] top-level scope
   @ none:0
 [2] top-level scope
   @ none:1

このエラーは、他のモジュールの関数に意図せずメソッドを追加するのを防ぎます。

この問題に対処する方法は2つあります。常にモジュールパスで関数名を修飾することができます:

julia> using .NiceStuff

julia> struct Cat end

julia> NiceStuff.nice(::Cat) = "nice 😸"

代わりに、特定の関数名を import することができます:

julia> import .NiceStuff: nice

julia> struct Cat end

julia> nice(::Cat) = "nice 😸"
nice (generic function with 2 methods)

どちらを選ぶかはスタイルの問題です。最初の形式は、別のモジュールの関数にメソッドを追加していることを明確に示します(インポートとメソッド定義が別のファイルにある可能性があることを忘れないでください)。一方、2番目の形式は短く、特に複数のメソッドを定義する場合に便利です。

変数が using または import を介して可視化されると、モジュールは同じ名前の独自の変数を作成することはできません。インポートされた変数は読み取り専用です; グローバル変数に代入すると、常に現在のモジュールが所有する変数に影響を与えるか、エラーが発生します。

Renaming with as

import または using によってスコープに持ち込まれた識別子は、キーワード as を使って名前を変更することができます。これは、名前の衝突を回避するためや、名前を短縮するために便利です。例えば、Base は関数名 read をエクスポートしますが、CSV.jl パッケージも CSV.read を提供しています。CSV 読み込みを何度も呼び出す場合、CSV. 修飾子を省略するのが便利です。しかし、その場合、Base.read を指しているのか CSV.read を指しているのかが曖昧になります。

julia> read;

julia> import CSV: read
WARNING: ignoring conflicting import of CSV.read into Main

名前の変更は解決策を提供します:

julia> import CSV: read as rd

インポートされたパッケージ自体も名前を変更できます:

import BenchmarkTools as BT

as は、単一の識別子がスコープに持ち込まれる場合にのみ using と共に機能します。例えば、using CSV: read as rd は機能しますが、using CSV as C は機能しません。なぜなら、これは CSV 内のすべてのエクスポートされた名前に対して操作するからです。

Mixing multiple using and import statements

複数の using または import ステートメントが上記のいずれかの形式で使用される場合、それらの効果は出現順に結合されます。例えば、

julia> using .NiceStuff         # exported names and the module name

julia> import .NiceStuff: nice  # allows adding methods to unqualified functions

NiceStuffのすべてのエクスポートされた名前とモジュール名自体をスコープに持ち込み、またniceにモジュール名をプレフィックスなしでメソッドを追加できるようにします。

Handling name conflicts

同じ名前をエクスポートする2つ(またはそれ以上の)パッケージがある状況を考えてみてください。

julia> module A
       export f
       f() = 1
       end
A
julia> module B
       export f
       f() = 2
       end
B

using .A, .Bは機能しますが、fを呼び出そうとすると、ヒント付きのエラーが発生します。

julia> using .A, .B

julia> f
ERROR: UndefVarError: `f` not defined in `Main`
Hint: It looks like two or more modules export different bindings with this name, resulting in ambiguity. Try explicitly importing it from a particular module, or qualifying the name with the module it should come from.

ここで、ジュリアはどの f を指しているのか決められないので、あなたが選択する必要があります。以下の解決策が一般的に使用されます:

  1. 単に A.fB.f のような資格付き名を使用してください。これにより、コードの読者にとって文脈が明確になります。特に、f がたまたま一致していても、さまざまなパッケージで異なる意味を持つ場合です。たとえば、degree は数学、自然科学、日常生活でさまざまな使い方があり、これらの意味は分けておくべきです。

  2. as キーワードを使用して、1つまたは両方の識別子の名前を変更します。例えば

    ```jldoctest module_manual julia> using .A: f as f

    julia> using .B: f as g

    ```

    B.fgとして利用できるようにします。ここでは、using Aを以前に使用していないと仮定しています。そうであれば、fは名前空間に取り込まれていたでしょう。

  3. 問題の名前が意味を共有する場合、1つのモジュールが別のモジュールからそれをインポートするか、他のパッケージで使用できるようにこのようなインターフェースを定義することだけを目的とした軽量の「ベース」パッケージを持つことが一般的です。そのようなパッケージ名は...Baseで終わるのが慣例です(これはJuliaのBaseモジュールとは関係ありません)。

Default top-level definitions and bare modules

モジュールは自動的に using Coreusing Base、および evalinclude 関数の定義を含み、これらはそのモジュールのグローバルスコープ内で式/ファイルを評価します。

これらのデフォルト定義が不要な場合、キーワード baremodule を使用してモジュールを定義できます(注:Core はまだインポートされています)。baremodule の観点から、標準の module は次のようになります:

baremodule Mod

using Base

eval(x) = Core.eval(Mod, x)
include(p) = Base.include(Mod, p)

...

end

もし Core さえも望まれない場合、何もインポートせず、名前を全く定義しないモジュールは Module(:YourNameHere, false, false) で定義でき、コードは @eval または Core.eval を使って評価できます。

julia> arithmetic = Module(:arithmetic, false, false)
Main.arithmetic

julia> @eval arithmetic add(x, y) = $(+)(x, y)
add (generic function with 1 method)

julia> arithmetic.add(12, 13)
25

Standard modules

重要な標準モジュールが3つあります:

  • Core には、言語に「組み込まれた」すべての機能が含まれています。
  • Base は、ほとんどすべてのケースで役立つ基本的な機能を含んでいます。
  • Main は、Juliaが起動したときのトップレベルモジュールおよび現在のモジュールです。
Standard library modules

デフォルトでは、Juliaは標準ライブラリモジュールをいくつか同梱しています。これらは通常のJuliaパッケージのように動作しますが、明示的にインストールする必要はありません。たとえば、単体テストを実行したい場合は、次のようにTest標準ライブラリをロードできます。

using Test

Submodules and relative paths

モジュールは サブモジュール を含むことができ、同じ構文 module ... end をネストすることができます。これは、複雑なコードベースを整理するのに役立つ別々の名前空間を導入するために使用できます。各 module は独自の scope を導入するため、サブモジュールは親から名前を自動的に「継承」するわけではないことに注意してください。

サブモジュールは、usingおよびimportステートメントで相対モジュール修飾子を使用して、囲む親モジュール(およびそれを含む)内の他のモジュールを参照することが推奨されます。相対モジュール修飾子は、現在のモジュールに対応するピリオド(.)で始まり、各連続する.は現在のモジュールの親に向かいます。必要に応じてモジュールが続き、最終的にアクセスする実際の名前が.で区切られて続きます。

次の例を考えてみましょう。ここで、サブモジュール SubA は関数を定義し、その後「兄弟」モジュールで拡張されます。

julia> module ParentModule
       module SubA
       export add_D  # exported interface
       const D = 3
       add_D(x) = x + D
       end
       using .SubA  # brings `add_D` into the namespace
       export add_D # export it from ParentModule too
       module SubB
       import ..SubA: add_D # relative path for a “sibling” module
       struct Infinity end
       add_D(x::Infinity) = x
       end
       end;

パッケージ内でコードを見ることができ、同様の状況で使用されます。

julia> import .ParentModule.SubA: add_D

しかし、これは code loading を通じて動作するため、ParentModule がパッケージ内にある場合にのみ機能します。相対パスを使用する方が良いです。

定義の順序も、値を評価する際には重要であることに注意してください。考慮してください。

module TestPackage

export x, y

x = 0

module Sub
using ..TestPackage
z = y # ERROR: UndefVarError: `y` not defined in `Main`
end

y = 1

end

SubTestPackage.yを定義される前に使用しようとしているため、値を持っていません。

同様の理由から、循環順序を使用することはできません:

module A

module B
using ..C # ERROR: UndefVarError: `C` not defined in `Main.A`
end

module C
using ..B
end

end

Module initialization and precompilation

大きなモジュールは、モジュール内のすべてのステートメントを実行することが多く、大量のコードをコンパイルする必要があるため、読み込むのに数秒かかることがあります。Juliaは、この時間を短縮するためにモジュールのプリコンパイルキャッシュを作成します。

プリコンパイルされたモジュールファイル(時々「キャッシュファイル」と呼ばれる)は、import または using がモジュールをロードする際に自動的に作成され、使用されます。キャッシュファイルがまだ存在しない場合、モジュールはコンパイルされ、将来の再利用のために保存されます。また、モジュールをロードせずにこれらのファイルを作成するために、Base.compilecache(Base.identify_package("modulename")) を手動で呼び出すこともできます。生成されたキャッシュファイルは、DEPOT_PATH[1]compiled サブフォルダーに保存されます。システムに何も変更がない場合、そのようなキャッシュファイルは、import または using でモジュールをロードする際に使用されます。

プリコンパイルキャッシュファイルは、モジュール、型、メソッド、および定数の定義を保存します。また、メソッドの特化やそれに対して生成されたコードを保存することもありますが、通常は開発者が明示的な precompile ディレクティブを追加するか、パッケージビルド中にコンパイルを強制するワークロードを実行する必要があります。

However, if you update the module's dependencies or change its source code, the module is automatically recompiled upon using or import. Dependencies are modules it imports, the Julia build, files it includes, or explicit dependencies declared by include_dependency(path) in the module file(s).

ファイル依存関係が include によって読み込まれる場合、変更はファイルサイズ(fsize)または内容(ハッシュに凝縮されたもの)が変更されていないかどうかを調べることによって判断されます。include_dependency によって読み込まれるファイル依存関係の場合、変更は修正時間(mtime)が変更されていないか、または修正時間が最も近い秒に切り捨てられているかどうかを調べることによって判断されます(サブ秒精度で mtime をコピーできないシステムに対応するため)。また、require の検索ロジックによって選択されたファイルへのパスが、プリコンパイルファイルを作成したパスと一致するかどうかも考慮されます。さらに、現在のプロセスにすでに読み込まれている依存関係のセットも考慮され、ファイルが変更されたり消えたりしても、それらのモジュールは再コンパイルされず、実行中のシステムとプリコンパイルキャッシュとの間に互換性のない状態を作成しないようにします。最後に、任意の compile-time preferences の変更も考慮されます。

モジュールが事前コンパイルに安全でないことがわかっている場合(例えば、以下に説明されている理由の1つのため)、モジュールファイルに__precompile__(false)を記述する必要があります(通常はファイルの先頭に配置します)。これにより、Base.compilecacheがエラーをスローし、using / importがそれを現在のプロセスに直接ロードし、事前コンパイルとキャッシングをスキップします。これにより、他の事前コンパイルされたモジュールによってそのモジュールがインポートされるのを防ぎます。

特定の動作について認識しておく必要があるかもしれません。これは、インクリメンタル共有ライブラリの作成に固有のものであり、モジュールを書く際には注意が必要です。たとえば、外部の状態は保持されません。これに対応するために、ランタイムで発生する必要がある初期化ステップと、コンパイル時に発生できるステップを明示的に分けてください。この目的のために、Juliaでは、ランタイムで発生する必要がある初期化ステップを実行する__init__()関数をモジュール内に定義することができます。この関数は、コンパイル中(--output-*)には呼び出されません。実質的に、この関数はコードのライフタイム中に正確に1回実行されると仮定できます。もちろん、必要に応じて手動で呼び出すこともできますが、デフォルトでは、この関数はローカルマシンの状態を計算することに関係していると仮定されており、コンパイルされたイメージにキャプチャする必要はありませんし、キャプチャすべきではありません。この関数は、モジュールがプロセスにロードされた後に呼び出されます。これは、インクリメンタルコンパイル(--output-incremental=yes)にロードされる場合も含まれますが、フルコンパイルプロセスにロードされる場合には呼び出されません。

特に、モジュール内に function __init__() を定義すると、Julia はモジュールがロードされた直後(例えば、importusing、または require によって)に __init__() を実行時に 最初 に呼び出します(つまり、__init__ は一度だけ呼び出され、モジュール内のすべてのステートメントが実行された後にのみ呼び出されます)。モジュールが完全にインポートされた後に呼び出されるため、サブモジュールや他のインポートされたモジュールの __init__ 関数は、囲むモジュールの __init__ に呼び出されます。

__init__の典型的な使用法の2つは、外部Cライブラリのランタイム初期化関数を呼び出すことと、外部ライブラリによって返されるポインタを含むグローバル定数を初期化することです。たとえば、Cライブラリlibfooを呼び出す必要があり、ランタイムでfoo_init()初期化関数を呼び出す必要があるとします。また、libfooによって定義されたvoid *foo_data()関数の戻り値を保持するグローバル定数foo_data_ptrを定義したいとします。この定数は、ポインタのアドレスが実行ごとに変わるため、ランタイムで初期化する必要があります(コンパイル時ではなく)。これを達成するには、モジュール内に次の__init__関数を定義することができます:

const foo_data_ptr = Ref{Ptr{Cvoid}}(0)
function __init__()
    ccall((:foo_init, :libfoo), Cvoid, ())
    foo_data_ptr[] = ccall((:foo_data, :libfoo), Ptr{Cvoid}, ())
    nothing
end

関数 __init__ 内でグローバルを定義することは完全に可能であり、これは動的言語を使用する利点の一つです。しかし、グローバルスコープで定数にすることで、コンパイラに型が知られることを保証し、より最適化されたコードを生成できるようにします。明らかに、foo_data_ptr に依存するモジュール内の他のグローバルも __init__ で初期化する必要があります。

ccallによって生成されないほとんどのJuliaオブジェクトに関する定数は、__init__に配置する必要はありません:それらの定義は事前コンパイルされ、キャッシュされたモジュールイメージからロードできます。これには、配列のような複雑なヒープ割り当てオブジェクトが含まれます。ただし、生ポインタ値を返すルーチンは、事前コンパイルが機能するためにランタイムで呼び出す必要があります(Ptrオブジェクトは、isbitsオブジェクトの内部に隠されていない限り、ヌルポインタに変わります)。これには、Julia関数@cfunctionおよびpointerの戻り値が含まれます。

辞書とセットの型、または一般的に hash(key) メソッドの出力に依存するものは、より厄介なケースです。キーが数値、文字列、シンボル、範囲、Expr、またはこれらの型の組み合わせ(配列、タプル、セット、ペアなどを介して)である一般的なケースでは、事前コンパイルが安全です。しかし、FunctionDataType のような他のいくつかのキー型、または hash メソッドを定義していない汎用のユーザー定義型の場合、フォールバックの hash メソッドはオブジェクトのメモリアドレス(その objectid を介して)に依存し、したがって実行ごとに変わる可能性があります。これらのキー型のいずれかを持っている場合、または確信が持てない場合は、安全のために __init__ 関数内からこの辞書を初期化することができます。あるいは、コンパイル時に初期化するのが安全になるように特別に処理されている IdDict 辞書型を使用することもできます。

プリコンパイルを使用する際には、コンパイルフェーズと実行フェーズの違いを明確に理解しておくことが重要です。このモードでは、Juliaが任意のJuliaコードの実行を可能にするコンパイラであり、コンパイルされたコードも生成するスタンドアロンのインタプリタではないことが、より明確に示されることがよくあります。

他の既知の潜在的な失敗シナリオには次のものが含まれます:

  1. グローバルカウンター(例えば、オブジェクトを一意に識別しようとするためのもの)。次のコードスニペットを考えてみてください:

    julia mutable struct UniquedById myid::Int let counter = 0 UniquedById() = new(counter += 1) end end

    このコードの意図は、すべてのインスタンスにユニークなIDを与えることでしたが、カウンターの値はコンパイルの最後に記録されます。この増分コンパイルされたモジュールのその後の使用は、同じカウンターの値から始まります。

    objectid(メモリポインタをハッシュ化することで機能する)は、以下のDictの使用に関するノートで示されているように、同様の問題があります。

    一つの代替案は、マクロを使用して @__MODULE__ をキャプチャし、現在の counter 値と一緒にそれだけを保存することですが、このグローバル状態に依存しないようにコードを再設計する方が良いかもしれません。

  2. 関連コレクション(DictSetなど)は、__init__で再ハッシュする必要があります。(将来的には、初期化関数を登録するためのメカニズムが提供されるかもしれません。)

  3. コンパイル時の副作用がロード時に持続することに依存します。例としては、他のJuliaモジュール内の配列や他の変数を修正すること、オープンファイルやデバイスへのハンドルを維持すること、他のシステムリソース(メモリを含む)へのポインタを保存することが含まれます。

  4. グローバルスコープで、別のモジュールから直接参照することによって、グローバルステートの偶発的な「コピー」を作成すること。例えば:

    ```julia #mystdout = Base.stdout #= will not work correctly, since this will copy Base.stdout into this module =#

    instead use accessor functions:

    getstdout() = Base.stdout #= best option =#

    or move the assignment into the runtime:

    init() = global mystdout = Base.stdout #= also works =# ```

コードをプリコンパイルする際に、ユーザーが他の誤った動作の状況を避けるために、実行できる操作にいくつかの追加制限が課されています:

  1. evalを呼び出して、別のモジュールに副作用を引き起こします。これは、インクリメンタルプリコンパイルフラグが設定されているときに警告が発生する原因にもなります。
  2. __init__() が開始された後のローカルスコープからの global const ステートメント (この件に関するエラー追加の計画については、問題 #12010 を参照してください)
  3. モジュールの置き換えは、インクリメンタルプリコンパイル中のランタイムエラーです。

注意すべき他のいくつかのポイント:

  1. ソースファイル自体に変更が加えられた後(Pkg.updateによるものを含む)、コードのリロードやキャッシュの無効化は行われず、Pkg.rmの後にクリーンアップも行われません。
  2. リシェイプされた配列のメモリ共有の挙動は、事前コンパイルによって無視されます(各ビューは独自のコピーを取得します)。
  3. ファイルシステムがコンパイル時と実行時の間で変更されないことを期待すること、例えば @__FILE__ / source_path() を使用して実行時にリソースを見つけることや、BinDeps の @checked_lib マクロ。時にはこれが避けられないこともあります。しかし、可能な場合は、リソースをコンパイル時にモジュールにコピーすることが良いプラクティスとなることがあります。そうすれば、実行時に見つける必要がなくなります。
  4. WeakRef オブジェクトとファイナライザは、現在シリアライザによって適切に処理されていません(これは今後のリリースで修正される予定です)。
  5. 内部メタデータオブジェクトのインスタンス(MethodMethodInstanceMethodTableTypeMapLevelTypeMapEntryなど)への参照をキャプチャすることは通常避けるのが最善です。これはシリアライザーを混乱させる可能性があり、望む結果を得られないかもしれません。これを行うことが必ずしもエラーであるわけではありませんが、システムがこれらの一部をコピーし、他のもののユニークなインスタンスを作成しようとすることに備えておく必要があります。

モジュール開発中にインクリメンタルプリコンパイルをオフにすることが役立つ場合があります。コマンドラインフラグ --compiled-modules={yes|no|existing} を使用すると、モジュールのプリコンパイルをオンまたはオフに切り替えることができます。--compiled-modules=no でJuliaを起動すると、コンパイルキャッシュ内のシリアライズされたモジュールは、モジュールおよびモジュール依存関係を読み込む際に無視されます。場合によっては、既存のプリコンパイル済みモジュールを読み込みたいが、新しいものを作成したくないことがあります。これは、--compiled-modules=existing でJuliaを起動することで実現できます。プリコンパイル中のネイティブコードストレージにのみ影響を与える --pkgimages={yes|no|existing} を使用することで、より細かい制御が可能です。Base.compilecache は手動で呼び出すこともできます。このコマンドラインフラグの状態は、パッケージのインストール、更新、および明示的なビルド時に自動プリコンパイルトリガーを無効にするために Pkg.build に渡されます。

環境変数を使用して、いくつかのプリコンパイルの失敗をデバッグすることもできます。JULIA_VERBOSE_LINKING=trueを設定すると、コンパイルされたネイティブコードの共有ライブラリのリンクに関する失敗を解決するのに役立つかもしれません。Juliaマニュアルの開発者ドキュメントの部分を参照してください。そこでは、「パッケージイメージ」の下にあるJuliaの内部を文書化したセクションでさらに詳細が見つかります。