julia types & package development
goal: create a Julia package that implements new types
julia package: git repository
for a good package:
- code
- tests
- license (MIT is a good one)
- documentation
Julia provides a great package manager for developers to declare dependencies and for users to get good versions of dependencies that work with each other, reproducibly.
how to create a new Julia package
Go to a place outside of any git repository: your package will be a new folder, that will be tracked with git.
In pkg mode with ]
, generate the standard files for a new package
and inspect what they are:
(v1.5) pkg> generate Trees # plural: see convention for package names
Generating project Trees:
Trees/Project.toml
Trees/src/Trees.jl
shell> cd Trees;
shell> cat Project.toml
name = "Trees"
uuid = "d3549d90-f74a-11e8-3813-3bb8358dfa17"
authors = ["Cecile Ane <cecileane@users.noreply.github.com>"]
version = "0.1.0"
shell> cat src/Trees.jl
module Trees
greet() = print("Hello World!")
end # module
Each package has its own global scope, and the package needs to have its own environment: so we create an environment for our package. That’s how we add dependencies if our new package relies on other packages.
(@v1.5) pkg> activate .
Activating environment at `~/Documents/private/st679/julia/Trees/Project.toml`
(@v1.5) pkg> add StatsFuns
(@v1.5) pkg> status
Status `~/.julia/dev/Trees/Project.toml`
[4c63d2b9] StatsFuns v0.9.6
To avoid broken dependencies later on, add information about the version of
your dependencies. Here: edit the file Trees/Project.toml
and manually
add a “compat” section like this:
[compat]
StatsFuns = "0.7, 0.8, 0.9" # list versions that are compatible with your package
license, readme, docs
To add a basic readme file, a licence,
and basic files for automatic tests and documentation,
it is best to generate the skeleton of your package using
PkgTemplates
instead of the basic generate
that we used above.
But it’s also best to learn step by step.
For now, the Example package provides a great template:
- MIT license: great choice
- tests run on Travis-CI (you will need an account)
- documentation built via Documenter
source code
Let’s add some code to our package: all in the src/
directory.
The main file is Trees.jl
, but the code in this file can include
code from other files, to keep code organized.
For example, create types.jl
and treemanipulate.jl
,
which we include
in Trees.jl
: see on
github
If you would rather use my package as a starting point
and modify it, quit your julia session, navigate some place
outside of your own Trees folder, then install my version
with the add
or dev
command in pkg mode:
(@v1.5) pkg> dev https://github.com/cecileane/Trees.jl
dev
will make it easier for you to see the code:
it will be placed in ~/.julia/dev/Trees/
by default.
If you modify the code there, the modifications will be used
after you do using Trees
(below).
- check the code
to see how types (classes) are defined:
struct
for immutable,mutable struct
for mutable - to use the code:
using Trees # reads & executes Trees.jl
tre1 = Tree() # we could do Trees.Tree() but Tree() works because exported
addedge!(tre1, 0,1)
addedge!(tre1, 0,2, 0.22)
tre1.edge[2]
The type Tree
and the function addedge!
are
exported.
Use prefix Trees.
for non-exported objects:
example(-1400, -1401) # error: example() not exported
Trees.example(-1400, -1401) # okay
new types Edge
and Tree
: mutable versus non-mutable
default constructors:
methods(Edge) # default method and other methods as defined
Edge(18,19, missing) # uses default method
Edge(18,19, 6.9)
e = Edge(18,19) # uses extra method
e.parent
e.child
e.length
e.parent = 20 # works: because mutable
tre1
fieldnames(typeof(tre1)) # (:edge, :label, :foo)
tre1.foo = 20 # error: because immutable
tre1.label = Dict(1=>"human", 2=>"chimp") # error: immutable
tre1.label # Dict{Int64,String}()
push!(tre1.label, 1=>"human", 2=>"chimp")
tre1.label
tre1
tests
The test suite must be in test/runtests.jl
,
so let’s create this file
mkdir test
touch test/runtests.jl
and then edit our runtests.jl
file
use testing macros @test
, @test_throws
, @test_nowarn
,
@test_logs
etc.
using Test
@test length(tre1.edge)==2
To run our tests, we need extra dependencies, just for testing
(not for package users): edit the project file Project.toml
[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Test"]
(an alternative is to add a file test/Project.toml
describing
which package the testing script depends on)
then we can run the full test suite in pkg mode:
(Trees) pkg> test Trees
Testing Trees
...
Test Summary: | Pass Total
building trees & path to the root | 11 11
Testing Trees tests passed
take the habit:
- develop a function, try it on small examples
- add these small examples in the test suite
publish
finally:
- turn your
Trees
folder into a git repository, and push it to github in a repo namedTrees.jl
despite the fact that our folder was namedTrees
– see guidelines for naming a package. - if you want to make it easier for others to use your package: use Registrator to register, tag and publish versions of your package
other
-
Profiling: to get an analysis of how much time is used by each function & identify the pieces of code that are the slow bottlenecks.
How:@profile
in the Profile module to generate the information, then visualize the information in a flame graph using one of various options, like the visualization integrated in Juno or the ProfileView package -
Benchmarks: to track if any new changes in your code make previous tasks run slower or faster.
How: create a suite of tasks in abenchmark/
folder and use PkgBenchmark to run the suite of tasks at each commit, and compare performance between commits.