at 16.09-beta 5.2 kB view raw
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>