1
0
Fork 0
mirror of https://github.com/NixOS/nix-pills synced 2024-09-19 04:00:13 -04:00
nix-pills/pills/14-override-design-pattern.xml
Pavel Roskin 3f6eb45c97 Fix typos
2024-02-19 23:27:03 -08:00

196 lines
9 KiB
XML

<chapter xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xi="http://www.w3.org/2001/XInclude"
version="5.0"
xml:id="override-design-pattern">
<title>Override Design Pattern</title>
<para>
Welcome to the 14th Nix pill. In the previous <link linkend="callpackage-design-pattern">13th</link> pill, we introduced the
<literal>callPackage</literal> pattern and used it to simplify the composition
of software in a repository.
</para>
<para>
The next design pattern is less necessary, but is useful in many cases and is
a good exercise to learn more about Nix.
</para>
<section>
<title>About composability</title>
<para>
Functional languages are known for being able to compose functions. In particular,
these languages gain expressivity from functions that manipulate an original
value into a new value having the same structure. This allows us to compose
multiple functions to perform the desired modifications.
</para>
<para>
In Nix, we mostly talk about <emphasis role="bold">functions</emphasis>
that accept inputs in order to return <emphasis role="bold">derivations</emphasis>.
In our world, we want utility functions that are able to manipulate those structures.
These utilities add some useful properties to the original value, and we'd like to be
able to apply more utilities on top of the result.
</para>
<para>
For example, let's say we have an initial derivation <literal>drv</literal> and
we want to transform it into a <literal>drv</literal> with debugging information and
custom patches:
</para>
<screen>debugVersion (applyPatches [ ./patch1.patch ./patch2.patch ] drv)</screen>
<para>
The final result should be the original derivation with some changes.
This is both interesting and very different from other packaging approaches,
which is a consequence of using a functional language to describe packages.
</para>
<para>
Designing such utilities is not trivial in a functional language without static
typing, because understanding what can or cannot be composed is difficult.
But we try to do our best.
</para>
</section>
<section>
<title>The override pattern</title>
<para>
In <link linkend="inputs-design-pattern">pill 12</link> we introduced the inputs
design pattern. We do not return a derivation picking dependencies directly from the
repository; rather we declare the inputs and let the callers pass the necessary
arguments.
</para>
<para>
In our repository we have a set of attributes that import the expressions of the
packages and pass these arguments, getting back a derivation. Let's take for example
the <package>graphviz</package> attribute:
</para>
<screen>graphviz = import ./graphviz.nix { inherit mkDerivation gd fontconfig libjpeg bzip2; };</screen>
<para>
If we wanted to produce a derivation of <package>graphviz</package> with a customized
<package>gd</package> version, we would have to repeat most of the above plus
specifying an alternative <package>gd</package>:
</para>
<screen><xi:include href="./14/mygraphviz.txt" parse="text" /></screen>
<para>
That's hard to maintain. Using <code>callPackage</code> would be easier:
</para>
<screen>mygraphviz = callPackage ./graphviz.nix { gd = customgd; };</screen>
<para>
But we may still be diverging from the original <package>graphviz</package> in the repository.
</para>
<para>
We would like to avoid specifying the nix expression again. Instead, we would
like to reuse the original <package>graphviz</package> attribute in the
repository and add our overrides like so:
</para>
<screen>mygraphviz = graphviz.override { gd = customgd; };</screen>
<para>
The difference is obvious, as well as the advantages of this approach.
</para>
<para>
<emphasis role="underline">Note:</emphasis> that <literal>.override</literal> is
not a "method" in the OO sense as you may think. Nix is a functional language.
The<literal>.override</literal> is simply an attribute of a set.
</para>
</section>
<section>
<title>The override implementation</title>
<para>
Recall that the <package>graphviz</package> attribute in the repository is
the derivation returned by the function imported from
<filename>graphviz.nix</filename>. We would like to add a further attribute
named "<literal>override</literal>" to the returned set.
</para>
<para>
Let's start by first creating a function "<literal>makeOverridable</literal>".
This function will take two arguments: a function (that must return a set)
and the set of original arguments to be passed to the function.
</para>
<para>
We will put this function in a <filename>lib.nix</filename>:
</para>
<screen><xi:include href="./14/make-overridable-lib.txt" parse="text" /></screen>
<para>
<literal>makeOverridable</literal> takes a function and a set of original arguments.
It returns the original returned set, plus a new <literal>override</literal> attribute.
</para>
<para>
This <literal>override</literal> attribute is a function taking a set of new
arguments, and returns the result of the original function called with the
original arguments unified with the new arguments. This is admittedly somewhat
confusing, but the examples below should make it clear.
</para>
<para>
Let's try it with <literal>nix repl</literal>:
</para>
<screen><xi:include href="./14/nix-repl-make-overridable-test.txt" parse="text" /></screen>
<para>
Note that, as we specified above, the function <literal>f</literal> does not return
the plain sum. Instead, it returns a set with the sum bound to the name
<literal>result</literal>.
</para>
<para>
The variable <literal>res</literal> contains the result of the function call without
any override. It's easy to see in the definition of <literal>makeOverridable</literal>.
In addition, you can see that the new <literal>override</literal> attribute is a function.
</para>
<para>
Calling <literal>res.override</literal> with a set will invoke the original function
with the overrides, as expected.
</para>
<para>
This is a good start, but we can't override again! This is because the returned
set (with <literal>result = 15</literal>) does not have an <literal>override</literal>
attribute of its own. This is bad; it breaks further composition.
</para>
<para>
The solution is simple: the <literal>.override</literal> function should make the
result overridable again:
</para>
<screen><xi:include href="./14/rec-make-overridable.txt" parse="text" /></screen>
<para>
Please note the <literal>rec</literal> keyword. It's necessary so that we can refer
to <literal>makeOverridable</literal> from <literal>makeOverridable</literal> itself.
</para>
<para>
Now let's try overriding twice:
</para>
<screen><xi:include href="./14/nix-repl-make-overridable-twice.txt" parse="text" /></screen>
<para>
Success! The result is 30 (as expected) because <literal>a</literal> is overridden
to 10 in the first override, and <literal>b</literal> is overridden to 20 in the
second.
</para>
<para>
Now it would be nice if <literal>callPackage</literal> made our
derivations overridable. This is an exercise for the reader.
</para>
</section>
<section>
<title>Conclusion</title>
<para>
The "<literal>override</literal>" pattern simplifies the way we customize packages
starting from an existing set of packages. This opens a world of possibilities for
using a central repository like <literal>nixpkgs</literal> and defining overrides
on our local machine without modifying the original package.
</para>
<para>
We can dream of a custom, isolated <command>nix-shell</command> environment for
testing <package>graphviz</package> with a custom <package>gd</package>:
</para>
<screen>debugVersion (graphviz.override { gd = customgd; })</screen>
<para>
Once a new version of the overridden package comes out in the repository, the
customized package will make use of it automatically.
</para>
<para>
The key in Nix is to find powerful yet simple abstractions in order to let the user
customize their environment with highest consistency and lowest maintenance time, by
using predefined composable components.
</para>
</section>
<section>
<title>Next pill</title>
<para>
In the next pill, we will talk about Nix search paths. By "search path", we mean a
place in the file system where Nix looks for expressions. This answers the
question of where <literal>&lt;nixpkgs&gt;</literal> comes from.
</para>
</section>
</chapter>