Author Archives: Blog by Bogumił Kamiński

Tutorials for DataFrames.jl release 0.21. Part I

By: Blog by Bogumił Kamiński

Re-posted from: https://bkamins.github.io/julialang/2020/05/25/data-frames-part1.html

DataFrames.jl release 0.21

DataFrames.jl version 0.21 was a major release that introduced a number of
significant changes to DataFrames.jl API. The list is long, so
I briefly summarize here he most significant things in terms of functionality:

  • we now allow to select columns using strings (selection using Symbols is
    still allowed);
  • a completely new design of API for working with columns of a data frame or
    grouped data frame, covered by select/select!, transform/transform!,
    and combine functions; it is consistent (so you learn it once and reuse
    everywhere), more flexible, and has a better performance than the old one;
    in particular two wrappers ByRow and AsTable have been added to API;
  • major enhancements to push! and append!, which allow an easy way to
    digest heterogeneous data (varying element types, varying column sets)
    into a data frame;
  • GroupedDataFrame now supports a fast lookup by grouping columns (so making
    a GroupedDataFrame can be now seen as adding an index to a data frame)
  • filter/filter! are now fast using Pair-interface;
  • rules for pseudo-broadcasting (spreading single observations across multiple
    rows) have been established and are consistently applied in all methods that
    allow this operation.

All these changes combined mean that now all operations on data frames can be
expressed via function chaining (and you have a full control if you want to
make copies or perform operations in-place). There are many users who like this
style of expressing transformations made on data. If you want to go this way,
then probably you should consider learning one of the packages that makes
it easier to work with |> operator. There are many excellent alternatives
in the Julia ecosystem. Let me mention two Pipe.jl (easier) and
Underscores.jl (more powerful, but harder to master).

After the release I got several questions about showing how things work in
practice. Therefore in this post I list tutorials that are currently available
and have been updated to show how DataFrames.jl v0.21 works.

In the Part II post (that I plan to prepare next week) I will show some new
material that was prepared under DataFrames.jl v0.21.

Tutorials for release 0.21

There are four sources of information about the functionality of DataFrames.jl
0.21 that you can check out (and I maintain them so that they should be
up to date):

  1. An official DataFrames.jl Manual.
  2. A notebook-based DataFrames.jl Tutorial.
  3. Video materials at JuliaAcademy.
  4. Recently updated the materials about DataFrames.jl that I have presented
    during JuliaCon2019 workshop. You will be able to find
    there two notebooks that include worked examples how you can process
    real-life data sets.

I hope these materials will be useful for exploring the latest release
of DataFrames.jl!

My practices for managing project dependencies in Julia

By: Blog by Bogumił Kamiński

Re-posted from: https://bkamins.github.io/julialang/2020/05/18/project-workflow.html

Project dependencies in Julia

When you work on a project using the Julia language most likely you will use
some packages that are available in the Julia ecosystem. In particular,
JuliaHub is a great place to look for packages that might be useful
for you.

Most likely the first thing you start doing is adding the package to your
default environment. This is the easiest thing to do, but has one significant
downside — Julia package ecosystem is evolving very fast. This, in particular,
means that during your project life cycle the versions of the packages provided
by their maintainers can go up and introduce breaking changes. In consequence
your code might suddenly stop working for no apparent reason.

In this post I have collected some practices I find useful to avoid such
problems. Even if you do not end up using the functionalities of the Julia
package manager I discuss on daily basis I think it is worth to be aware of
their existence.

All examples were tested under Julia 1.4.1.

For every project keep a separate project environment

This is a basic rule. Unless I do quick-and-dirty interactive calculations
I always create a project environment for my work.

Fortunately this is really easy. Just use generate command in the Julia
package manager. The steps to achieve it are easy:

  1. start julia in the folder in which you want to create a project
  2. press ] character to enter package manager mode
  3. execute generate [target folder name] and press enter
  4. press backspace to leave this mode
  5. write exit()

