167 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			XML
		
	
	
	
	
	
		
		
			
		
	
	
			167 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			XML
		
	
	
	
	
	
|   | <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> | |||
|  | { | |||
|  |   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 | |||
|  | { | |||
|  |   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> | |||
|  | { | |||
|  |   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> | |||
|  | { | |||
|  |   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> | |||
|  | { | |||
|  |   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> | |||
|  | { | |||
|  |   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> |