a099ca4505
[Squashed commits to make git blame etc. more likely to work. -ED]
167 lines
5.0 KiB
XML
167 lines
5.0 KiB
XML
<section 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="sec-module-abstractions">
|
||
|
||
<title>Abstractions</title>
|
||
|
||
<para>If you find yourself repeating yourself over and over, it’s time
|
||
to abstract. Take, for instance, this Apache HTTP Server configuration:
|
||
|
||
<programlisting>
|
||
{
|
||
services.httpd.virtualHosts =
|
||
[ { hostName = "example.org";
|
||
documentRoot = "/webroot";
|
||
adminAddr = "alice@example.org";
|
||
enableUserDir = true;
|
||
}
|
||
{ hostName = "example.org";
|
||
documentRoot = "/webroot";
|
||
adminAddr = "alice@example.org";
|
||
enableUserDir = true;
|
||
enableSSL = true;
|
||
sslServerCert = "/root/ssl-example-org.crt";
|
||
sslServerKey = "/root/ssl-example-org.key";
|
||
}
|
||
];
|
||
}
|
||
</programlisting>
|
||
|
||
It defines two virtual hosts with nearly identical configuration; the
|
||
only difference is that the second one has SSL enabled. To prevent
|
||
this duplication, we can use a <literal>let</literal>:
|
||
|
||
<programlisting>
|
||
let
|
||
exampleOrgCommon =
|
||
{ hostName = "example.org";
|
||
documentRoot = "/webroot";
|
||
adminAddr = "alice@example.org";
|
||
enableUserDir = true;
|
||
};
|
||
in
|
||
{
|
||
services.httpd.virtualHosts =
|
||
[ exampleOrgCommon
|
||
(exampleOrgCommon // {
|
||
enableSSL = true;
|
||
sslServerCert = "/root/ssl-example-org.crt";
|
||
sslServerKey = "/root/ssl-example-org.key";
|
||
})
|
||
];
|
||
}
|
||
</programlisting>
|
||
|
||
The <literal>let exampleOrgCommon =
|
||
<replaceable>...</replaceable></literal> defines a variable named
|
||
<literal>exampleOrgCommon</literal>. The <literal>//</literal>
|
||
operator merges two attribute sets, so the configuration of the second
|
||
virtual host is the set <literal>exampleOrgCommon</literal> extended
|
||
with the SSL options.</para>
|
||
|
||
<para>You can write a <literal>let</literal> wherever an expression is
|
||
allowed. Thus, you also could have written:
|
||
|
||
<programlisting>
|
||
{
|
||
services.httpd.virtualHosts =
|
||
let exampleOrgCommon = <replaceable>...</replaceable>; in
|
||
[ exampleOrgCommon
|
||
(exampleOrgCommon // { <replaceable>...</replaceable> })
|
||
];
|
||
}
|
||
</programlisting>
|
||
|
||
but not <literal>{ let exampleOrgCommon =
|
||
<replaceable>...</replaceable>; in <replaceable>...</replaceable>;
|
||
}</literal> since attributes (as opposed to attribute values) are not
|
||
expressions.</para>
|
||
|
||
<para><emphasis>Functions</emphasis> provide another method of
|
||
abstraction. For instance, suppose that we want to generate lots of
|
||
different virtual hosts, all with identical configuration except for
|
||
the host name. This can be done as follows:
|
||
|
||
<programlisting>
|
||
{
|
||
services.httpd.virtualHosts =
|
||
let
|
||
makeVirtualHost = name:
|
||
{ hostName = name;
|
||
documentRoot = "/webroot";
|
||
adminAddr = "alice@example.org";
|
||
};
|
||
in
|
||
[ (makeVirtualHost "example.org")
|
||
(makeVirtualHost "example.com")
|
||
(makeVirtualHost "example.gov")
|
||
(makeVirtualHost "example.nl")
|
||
];
|
||
}
|
||
</programlisting>
|
||
|
||
Here, <varname>makeVirtualHost</varname> is a function that takes a
|
||
single argument <literal>name</literal> and returns the configuration
|
||
for a virtual host. That function is then called for several names to
|
||
produce the list of virtual host configurations.</para>
|
||
|
||
<para>We can further improve on this by using the function
|
||
<varname>map</varname>, which applies another function to every
|
||
element in a list:
|
||
|
||
<programlisting>
|
||
{
|
||
services.httpd.virtualHosts =
|
||
let
|
||
makeVirtualHost = <replaceable>...</replaceable>;
|
||
in map makeVirtualHost
|
||
[ "example.org" "example.com" "example.gov" "example.nl" ];
|
||
}
|
||
</programlisting>
|
||
|
||
(The function <literal>map</literal> is called a
|
||
<emphasis>higher-order function</emphasis> because it takes another
|
||
function as an argument.)</para>
|
||
|
||
<para>What if you need more than one argument, for instance, if we
|
||
want to use a different <literal>documentRoot</literal> for each
|
||
virtual host? Then we can make <varname>makeVirtualHost</varname> a
|
||
function that takes a <emphasis>set</emphasis> as its argument, like this:
|
||
|
||
<programlisting>
|
||
{
|
||
services.httpd.virtualHosts =
|
||
let
|
||
makeVirtualHost = { name, root }:
|
||
{ hostName = name;
|
||
documentRoot = root;
|
||
adminAddr = "alice@example.org";
|
||
};
|
||
in map makeVirtualHost
|
||
[ { name = "example.org"; root = "/sites/example.org"; }
|
||
{ name = "example.com"; root = "/sites/example.com"; }
|
||
{ name = "example.gov"; root = "/sites/example.gov"; }
|
||
{ name = "example.nl"; root = "/sites/example.nl"; }
|
||
];
|
||
}
|
||
</programlisting>
|
||
|
||
But in this case (where every root is a subdirectory of
|
||
<filename>/sites</filename> named after the virtual host), it would
|
||
have been shorter to define <varname>makeVirtualHost</varname> as
|
||
<programlisting>
|
||
makeVirtualHost = name:
|
||
{ hostName = name;
|
||
documentRoot = "/sites/${name}";
|
||
adminAddr = "alice@example.org";
|
||
};
|
||
</programlisting>
|
||
|
||
Here, the construct
|
||
<literal>${<replaceable>...</replaceable>}</literal> allows the result
|
||
of an expression to be spliced into a string.</para>
|
||
|
||
</section>
|