Here is a screen shot of the session where I executed these steps:

~$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.4.1 (2020-04-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

(@v1.4) pkg> generate test_project
 Generating  project test_project:
    test_project/Project.toml
    test_project/src/test_project.jl

julia> exit()
~$

Alternatively you can achieve this by running the following command:

~$ julia -e 'using Pkg; Pkg.generate("test_project")'
 Generating  project test_project:
    test_project/Project.toml
    test_project/src/test_project.jl
~$

When I use -e command Julia executes the commands that I pass and quits.
In this case I loaded the Pkg module and executed Pkg.generate function that
is a part of Pkg module API.

Let us check what is the contents of the test_project folder:

~$ cd test_project/
~/test_project$ ls -R
.:
Project.toml  src

./src:
test_project.jl
~/test_project$

You can see that two files were created: a Project.toml file in the top-level
directory that specifies the dependencies of our project
and src/test_project.jl which is a placeholder for our code.
Let us quickly inspect their contents:

~/test_project$ cat Project.toml
name = "test_project"
uuid = "d78710ad-1861-4169-903b-684d2f77c7fa"
authors = ["Bogumił Kamiński <[email protected]>"]
version = "0.1.0"
~/test_project$ cat src/test_project.jl
module test_project

greet() = print("Hello World!")

end # module
~/test_project$

You can change the contents or rename the test_project.jl file in whatever
way you like, but do not touch Project.toml file as it contains the list
of dependencies of your project (currently there are none). Here
you can find the details of the specification of Project.toml file contents.

Now — a crucial step is that whenever you want to work with your project
always activate the project environment specified by Project.toml file
when starting Julia.

The easiest way to do it is to make sure that you are in the folder that
contains Project.toml file and write:

~/test_project$ julia --project=.
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.4.1 (2020-04-14)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia>

The --project=. part tells Julia to start the interpreter using Project.toml
file from the current working directory as a specification of your dependencies.

I this post I discuss how to make Julia automatically activate the
project environment in your current working directory on startup.

Now we can check that indeed we are in a correct project environment that is
empty:

julia> using Pkg

julia> Pkg.status()
Project test_project v0.1.0
Status `~/test_project/Project.toml`
  (empty environment)

julia>

In the next section I discuss how to add packages to your project.

When adding packages use preserve=PRESERVE_DIRECT keyword argument

You can add dependencies to your project using the Pkg.add function in the
project manager. You can find the list of the available options by running the
help for the Pkg.add function (I omit the output here as it is long).

It is crucial that the Pkg.add function has a preserve keyword argument that
tells Julia what it is allowed to do with the already installed packages. In
this post I have discussed potential problems when multiple
packages you install have conflicting dependencies. Therefore my practice is to
use preserve=PRESERVE_DIRECT keyword argument. I do not want the packages that
my code depends on to change versions (and if there is some conflict generated
due to this restriction I prefer to get an error rather than a package version
change). However, I typically allow changing versions of recursive dependencies
as normally it should not affect my code.

Let me give an example how one can run such a command (in this case it does not
really matter if we add the preserve=PRESERVE_DIRECT keyword argument as we do
not have any packages installed):

julia> Pkg.add("Pipe", preserve=PRESERVE_DIRECT)
   Updating registry at `~/.julia/registries/General`
   Updating git-repo `https://github.com/JuliaRegistries/General.git`
  Resolving package versions...
   Updating `~/test_project/Project.toml`
  [b98c9c47] + Pipe v1.2.0
   Updating `~/test_project/Manifest.toml`
  [b98c9c47] + Pipe v1.2.0

julia>

If you are curious what Pipe.jl package does you can check it out
here.

We are informed that Project.toml and Manifest.toml were updated.
Let us inspect their contents from within Julia as an exercise:

julia> print(read("Project.toml", String))
name = "test_project"
uuid = "d78710ad-1861-4169-903b-684d2f77c7fa"
authors = ["Bogumił Kamiński <[email protected]>"]
version = "0.1.0"

[deps]
Pipe = "b98c9c47-44ae-5843-9183-064241ee97a0"

julia> print(read("Manifest.toml", String))
# This file is machine-generated - editing it directly is not advised

[[Pipe]]
git-tree-sha1 = "f11840ebaf295b39319c2750f158621a96173fc5"
uuid = "b98c9c47-44ae-5843-9183-064241ee97a0"
version = "1.2.0"
julia>

In general Manifest.toml contains the exact specification of all dependencies
of our project (direct and recursive) and Project.toml lists only essential
information about direct dependencies.

Before we move on let me stress in what cases using preserve=PRESERVE_DIRECT
is most important. Assume you have worked on some project for some time already.
It had several packages as its dependencies. Now you decide that you need to add
some new package to its direct dependencies. The potential problem is that it is
possible (as you have worked on your project already for some time) that the
packages that you have installed previously have new versions available. Most
likely you do not want these packages to change their versions when you add
a new package as your code might stop working. This is exactly what
preserve=PRESERVE_DIRECT keyword argument safeguards you against.

When updating packages use level=UPDATELEVEL_PATCH option

However, if you work on a project for some time the packages that you depend on,
might have released patches (e.g. bug fixes or documentation enhancements) that
you want to allow in your project.

You can achieve such an update by running Pkg.update(level=UPDATELEVEL_PATCH).
Here is an example of this command run:

julia> Pkg.update(level=UPDATELEVEL_PATCH)
   Updating registry at `~/.julia/registries/General`
   Updating git-repo `https://github.com/JuliaRegistries/General.git`
   Updating `~/test_project/Project.toml`
 [no changes]
   Updating `~/test_project/Manifest.toml`
 [no changes]

julia>

In this case nothing happened as we have just installed that package. To check
out all the options of the Pkg.update function run its help. In particular, it is
OK to allow changing major or minor versions of your dependencies when running
the Pkg.update function, but remember that in this case your code might stop
working correctly, so if you do this please make sure to test that your code
produces the expected results after the update (and if it fails you can use the
Pkg.undo() function to undo the latest change to the active project).

Final notes on using package manager

In this post I used the Pkg module API by calling functions. All this
functionality is also available via a package manager, which you enter by
pressing ] in the Julia command line. The names of the commands are the same,
and you can get help on them by writing e.g. help add.

