08: Moduly, balíčky, dokumentace
Tento notebook je výukovým materiálem v předmětu BI-JUL.21 vyučovaném v zimním semestru akademického roku 2024/2025 Tomášem Kalvodou. Tvorba těchto materiálů byla podpořena NVS FIT.
Hlavní stránkou předmětu, kde jsou i další notebooky a zajímavé informace, je jeho Course Pages stránka.
versioninfo()
1. Moduly
K sdružení souvisejících funkcí, metod a typů Julia nabízí moduly. Moduly poskytují oddělený jmenný prostor (namespace) a lze se tak vyhnout problémům s pojmenováním metod, proměnných, atd.
Struktura modulu je následující
module ModuleName
# importy, exporty...
# kód
end
Tělo modulu bývá zvykem neodrážet od kraje. Importy použitých funkcí a dalších modulů bývá zvykem uvádět hned na začátku.
Tělo modulu také lze rozdělit do více souborů a použít pak metodu include
.
Tato metoda načte kód ze zadaného souboru.
Z pohledu Julia se pak tento kód chápe jako kdyby byl na místě, kde bylo volání include
.
Jako příklad uvažme následující modul.
"""
Demonstrační modul.
"""
module MyModule
import Base.show
export Dimensional, my_circle_area
"""
Zvrhlá konstanta.
"""
const MyPi = 3.14159#ish
"""
Náš užasný typ "rozměrného" čísla s jednotkou.
"""
struct Dimensional{T <: Number}
value::T
unit::String
end
show(io::IO, x::Dimensional{T}) where { T <: Number } =
print(io, "$(x.value) $(x.unit)")
"""
Obsah kruhu o daném poloměru.
"""
function my_circle_area(radius::Dimensional{T}) where { T <: Number }
return Dimensional(MyPi * radius.value^2, radius.unit * "²")
end
"""
Obsah kvádru o zadaných délkách stran.
"""
function my_cuboid_volume(a::Dimensional{T}, b::Dimensional{T}, c::Dimensional{T}) where { T <: Number }
return Dimensional(a.value * b.value * c.value, a.unit * "×" * b.unit * "×" * c.unit)
end
end # module
Všimněte si, že předchozí buňka po vyhodnocení vrátila Main.MyModule
.
V notebooku, nebo v REPL, pracujeme v Main
modulu.
Předchozí buňkou jsme tedy fakticky definovali podmodul modulu Main
.
MyModule == Main.MyModule
Následující ukázky očekávají, že jsme tento modul ještě neimportovali, případně pro jistotu restartujte kernel.
V jmenném prostoru notebooku modul máme k dispozici
MyModule
# MyModule.
K jednotlivým metodám, typům a konstantám definovaných v modulu přístup nemáme (myšleno v našem namespace).
MyPi
Dimensional
my_circle_area
Cestu k metodám, typům nebo konstantam musíme (zatím, pokud jsme žádné neexportovali) specifikovat pomocí .
:
MyModule.MyPi
Main.MyModule.MyPi
r = MyModule.Dimensional(2, "m")
MyModule.my_circle_area(r)
V některých případech je potřeba za tečkou použít dvojtečku (a vytvoření symbolu), nebo závorky, abychom se vyhnuli syntaktickým problémů:
typeof(:+)
typeof(+)
Base.+
Base.:+
Base.:(==)
Každopádně, tato nutnost opisovat název modulu jistě není úplně požadované chování. Typicky bychom s některými metodami (nebo typy) chtěli pracovat přímo v našem prostředí a ne se na ně takto podrobně odvolávat pomocí modulu. Jak to udělat si ukážeme za pár okamžiků.
V modulu vytvořeném způsobem popsaným výše už jsou automaticky inportovány exportované metody z modulů Core
a Base
a metody include
a eval
.
Pokud bychom chtěli vytvořit "prázdný" modul, musíme použít baremodule
na místo module
.
Definice modulu je v podstatě následující:
baremodule Module
using Core, Base
eval(x) = Core.eval(Module, x)
include(p) = Base.include(Module, p)
#...
end
Dostupné metody lze prozkoumat
# MyModule.
1.1 Standardní moduly
V předchozí sekci jsme zmínili moduly Core
, Base
a Main
. V Julia jsou to tři "speciální" moduly, jsou vždy k dispozici:
Core
: funkcionalita obsažená přímo "v jazyce" (prozkoumejte nabídku níže),Base
: funkcionalita užitečná ve "většině situací",Main
: modul, v kterém se pracuje při spuštění Julia REPL (nebo i zde v notebooku).
# Core.
# Base.
# Main.
Aktuální modul lze získat pomocí esotericky pojmenovaného makra @__MODULE__
.
@__MODULE__
import
1.1 K importu požadované metody implementované v jistém modulu lze nepřekvapivě použít klíčové slovo import
, a to hned několika způsoby:
import ModuleName # prostý import modulu
import ModuleName as OtherName # import s přejmenováním
import ModuleName: stuff # import dané metody/typu/...
import ModuleName: stuff as other_name # import a přejmenování dané metody/typu/...
Import zcela ignoruje seznam exportovaných objektů.
Pojďme si tyto možnosti postupně rozebrat. Protože jsme modul MyModule
definovali přímo zde v notebooku, musíme k němu udat plnou cestu, což je Main.Module
.
Obyčejný import do jmenného prostoru zavede modul samotný (ten u nás zde v notebooku už máme) a jinak nic dalšího.
import Main.MyModule
MyPi
Dimensional
my_circle_area
V některých situacích může být vhodné importovaný modul přejmenovat, abychom se nedostali do konfliktu s pojmenováním. K tomu slouží as
:
import .MyModule as MM
MM
Stále ale nemáme přímý přistup k metodám a typů v modulu. Dále můžeme jednotlivě vybrané objekty zavést i do aktuálního jmenného prostoru.
import Main.MyModule: MyPi, Dimensional
import Main.MyModule: my_circle_area as circle_area
MyPi
Dimensional
circle_area
my_cuboid_volume
Pozor, *
nefunguje tak jak byste čekali (co se přesně stalo? Porovnejte s Pythonem).
import Main.MyModule: *
my_cuboid_volume
*
using
1.2 V tento moment prosím restartujte jádro a znovu vyhodnoťte buňku s definicí našeho ukázkového modulu.
using
se chová podobně jako import
. Následující kód do jmenného prostoru zavede modul (u nás už ho máme) a dále všechny modulem exportované objekty.
using Main.MyModule
MyModule
MyPi
Dimensional
my_circle_area
Konečně, trošku překvapivě
using Module: stuff
je ekvivalentní
import Module: stuff
Také se načtou pouze zmíněné metody, ne všechny exportované.
Seznam všech exportovaných metod a typů lze získat pomocí metody names
.
names(MyModule)
Například (restart jádra!):
using .MyModule: MyPi
MyPi
Dimensional
Podmoduly
Moduly mohou přirozeně obsahovat podmoduly. Podmoduly jsou zcela odděleny od svých předků.
Předka modulu lze zjistit pomocí metody parentmodule
.
parentmodule(MyModule)
parentmodule(Main)
parentmodule(Base)
parentmodule(Core)
typeof(Main)
typeof(Module)
Projekty
2.Julia projekty umožňují definovat jaké balíčky a v jakých verzích se při výpočtu (nebo jakékoliv další jiné aktivitě) použily. Zajišťují správu závislostí a tím podporují reprodukovatelnost výpočtu. Nemělo by se vám později stát, že najednou kód nefunguje, protože se používá balíček v jiné verzi.
Projekt v podstatě není nic jiného než adresář (lépe git repozitář) obsahující dva soubory
Project.toml
: přímé závislosti, (případně název projektu a unikátní identifikátor),Manifest.toml
: všechny (i nepřímé) závislosti.
2.1 Vytvoření projektu
Projekt lze vytvořit velmi snadno.
Spustíme Julia v adresáři, kde chceme vytvořit adresář s projektem, přepneme se do Pkg
módu a zadáme následující příkaz
(@v1.11) pkg> generate MyProject
Generating project MyProject:
MyProject/Project.toml
MyProject/src/MyProject.jl
Prostředí již existujícího projektu pak aktivujeme příkazem (případně ] activate .
, jsme-li v adresáři projektu; nebo parametrem --project
při startu Julia, viz níže):
(@v1.11) pkg> activate MyProject
Activating new environment at `~/documents/fit/B231-BI-JUL/MyProject/Project.toml`
(MyProject) pkg>
Všimněte si změny v příkazové řádce.
Julia nám naznačuje, že nepracujeme v globálním prostředí, ale v projektu MyProject
.
Pokud byste zkusili importovat balíčky, které jste dříve instalovali, tak tato operace selže.
Toto prostředí je zatím "prázdné".
2.2 Přidávání balíčků do projektu
Přidejme si do něho třeba balíček pro práci s prvočísly Primes
,
(MyProject) pkg> add Primes
Updating registry at `~/.julia/registries/General`
Updating git-repo `https://github.com/JuliaRegistries/General.git`
Resolving package versions...
Updating `~/documents/fit/B231-BI-JUL/MyProject/Project.toml`
[18e54dd8] + IntegerMathUtils v0.1.2
[27ebfcd6] + Primes v0.5.4
Updating `~/documents/fit/B231-BI-JUL/MyProject/Manifest.toml`
[27ebfcd6] + Primes v0.5.4
Všimněte si, že byl vytvořen další soubor, Manifest.toml
.
Pokud nyní opustíte Julia a přesunete se do adresáře MyProject
, pak zde najdete dva TOML soubory.
Project.toml
:
2.3 Obsah [deps]
Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
Tento soubor ale může obsahovat i další užitečné informace, viz dokumentaci Pkg.jl
.
Například lze definovat minimální podporovanou Julia verzi.
[compat]
julia = "1.6"
Manifest.toml
:
2.4 Obsah # This file is machine-generated - editing it directly is not advised
[[Primes]]
git-tree-sha1 = "afccf037da52fa596223e5a0e331ff752e0e845c"
uuid = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
version = "0.5.0"
2.5 Aktivace prostředí projektu
Prostředí projektu jde aktivovat několika způsoby, můžeme použít příkazovou řádku
$ julia --project=PROJECT_DIRECTORY
nebo, pokud jsme přímo v adresáři projektu
$ julia --project=@.
Alternativně lze použít Pkg
mód a příkaz activate
(s udáním adresáře, nebo .
pro aktuální adresář).
V Pkg
módu může být užitečný příkaz status
, který nám vypíše aktuální stav projektu.
(MyProject) pkg> status
Status `~/documents/fit/B231-BI-JUL/MyProject/Project.toml`
[27ebfcd6] Primes v0.5.0
Nyní můžeme v rámci projektu implementovat co je potřeba. Často budeme chtít ale projekt sdílet s ostatními (například se studenty :-)).
Nejprve je nutno získat adresář s projektem (git repozitář, zip,...).
Poté stačí spustit Julia ve správném prostředí a nainstalovat závislosti, v Pkg
módu je pro to příkaz instantiate
(tj. alternativně import Pkg; Pkg.instantiate()
).
2.6 Cvičení: Projekt Möbius
Zkuste si stáhnout a zprovoznit ukázkový projekt MyProject
s výpočtem Möbiovy funkce.
Prozkoumejte adresářovou strukturu, ověřte funkčnost.
3. Balíčky
Julia balíček ("knihovna" atp.) není nic jiného než trochu bohatší projekt s předepsanou strukturou.
Doporučeným postupem při vytváření balíčku je použít balíček PkgTemplates
, který vás procesem tvorby proveden.
Z Python světa možná znáte analogický projekt Cookiecutter.
Nejprve nainstalujeme PkgTemplates
, tedy v Pkg
módu provedeme
(@v1.11) pkg> add PkgTemplates
Poté vytvoříme náš nový skvělý balíček (k dispozici je interaktivní průvodce, nebo lze šablonu ručně konfigurovat -- viz dokumentaci zmíněného balíčku):
julia> using PkgTemplates
julia> generate("DobbleExample")
Template keywords to customize:
[press: d=done, a=all, n=none]
[X] user
[X] authors
[X] dir
[X] host
[X] julia
> [X] plugins
Enter value for 'user' (String, default="kalvotom"):
Enter value for 'authors' (Vector{String}, comma-delimited, default="Tomáš Kalvoda <tomas.kalvoda@fit.cvut.cz> and contributors"):
Enter value for 'dir' (String, default="~/.julia/dev"): ./
Select Git repository hosting service:
github.com
gitlab.com
bitbucket.org
> Other
Enter value for 'host' (String, default="github.com"): gitlab.fit.cvut.cz
Select minimum Julia version:
1.0
1.1
1.2
1.3
1.4
1.5
> 1.6
Other
Select plugins:
[press: d=done, a=all, n=none]
[ ] CompatHelper
[X] ProjectFile
[X] SrcDir
[X] Git
[X] License
[X] Readme
[X] Tests
[ ] TagBot
[ ] AppVeyor
[ ] BlueStyleBadge
[ ] CirrusCI
[ ] Citation
> [X] Codecov
[ ] ColPracBadge
[ ] Coveralls
[ ] Develop
[X] Documenter
[ ] DroneCI
[ ] GitHubActions
[X] GitLabCI
[ ] RegisterAction
[ ] TravisCI
ProjectFile keywords to customize:
[press: d=done, a=all, n=none]
> [ ] version
[ ] None
SrcDir keywords to customize:
[press: d=done, a=all, n=none]
> [ ] destination
[ ] file
Git keywords to customize:
[press: d=done, a=all, n=none]
> [ ] branch
[ ] email
[ ] gpgsign
[ ] ignore
[ ] jl
[ ] manifest
[ ] name
[ ] ssh
License keywords to customize:
[press: d=done, a=all, n=none]
> [ ] destination
[ ] name
[ ] path
Readme keywords to customize:
[press: d=done, a=all, n=none]
> [ ] badge_off
[ ] badge_order
[ ] destination
[ ] file
[ ] inline_badges
Tests keywords to customize:
[press: d=done, a=all, n=none]
> [ ] file
[ ] project
Codecov keywords to customize:
[press: d=done, a=all, n=none]
> [ ] file
[ ] None
Documenter deploy style:
NoDeploy
TravisCI
> GitLabCI
GitHubActions
Documenter keywords to customize:
[press: d=done, a=all, n=none]
> [ ] assets
[ ] devbranch
[ ] index_md
[ ] logo
[ ] make_jl
GitLabCI keywords to customize:
[press: d=done, a=all, n=none]
> [ ] coverage
[ ] extra_versions
[ ] file
[ Info: Running prehooks
[ Info: Running hooks
Activating environment at `~/documents/fit/B211-BI-JUL/DobbleExample/Project.toml`
Updating registry at `~/.julia/registries/General`
No Changes to `~/documents/fit/B211-BI-JUL/DobbleExample/Project.toml`
No Changes to `~/documents/fit/B211-BI-JUL/DobbleExample/Manifest.toml`
Precompiling project...
1 dependency successfully precompiled in 3 seconds
Activating environment at `~/.julia/environments/v1.6/Project.toml`
Activating new environment at `~/documents/fit/B211-BI-JUL/DobbleExample/docs/Project.toml`
Resolving package versions...
Updating `~/documents/fit/B211-BI-JUL/DobbleExample/docs/Project.toml`
[e30172f5] + Documenter v0.27.10
Updating `~/documents/fit/B211-BI-JUL/DobbleExample/docs/Manifest.toml`
[a4c015fc] + ANSIColoredPrinters v0.0.1
[ffbed154] + DocStringExtensions v0.8.6
[e30172f5] + Documenter v0.27.10
[b5f81e59] + IOCapture v0.2.2
[682c06a0] + JSON v0.21.2
[69de0a69] + Parsers v2.1.1
[2a0f44e3] + Base64
[ade2ca70] + Dates
[b77e0a4c] + InteractiveUtils
[76f85450] + LibGit2
[56ddb016] + Logging
[d6f4376e] + Markdown
[a63ad114] + Mmap
[ca575930] + NetworkOptions
[de0858da] + Printf
[3fa0cd96] + REPL
[9a3f8284] + Random
[ea8e919c] + SHA
[9e88b42a] + Serialization
[6462fe0b] + Sockets
[8dfed614] + Test
[4ec0a83e] + Unicode
Resolving package versions...
Updating `~/documents/fit/B211-BI-JUL/DobbleExample/docs/Project.toml`
[8640a1c4] + DobbleExample v0.1.0 `..`
Updating `~/documents/fit/B211-BI-JUL/DobbleExample/docs/Manifest.toml`
[8640a1c4] + DobbleExample v0.1.0 `..`
Activating environment at `~/.julia/environments/v1.6/Project.toml`
[ Info: Running posthooks
[ Info: New package is at /home/kalvin/documents/fit/B211-BI-JUL/DobbleExample
Template:
authors: ["Tomáš Kalvoda <tomas.kalvoda@fit.cvut.cz> and contributors"]
dir: "~/documents/fit/B211-BI-JUL"
host: "gitlab.fit.cvut.cz"
julia: v"1.6.0"
user: "kalvotom"
plugins:
Codecov:
file: nothing
Documenter:
assets: String[]
logo: Logo(nothing, nothing)
makedocs_kwargs: Dict{Symbol, Any}()
canonical_url: PkgTemplates.gitlab_pages_url
make_jl: "~/.julia/packages/PkgTemplates/VOMig/templates/docs/make.jl"
index_md: "~/.julia/packages/PkgTemplates/VOMig/templates/docs/src/index.md"
devbranch: nothing
Git:
ignore: String[]
name: nothing
email: nothing
branch: "main"
ssh: false
jl: true
manifest: false
gpgsign: false
GitLabCI:
file: "~/.julia/packages/PkgTemplates/VOMig/templates/gitlab-ci.yml"
coverage: true
extra_versions: ["1.0", "1.6"]
License:
path: "~/.julia/packages/PkgTemplates/VOMig/templates/licenses/MIT"
destination: "LICENSE"
ProjectFile:
version: v"0.1.0"
Readme:
file: "~/.julia/packages/PkgTemplates/VOMig/templates/README.md"
destination: "README.md"
inline_badges: false
badge_order: DataType[Documenter{GitHubActions}, Documenter{GitLabCI}, Documenter{TravisCI}, GitHubActions, GitLabCI, TravisCI, AppVeyor, DroneCI, CirrusCI, Codecov, Coveralls, BlueStyleBadge, ColPracBadge]
badge_off: DataType[]
SrcDir:
file: "~/.julia/packages/PkgTemplates/VOMig/templates/src/module.jl"
Tests:
file: "~/.julia/packages/PkgTemplates/VOMig/templates/test/runtests.jl"
project: false
Po této akci vznikne následující adresářová struktura:
$ tree -a
.
├── docs
│ ├── make.jl
│ ├── Manifest.toml
│ ├── Project.toml
│ └── src
│ └── index.md
├── .git
│ └── # CENSORED
├── .gitignore
├── .gitlab-ci.yml
├── LICENSE
├── Manifest.toml
├── Project.toml
├── README.md
├── src
│ └── DobbleExample.jl
└── test
└── runtests.jl
Tuto šablonu jsem ještě lehce upravil a doplnil a naleznete ho v repozitáři v předmětové skupině.
3.1 Cvičení: Generátor Dobble karet
V uvedeném balíčku je velmi hrubý nástřel balíčku umožňujícího generovat Dobble kartičky. Během cvičení dokončíme implementaci a vybavíme balíček
- generátorem dokumentace,
- testy,
- měřením pokrytí kódu testy.
Detaily k zadání naleznete v uvedeném repozitáři.
V implementaci si také vyzkušíme implementovat iterátor (přes všechny kartičky/přímky).
K tomu je potřeba vytvořit metodu iterate
.
Reference
V tomto notebooku vycházíme z oficiální dokumentace modulů, z dokumentace Pkg.jl
a dokumentace PkgTemplates.jl
.
Dokumentaci generátoru dokumentace naleznete zde.