diff options
Diffstat (limited to 'nixos/doc/manual/configuration/abstractions.xml')
-rw-r--r-- | nixos/doc/manual/configuration/abstractions.xml | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/nixos/doc/manual/configuration/abstractions.xml b/nixos/doc/manual/configuration/abstractions.xml new file mode 100644 index 000000000000..cbd54bca62f9 --- /dev/null +++ b/nixos/doc/manual/configuration/abstractions.xml @@ -0,0 +1,166 @@ +<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> |