Understanding package version restrictions in Julia

By: Blog by Bogumił Kamiński

Re-posted from: https://bkamins.github.io/julialang/2020/05/11/package-version-restrictions.html

This post was written for Julia 1.4.1.

Dependencies of the packages in Julia

Each package in Julia has a list of other packages it depends on.
They are specified in Project.toml file. Here is an
example from DataFrames.jl pacakge release 0.21.0:

name = "DataFrames"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "0.21.0"

[deps]
CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597"
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
Future = "9fa8497b-333b-5362-9e8d-4d0656e87820"
InvertedIndices = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
IteratorInterfaceExtensions = "82899510-4779-5014-852e-03e436cf321d"
Missings = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
PooledArrays = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
SortingAlgorithms = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
TableTraits = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[extras]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
DataValues = "e7dc6d0d-1eca-5fa6-8ad6-5aecde8b7ea5"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["DataStructures", "DataValues", "Dates", "Logging", "Random", "Test"]

[compat]
julia = "1"
CategoricalArrays = "0.8"
Compat = "2.2, 3"
DataAPI = "1.2"
InvertedIndices = "1"
IteratorInterfaceExtensions = "0.1.1, 1"
Missings = "0.4.2"
PooledArrays = "0.5"
Reexport = "0.1, 0.2"
SortingAlgorithms = "0.1, 0.2, 0.3"
Tables = "1"
TableTraits = "0.4, 1"

