1.10. Модули и пакеты#
1.10.1. Модули#
Совет
Подробнее в секции мануала Modules.
В Julia можно разбивать код программы на модули (modules). Модуль создаёт собственное пространство имён и может быть прекомпилирован.
Основной синтаксис выглядит так.
module Points
using LinearAlgebra
export dist, Point
include("types.jl")
include("functions.jl")
private_foo() = println("Hello!")
end # module
Всё, что между командами module ... end
представляет собой модуль.
В данном примере создаётся модуль Points
.
Инструкция using LinearAlgebra
импортирует публичные имена из модуля LinearAlgebra
.
При таком вызове, например, функция LinearAlgebra.norm
из модуля доступна просто по имени norm
.
Фактически, программист так указывает зависимости модуля Points
от других модулей.
Модуль Points
также делает имена dist
и Point
публичными. Т.е., когда кто-нибудь импортирует Points
командой using
, то ему будут доступны имена dist
и Point
. Точно также где-то в исходном коде модуля LinearAlgebra
происходит экспорт имени norm
.
Функция include("<path_to_file>")
делает подстановку содержимого файла в модуль.
В Julia позднее связывание имён, поэтому вы можете спокойно экспортировать что-то, а объявить где-то позднее.
Как импортировать свой модуль?
Если модуль Points
помещён в файл myscripts/Points.jl
, то можно импортировать модуль так
include("myscripts/Points.jl") # В общем случае полный путь до файла с исходным кодом модуля
using .Points
@show dist(Point(1, 2))
Здесь функцией include
совершается подстановка содержимого файла с модулем, как будто бы мы его скопировали сюда.
Затем используется using .Points
– заметьте точку.
По умолчанию Julia ищет модули (а точнее, пакет) в текущем окружении.
Глобальное окружение вы можете посмотреть в REPL командой ] status
– она покажет список установленных пакетов (но не покажет стандартные библиотеки).
В нашем случае пакет не создавался и регистрировался в глобальном окружении, поэтому команда using Points
привела бы к ошибке.
Но, с помощью .
поиск модуля осуществляется относительно скрипта, а не окружения.
Кроме того, есть ..
для обращения к родительскому модулю.
using и import
Для импортирования существуют инструкции using
и import
.
Их главное отличие в назначении.
Инструкция using
предназначена для использования кода модуля пользователем.
Например, using LinearAlgebra
позволяет нам, как пользователям модуля LinearAlgebra
, использовать функции norm
, cross
…
Инструкция import
отличается от using
тем, что позволяет переопределять и создавать новые методы для функций, определённых в импортированном модуле.
Другими словами, import
для разработчиков.
Например, чтобы добавить метод скалярного произведения для собственного типа данных, придётся воспользоваться import LinearAlgebra
.
Что импортируется, а что нет
Можно импортировать только некоторый функционал модуля, для этого используется двоеточие :
.
Можно переименовывать импортируемые имена с помощью as
.
В таблице ниже показано, какие имена доступны при использовании разных вариантов using
и import
.
Команда импорта |
Какие имена доступны |
---|---|
|
|
|
Только |
|
Только |
|
|
|
|
1.10.1.1. Пример разработки модуля#
Ниже показана разработка модуля в несколько этапов. В нём привычная структура Point{T}
, а её интерфейс оборачивается в модуль. Затем, для примера, структура встраивается в существующую экосистему языка: можно скалярно умножать точки, складывать или умножать на скаляр.
Points.jl
module Points
export dist
export Point
struct Point{T}
x::T
y::T
end
dist(p::Point) = sqrt(p.x^2 + p.y^2)
random_point() = Point(rand(2)...)
end # module
script.jl
include("path/to/Points.jl")
using .Points
println(dist(Point(3, 4)))
println(Points.random_point())
Points.jl
module Points
import LinearAlgebra # для добавления метода к скалярному произведению
export dist
export Point
struct Point{T}
x::T
y::T
end
dist(p::Point) = sqrt(p.x^2 + p.y^2)
random_point() = Point(rand(2)...)
# Добавление метода к скалярному произведению LinearAlgebra.dot
LinearAlgebra.dot(p1::Point, p2::Point) = p1.x * p2.x + p1.y * p2.y
end # module
script.jl
include("path/to/Points.jl")
using .Points
using LinearAlbgebra # Для dot(x, y)
println(dot(Point(-1, 2), Point(-2, -3)))
Points.jl
module Points
import LinearAlgebra
export dist
export Point
struct Point{T}
x::T
y::T
end
dist(p::Point) = sqrt(p.x^2 + p.y^2)
random_point() = Point(rand(2)...)
LinearAlgebra.dot(p1::Point, p2::Point) = p1.x * p2.x + p1.y * p2.y
# Расширяение стандартной библиотеки языка, модуля Base
# `+` коммутативно
Base.:+(p1::Point, p2::Point) = Point(p1.x + p2.x, p1.y + p2.y)
# `*` не коммутативно
Base.:*(α::Number, p::Point) = Point(α * p.x, α * p.y)
Base.:*(p::Point, α::Number) = α * p
end # module
script.jl
include("path/to/Points.jl")
using .Points
println(Point(1, 2) + Point(3.0, 4.1))
println(2 * Point(1, 2))
println(Point(1, 2) * 2.0)
Разбиение исходного кода на файлы
Когда исходный код разрастается, его разбивают на отдельные файлы.
Для библиотеки кода в Julia типично наличие корневого файла, в котором объявлен модуль, зависимости от других библиотек и экспортируемые имена, а оставшийся код библиотеки подставлятся с помощью include
.
Весь код библиотеки помещают в директорию src
, а корневой файл называют также, как и главный модуль библиотеки.
Ниже показана типичная структура исходного когда библиотеки.
Points.jl
Структура директории.
src/
operators.jl
interface.jl
types.jl
Points.jl
Код модуля src/Points.jl
.
module Points
import LinearAlgebra
export dist
export Point
include("types.jl")
include("interface.jl")
include("operators.jl")
end # module
src/types.jl
struct Point{T}
x::T
y::T
end
src/interface.jl
dist(p::Point) = sqrt(p.x^2 + p.y^2)
random_point() = Point(rand(2)...)
src/operators.jl
LinearAlgebra.dot(p1::Point, p2::Point) = p1.x * p2.x + p1.y * p2.y
Base.:+(p1::Point, p2::Point) = Point(p1.x + p2.x, p1.y + p2.y)
Base.:*(α::Number, p::Point) = Point(α * p.x, α * p.y)
Base.:*(p::Point, α::Number) = α * p
1.10.2. Пакеты#
Пакет (package) это инструмент для распространения библиотеки кода среди пользователей. Пакет содержит саму библиотеку кода и метаинформацию о нём, необходимую для идентификации пакета в системе и корректной установки. Кроме того, пакет может содержать примеры применения и набор тестов.
Пакеты в Julia создаются с помощью Pkg-mode в Julia REPL. Сам пакет представляет из себя git-репозиторий со следующей структурой
AwesomeLibrary/
src/
AwesomeLibrary.jl
...
tests/
...
Project.toml
Непосредственный код библиотеки содержится в src/
и имеет ту же структуру, которую мы рассмотрели в примере разработки модуля.
В tests/
содержится система тестирования кода библиотеки.
В Project.toml
содержится информация об авторе пакета, его уникальный номер, а также перечисление программных библиотек с версиями, которые необходимы для работы пакета (зависимостей).
Пакеты можно разрабатывать и использовать локально.
Это лучше всего делать в директории ~/.julia/dev/
.
Также, если вы хотите внести изменения не в свой пакет, то для этого используется ] dev
, а исходный код появится в ~/.julia/dev/
.
Для распространения пакеты публикуются на git-платформе, обычно на GitHub, например, DataFrames.jl или Unitful.jl.
Кроме того, пакеты обычно регистрируются в реестре пакетов для удобства поиска пользователями.
Реестр также является git-репозиторием и хранит информацию о всех зарегистрированных версиях пакетов.
В REPL вы можете посмотреть добавленные реестры командой ] registry status
.
Скорее всего, там окажется General
реестр – официальный реестр пакетов для Julia.
Можно искать пакеты в REPL, на github или на интернет-платформе, вроде такой.
Зарегистрированные в реестре пакеты устанавливаются просто: ] add PackageName
, например, ] add DataFrames
.
После этой команды Julia запросит информацию в реестрах о пакете DataFrames
, найдёт его фактическое расположение, скачает и установит все зависимости.
Список установленных пакетов в окружении можно узнать командой ] status
.
В Julia пакеты имеют независимые окружения. Т.е. зависимости пакета изолированы от остальных окружений, в том числе от глобального. Это уменьшает количество конфликтов версий между используемыми библиотеками.
Полная информация об окружении содержится в файле Project.toml
.
Если вы хотите поделиться кодом, который использует библиотеки, то можно вместе с ним передать Project.toml
файл.
Так поступают для Jupyter ноутбуков, а в Pluto.jl информация об окружении содержится внутри скрипта.
В окружение также входит файл Manifest.toml
, создаваемый автоматически.
В нём содержится полная и точная информация о зависимостях пакета для вашего компьютера.
Т.е. какая библиотека откуда берётся.