about summary refs log tree commit diff
path: root/nixos/doc/manual/configuration/abstractions.xml
blob: f794085295cf67ea6d4a53c54ab92c8e965d0816 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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>
{
  <xref linkend="opt-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
{
  <xref linkend="opt-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>
{
  <xref linkend="opt-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>
{
  <xref linkend="opt-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>
{
  <xref linkend="opt-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>
{
  <xref linkend="opt-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>