The details how to read this file are given here. For this post
the important part is [compat] section (again here you can find
the details). This section informs Julia package manager which versions of other
packages are allowed to be installed with version 0.21.0 of DataFrames.jl.

Conflicting dependency requirements

The crucial thing is that when you install packages Julia package manager
analyzes the [compat] section of each package required to be installed in the
project and tries to find such a combination of package versions that is
possible to be active at the same time.

Sometimes it is impossible to find such a combination and you get an error
that starts with the words Unsatisfiable requirements detected for package.
This is a problematic situation, and here you have a description
how to read such a message, and here some tips how to fix them.

While getting an error is a critically problematic situation it has one positive
element: you know that something went wrong.

In this post we want to concentrate on a less critical case: you install several
packages, you get no errors, but for some strange reasons your code does not
work as expected. The likely situation is caused by the fact that the package
manager has decided that it is only possible to install some old version of a
package, that e.g. can have a different API than the latest version of the
package that you have implemented your code against.

From my experience such situations are quite common and hard to detect
especially if you have dozens of installed packages.

Let me give a simple example of such a situation. Recently I wanted to install
Plots.jl and GraphPlot.jl packages. Here is a dump of the steps I have made in
a clean environment:

(@v1.4) pkg> activate .
 Activating new environment at `~/Project.toml`

(bkamins) pkg> status
Status `~/Project.toml`
  (empty environment)

(bkamins) pkg> add Plots
   Updating registry at `~/.julia/registries/General`
   Updating git-repo `https://github.com/JuliaRegistries/General.git`
  Resolving package versions...
   Updating `~/Project.toml`
  [91a5bcdd] + Plots v1.2.3
   Updating `~/Manifest.toml`
  [6e34b625] + Bzip2_jll v1.0.6+2
  [35d6a980] + ColorSchemes v3.9.0
  [3da002f7] + ColorTypes v0.10.3
  [5ae59095] + Colors v0.12.0
  [d38c429a] + Contour v0.5.3
  [9a962f9c] + DataAPI v1.3.0
  [864edb3b] + DataStructures v0.17.15
  [c87230d0] + FFMPEG v0.3.0
  [b22a6f82] + FFMPEG_jll v4.1.0+3
  [53c48c17] + FixedPointNumbers v0.8.0
  [d7e528f0] + FreeType2_jll v2.10.1+2
  [559328eb] + FriBidi_jll v1.0.5+3
  [28b8d3ca] + GR v0.49.1
  [4d00f742] + GeometryTypes v0.8.3
  [682c06a0] + JSON v0.21.0
  [c1c5ebd0] + LAME_jll v3.100.0+1
  [dd192d2f] + LibVPX_jll v1.8.1+1
  [442fdcdd] + Measures v0.3.1
  [e1d29d7a] + Missings v0.4.3
  [77ba4419] + NaNMath v0.3.3
  [e7412a2a] + Ogg_jll v1.3.4+0
  [458c3c95] + OpenSSL_jll v1.1.1+2
  [91d4177d] + Opus_jll v1.3.1+1
  [bac558e1] + OrderedCollections v1.2.0
  [69de0a69] + Parsers v1.0.3
  [ccf2f8ad] + PlotThemes v2.0.0
  [995b91a9] + PlotUtils v1.0.2
  [91a5bcdd] + Plots v1.2.3
  [3cdcf5f2] + RecipesBase v1.0.1
  [01d81517] + RecipesPipeline v0.1.9
  [189a3867] + Reexport v0.2.0
  [ae029012] + Requires v1.0.1
  [992d4aef] + Showoff v0.3.1
  [a2af1166] + SortingAlgorithms v0.3.1
  [90137ffa] + StaticArrays v0.12.3
  [2913bbd2] + StatsBase v0.33.0
  [83775a58] + Zlib_jll v1.2.11+9
  [0ac62f75] + libass_jll v0.14.0+2
  [f638f0a6] + libfdk_aac_jll v0.1.6+2
  [f27f6e37] + libvorbis_jll v1.3.6+4
  [1270edf5] + x264_jll v2019.5.25+2
  [dfaa095f] + x265_jll v3.0.0+1
  [2a0f44e3] + Base64
  [ade2ca70] + Dates
  [8bb1440f] + DelimitedFiles
  [8ba89e20] + Distributed
  [b77e0a4c] + InteractiveUtils
  [76f85450] + LibGit2
  [8f399da3] + Libdl
  [37e2e46d] + LinearAlgebra
  [56ddb016] + Logging
  [d6f4376e] + Markdown
  [a63ad114] + Mmap
  [44cfe95a] + Pkg
  [de0858da] + Printf
  [3fa0cd96] + REPL
  [9a3f8284] + Random
  [ea8e919c] + SHA
  [9e88b42a] + Serialization
  [6462fe0b] + Sockets
  [2f01184e] + SparseArrays
  [10745b16] + Statistics
  [8dfed614] + Test
  [cf7118a7] + UUIDs
  [4ec0a83e] + Unicode

