summary refs log tree commit diff
path: root/nixos/doc/manual/configuration/abstractions.xml
blob: 5bf0635cc1aa614f3fac70592e5be260d7429cd4 (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
<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>