My New Workflow with Julia 1.0

So there has been a lot of changes with this first release of the Julia programming language and it is sometimes hard to distill the most essential information from the rather dense documentation.

But here is how I am presently working.

Setting Up Your System

I like to be able to write julia at the terminal whenever I want, so make sure it is in my PATH by creating a symbolic linke:

$ ln -s /Applications/Development/Julia-0.7.app/Contents/Resources/julia/bin/julia /usr/local/bin/julia

If you are not on a Mac, you’ll setup this different. E.g. on my linux box at work, I got Julia installed under /opt/julia-0.7, so I create a link to the opt/bin directory.

Startup File

Configuration of your startup file has changed position since Julia 0.6. This file contains Julia code which is automatically run each time you open Julia. You put initialization code there you get tired of writing every time you start a Julia session.

$ mkdir  -p .julia/config
$ touch .julia/config/startup.jl

In my startup.jl file I've put the line:

push!(LOAD_PATH, "$(homedir())/Development/Julia")

Because I typically write my Julia packages as subdirectories of ~/Development/Julia. E.g. my package for reading the Apple plist format is under ~/Development/Julia/Plists.

Oh My REPL

To get access to syntax highlighting and rainbow colored parenthesis (makes finding matching parenthesis much easier) I install the OhMyREPL package.

$ julia

Then I hit the ] key to jump into package mode in the Julia REPL.

(v0.7) pkg>

Then just tell Julia to add the OhMyREPL package:

(v0.7) pkg> add OhMyREPL

Pay attention to the first part that says (v0.7), because that tells you which environment you adding the package to, more on that later.

Since I love OhMyREPL so much I add it to my startup.jl file. So my whole file looks like this:

push!(LOAD_PATH, "$(homedir())/Development/Julia")
using OhMyREPL

Revise Package

There is one more package I strongly advice you to install Revise:

(v0.7) pkg> add Revise

This package allows Julia to monitor changes in your code, and update the REPL environment to reflect that. Since we no longer have the workspace() command available this is needed. It isn't perfect but most of the time it means a much smoother workflow than I was used to before.

Writing Code and Package Management

You need to pay a bit more attention to the package management system now than before when writing code.

I’ve started writing most of my code inside a package. This has several advantages that I will touch upon.

When writing code for reading plist formatted files I would create it by going into package mode and write:

(v0.7) pkg> generate PLists

But lets just create a throw away package called Foobar to demonstrate the system.

(v0.7) pkg> generate Foobar

Next we jump into shell mode to go into the newly create directory containing Foobar package.

shell> cd Foobar

We do this because we want to add packages we depend on to it. Initially the the package will contain a project configuration file:

shell> cat Project.toml
authors = ["Erik Engheim <erik.engheim@earth.com>"]
name = "Foobar"
uuid = "641dae7e-b2eb-11e8-36b1-0d425f221c6d"
version = "0.1.0"

When we issue package commands, they always affect a Project.toml and Manifest.toml file in the currently active directory. That represents your environment.

So if we want to add packages to the environment our Foobar package is in, we need to make the Foobar directory the active one:

(v0.7) pkg> activate .

This works because the shell happens to be at ~/Development/Julia/Foobar, if we had still been in Julia/, we would have written: activate Foobar.

(Foobar) pkg> add TerminalMenus

This adds the TerminalMenus package as a dependency for our Foobar package. You can see this reflected in the project file under [deps]:

shell> cat Project.toml
name = "Foobar"
uuid = "641dae7e-b2eb-11e8-36b1-0d425f221c6d"
authors = ["Erik Engheim <erik.engheim@mac.com>"]
version = "0.1.0"

[deps]
TerminalMenus = "dc548174-15c3-5faf-af27-7997cfbde655"

And because we have added packages to the environment, we get a new file Manifest.toml, which contains more details. It gives the full list of all packages used directly by TerminalMenus:

shell> cat Manifest.toml
[[TerminalMenus]]
deps = ["Compat", "REPL", "Test"]
git-tree-sha1 = "9ae6ed0c94eee4d898e049820942af21daf15efc"
uuid = "dc548174-15c3-5faf-af27-7997cfbde655"
version = "0.1.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
...