(bkamins) pkg> add GraphPlot
  Resolving package versions...
   Updating `~/Project.toml`
  [a2cc645c] + GraphPlot v0.3.1
   Updating `~/Manifest.toml`
  [ec485272] + ArnoldiMethod v0.0.4
  [a81c6b42] + Compose v0.8.2
  [a2cc645c] + GraphPlot v0.3.1
  [d25df0c9] + Inflate v0.1.2
  [c8e1da08] + IterTools v1.3.0
  [093fc24a] + LightGraphs v1.3.3
  [1914dd2f] + MacroTools v0.5.5
  [699a6c99] + SimpleTraits v0.9.2
  [1a1011a3] + SharedArrays

(bkamins) pkg> status
Status `~/Project.toml`
  [a2cc645c] GraphPlot v0.3.1
  [91a5bcdd] Plots v1.2.3

All seems to went through without a problem. Except that the current release of
GraphPlot.jl (as of time of writing this post) is 0.4.2.

So we try adding this version of the package:

(bkamins) pkg> add [email protected]
  Resolving package versions...
   Updating `~/Project.toml`
  [a2cc645c] ↑ GraphPlot v0.3.1 ⇒ v0.4.2
  [91a5bcdd] ↓ Plots v1.2.3 ⇒ v1.0.14
   Updating `~/Manifest.toml`
  [35d6a980] - ColorSchemes v3.9.0
  [3da002f7] ↓ ColorTypes v0.10.3 ⇒ v0.9.1
  [5ae59095] ↓ Colors v0.12.0 ⇒ v0.11.2
  [53c48c17] ↓ FixedPointNumbers v0.8.0 ⇒ v0.7.1
  [28b8d3ca] ↓ GR v0.49.1 ⇒ v0.48.0
  [a2cc645c] ↑ GraphPlot v0.3.1 ⇒ v0.4.2
  [ccf2f8ad] ↓ PlotThemes v2.0.0 ⇒ v1.0.3
  [995b91a9] ↓ PlotUtils v1.0.2 ⇒ v0.6.5
  [91a5bcdd] ↓ Plots v1.2.3 ⇒ v1.0.14

And we see that multiple packages got downgraded to their earlier versions.

In the next two sections we will discuss: (a) how to detect such situations
automatically, and (b) how to diagnose what is the root cause of the problem.

Automatically detecting packages installed in a version that is not latest

Here is a short snippet that lists you the packages that are installed but
are not in their latest versions. It is probably not 100% proof for corner
cases of various configurations (this is what Julia package manager does, and
it is a piece of complex code) but shoul be good enough for typical use cases.
It produces us a dictionary of packages that are not installed in their latest
versions giving us a tuple indicating installed and latest version for each
such package.

julia> using Pkg

