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