1<section xmlns="http://docbook.org/ns/docbook"
2 xmlns:xlink="http://www.w3.org/1999/xlink"
3 xmlns:xi="http://www.w3.org/2001/XInclude"
4 version="5.0"
5 xml:id="sec-module-abstractions">
6
7<title>Abstractions</title>
8
9<para>If you find yourself repeating yourself over and over, it’s time
10to abstract. Take, for instance, this Apache HTTP Server configuration:
11
12<programlisting>
13{
14 services.httpd.virtualHosts =
15 [ { hostName = "example.org";
16 documentRoot = "/webroot";
17 adminAddr = "alice@example.org";
18 enableUserDir = true;
19 }
20 { hostName = "example.org";
21 documentRoot = "/webroot";
22 adminAddr = "alice@example.org";
23 enableUserDir = true;
24 enableSSL = true;
25 sslServerCert = "/root/ssl-example-org.crt";
26 sslServerKey = "/root/ssl-example-org.key";
27 }
28 ];
29}
30</programlisting>
31
32It defines two virtual hosts with nearly identical configuration; the
33only difference is that the second one has SSL enabled. To prevent
34this duplication, we can use a <literal>let</literal>:
35
36<programlisting>
37let
38 exampleOrgCommon =
39 { hostName = "example.org";
40 documentRoot = "/webroot";
41 adminAddr = "alice@example.org";
42 enableUserDir = true;
43 };
44in
45{
46 services.httpd.virtualHosts =
47 [ exampleOrgCommon
48 (exampleOrgCommon // {
49 enableSSL = true;
50 sslServerCert = "/root/ssl-example-org.crt";
51 sslServerKey = "/root/ssl-example-org.key";
52 })
53 ];
54}
55</programlisting>
56
57The <literal>let exampleOrgCommon =
58<replaceable>...</replaceable></literal> defines a variable named
59<literal>exampleOrgCommon</literal>. The <literal>//</literal>
60operator merges two attribute sets, so the configuration of the second
61virtual host is the set <literal>exampleOrgCommon</literal> extended
62with the SSL options.</para>
63
64<para>You can write a <literal>let</literal> wherever an expression is
65allowed. Thus, you also could have written:
66
67<programlisting>
68{
69 services.httpd.virtualHosts =
70 let exampleOrgCommon = <replaceable>...</replaceable>; in
71 [ exampleOrgCommon
72 (exampleOrgCommon // { <replaceable>...</replaceable> })
73 ];
74}
75</programlisting>
76
77but not <literal>{ let exampleOrgCommon =
78<replaceable>...</replaceable>; in <replaceable>...</replaceable>;
79}</literal> since attributes (as opposed to attribute values) are not
80expressions.</para>
81
82<para><emphasis>Functions</emphasis> provide another method of
83abstraction. For instance, suppose that we want to generate lots of
84different virtual hosts, all with identical configuration except for
85the host name. This can be done as follows:
86
87<programlisting>
88{
89 services.httpd.virtualHosts =
90 let
91 makeVirtualHost = name:
92 { hostName = name;
93 documentRoot = "/webroot";
94 adminAddr = "alice@example.org";
95 };
96 in
97 [ (makeVirtualHost "example.org")
98 (makeVirtualHost "example.com")
99 (makeVirtualHost "example.gov")
100 (makeVirtualHost "example.nl")
101 ];
102}
103</programlisting>
104
105Here, <varname>makeVirtualHost</varname> is a function that takes a
106single argument <literal>name</literal> and returns the configuration
107for a virtual host. That function is then called for several names to
108produce the list of virtual host configurations.</para>
109
110<para>We can further improve on this by using the function
111<varname>map</varname>, which applies another function to every
112element in a list:
113
114<programlisting>
115{
116 services.httpd.virtualHosts =
117 let
118 makeVirtualHost = <replaceable>...</replaceable>;
119 in map makeVirtualHost
120 [ "example.org" "example.com" "example.gov" "example.nl" ];
121}
122</programlisting>
123
124(The function <literal>map</literal> is called a
125<emphasis>higher-order function</emphasis> because it takes another
126function as an argument.)</para>
127
128<para>What if you need more than one argument, for instance, if we
129want to use a different <literal>documentRoot</literal> for each
130virtual host? Then we can make <varname>makeVirtualHost</varname> a
131function that takes a <emphasis>set</emphasis> as its argument, like this:
132
133<programlisting>
134{
135 services.httpd.virtualHosts =
136 let
137 makeVirtualHost = { name, root }:
138 { hostName = name;
139 documentRoot = root;
140 adminAddr = "alice@example.org";
141 };
142 in map makeVirtualHost
143 [ { name = "example.org"; root = "/sites/example.org"; }
144 { name = "example.com"; root = "/sites/example.com"; }
145 { name = "example.gov"; root = "/sites/example.gov"; }
146 { name = "example.nl"; root = "/sites/example.nl"; }
147 ];
148}
149</programlisting>
150
151But in this case (where every root is a subdirectory of
152<filename>/sites</filename> named after the virtual host), it would
153have been shorter to define <varname>makeVirtualHost</varname> as
154<programlisting>
155makeVirtualHost = name:
156 { hostName = name;
157 documentRoot = "/sites/${name}";
158 adminAddr = "alice@example.org";
159 };
160</programlisting>
161
162Here, the construct
163<literal>${<replaceable>...</replaceable>}</literal> allows the result
164of an expression to be spliced into a string.</para>
165
166</section>