julia> cd(joinpath(DEPOT_PATH[1], "registries", "General")) do
           deps = Pkg.dependencies()
           registry = Pkg.TOML.parse(read("Registry.toml", String))
           general_pkgs = registry["packages"]

           constrained = Dict{String, Tuple{VersionNumber,VersionNumber}}()
           for (uuid, dep) in deps
               suuid = string(uuid)
               dep.is_direct_dep || continue
               dep.version === nothing && continue
               haskey(general_pkgs, suuid) || continue
               pkg_meta = general_pkgs[suuid]
               pkg_path = joinpath(pkg_meta["path"], "Versions.toml")
               versions = Pkg.TOML.parse(read(pkg_path, String))
               newest = maximum(VersionNumber.(keys(versions)))
               if newest > dep.version
                   constrained[dep.name] = (dep.version, newest)
               end
           end

           return constrained
       end
Dict{String,Tuple{VersionNumber,VersionNumber}} with 1 entry:
  "Plots" => (v"1.0.14", v"1.2.3")

As you can see we get an information that Plots.jl currently is not in its
latest version as expected. We already know that it was downgraded when forcing
to install GraphPlot.jl in version 0.4.2.

Let me give a few comments how the presented code works:

  • we run our function in the registries/General subdirectory of the first
    entry of the DEPOT_PATH variable (this is where Julia package manager first
    looks for installed packages);
  • the deps variable is a a dictionary of all dependencies of the current
    project;
  • Registry.toml holds information about available packages in the registry,
    we parse it and store dictionary with package information in the
    general_pkgs variable;
  • constrained is a dictionary that we will populate with information about
    packages that are not in their latest version
  • we iteratively scan all packages in deps dictionary we are interested only
    in packages that are direct dependencies (that were explicitly installed),
    have a version (packages from standard library do not have a version but we
    do not install them anyway), and that are present in general_pkgs dictionary
    (this means that if you are using several depots or sone non-standard way
    of installing packages this code is not guaranteed to be 100% correct);
  • if we have a package that is interesting for us we get a list of all its
    available versions from Versions.toml that is stored in its directory,
    we extract all the versions, convert them to VersionNumber type and select
    a maximum version (note that VersionNumber type has a properly defned order
    so this is safe to do);
  • finally we compare the installed and latest versions of the packages and if
    they differ this information is stored and later returned.

Diagnosing the problems with package version constraints

We have learned that upgrading GraphPlot.jl package to version 0.4.2 caused us
dependency problems. Let us then find out what are its copat sepecifications.
Here is the code that does the job:

julia> using GraphPlot
[ Info: Precompiling GraphPlot [a2cc645c-3eea-5389-862e-a155d0052231]

julia> toml_loc = joinpath(dirname(pathof(GraphPlot)), "..", "Project.toml");

julia> Pkg.TOML.parse(read(toml_loc, String))["compat"]
Dict{String,Any} with 7 entries:
  "Compose"               => "0.7, 0.8"
  "julia"                 => "1"
  "ColorTypes"            => "0.9"
  "ArnoldiMethod"         => "0.0.4"
  "LightGraphs"           => "1.1"
  "VisualRegressionTests" => ">= 0.2"
  "Colors"                => "0.11"

And we see that ColorTypes.jl and Colors.jl had to be downgraded when we
installed the 0.4.2 version of GraphPlot.jl (we can see in the listing above
that they were downgraded). And downgrading these two packages, in cascade,
made Julia package manager change the versions of other packages, Plots.jl in
particular.

In the code above note that in order to get a path of GraphPlot.jl file from
GraphPlot.jl package we first have to load it with using GraphPlot command.
Then in order to properly construct the toml_loc variable we have to go one
step up in the path with .. as GraphPlot.jl file is located in src
subdirectory in the package sources.

In order to resolve such problems systematically is best to submit a PR to the
packages that have outdated dependencies. This is exactly what I did
here (and the PR got merged as of this writing).