Keep in mind that I could have made any directory active with the package system. There is nothing magical about Julia packages. I could pick any random directory say Spam and write activate Spam. You would not see any change initially. But if you start adding packages:

(Spam) pkg> add TerminalMenus

Then Spam directory would get Project.toml and Manifest.toml files.

What is the Point of the Environments?

So we can go around and create project and manifest files all over the place but what purpose does it serve?

Like the RVM and pyenv systems in Ruby and Python, this files affect what Julia will do when you try to load a package:

julia> using TerminalMenus

This will fail, if you have not added the TerminalMenus package to the environment which is currently in, or more concretely if there are no Project.toml and Manifest.toml files containing info about the TerminalMenus package.

The beauty of this is that you can sort of create your own little isolated sandboxes. Every directory with these files forming an environment sees a different reality. They know a different set of packages and package version numbers.

Practical Usage

When doing my regular development I start Julia from the directory where I have my code in (my package).

I would start by loading Revise, because then I can change the code of my functions and that will be reflected in the REPL as soon as the file is saved. No need for reload of Julia.

julia> using Revise

I make sure I do this while still in the Julia (v0.7) environment, the default. If you are not there, you can always jump back by writing activate with no argument:

julia> activate

The reason I have to do it there is because in say my Foobar environment, Julia would not know about Revise unless I specifically installed it. But we don't want our packages to contain dependencies to package we just use to aid our development.

Afterwards I make the Foobar environment active, so that further using statements will be based on that environment.

(v0.7) pkg> activate .
julia> using Foobar

However since Foobar parent directory ~/Development/Julia, is in LOAD_PATH you don't have to do that. Julia will look in all LOAD_PATH for packages when you issue a using SomePackage.

Working in Multiple Locations on a Package

Just check in all the file in a package in a git repository and push it to e.g. github. Convention is to give is a .jl suffix to clearly identify it as a Julia package.

If you have not registered your package, you can still easily add it to another computer. E.g. when I want to work on my PLists package on another computer. I clone it from github:

$ git clone git@github.com:ordovician/PLists.jl.git PLists

Then I go into the directory, activate it’s environment so that when I run instantiate Julia will pull the dependent packages as described in the package environment:

$ cd PLists
$ julia
pkg> activate .
pkg> instantiate

The dependent packages are described in the Project.toml and Manifest.toml files inside the package. So let me clarify again what confused me a bit at first. Any directory you put these files you are essentially creating a separate Julia environment. A Julia package thus happens to have its own environment. So a package always has an environment in Julia 1.0, but not all environments are in packages.

Working on Multiple Dependent Packages

One of my packages, QtUIParser depends on another package PLists, and neither are registered. I also work on them in parallel at times, because I may need to extend PLists with new features I need inQtUIParser.

This process I must admit is a bit confusing to me. I’ve gotten it working myself, but I am not sure what the intended ideal way of doing this is.

To make it so that PLists would get download when I run instantiate on another computer on the QtUIParser package, I need to add it in a way that lets Julia find it anywhere.

$ cd QtUIParser
$ julia
pkg> activate .
pkg> add https://github.com/ordovician/PLists.jl

By using the full URL of the dependent package, Julia can find it. Had it been a registered package, Julia would not need me to specify the full path, because it could lookup the path in the registry.

An alternative approach is to download the package manually using git. Then you would add it to your project by specifying a local path:

$ git clone git@github.com:ordovician/PLists.jl.git SomeLocalPath/PLists
$ cd QtUIParser
$ julia
pkg> activate .
pkg> add SomeLocalPath/PLists

The downside of this approach is that, then you have created an environment which depends dependent packages being located in the same relative path on all computers installing QtUIParser.

A problem with working on packages dependent on each other is that while you may modify the code of the packages locally, other packages using that package won’t know about the changes.

The package dev command works similar to add but instead of locking the package to the state it was when you added it, the environment using the package will continuously use the latests changes to the package.

pkg> dev PLists

or

pkg> dev https://github.com/ordovician/PLists.jl