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>
 |