[Nix-dev] Re: NixOS: New scheme

Nicolas Pierron nicolas.b.pierron at gmail.com
Mon Dec 22 20:03:53 CET 2008


On Mon, Dec 22, 2008 at 16:02, Ludovic Courtès <ludo at gnu.org> wrote:
> In good faith I was trying to write an Upstart job for PulseAudio using
> this new style, but I'm afraid it's beyond my capabilities...

I don't think so ;)
So I will try to explain it with more details.

> "Nicolas Pierron"
> <nicolas.b.pierron at gmail.com> writes:
>
>> The implementation need all other module used to implement it and the
>> options defined by this module with the attribute named "require"
>
> I don't understand this sentence.

Me too, my fault.  The "require" attribute is used to list all
configuration files that you need to implement your own configuration
file safely.  The idea is that it imports in your own attribute set
all configuration that are needed for your implementation.

>>   require = [
>>     # options needed for the implementation
>>     (import ../upstart-jobs/default.nix)
>
> This happens to be commented out, e.g., in `upstart-jobs/cron.nix' in
> `trunk'.  Why would it be needed?

In the trunk, "../upstart-jobs/default.nix" is not yet a valid
configuration file, therefore this would create a failure.

>>     # options defined by this module
>>     options
>>   ];
>
> Interestingly, options defined by this module are *provided*, not
> *required*, so why does one need to list them in the `require'
> attribute?!

This is more like a legacy from the first time I got this idea, which
is to import sub-configuration files.

> Furthermore, how does that `require' magic work?  It appears that
> `fixOptionSetsFun' (from `lib/default.nix' in Nixpkgs) plays an
> important role, but I don't understand it, and the comment above isn't
> very helpful either to me.

Usually a magician does not reveal his tricks, but I am not one ;)
The concept is quite simple, I will explain it by describing each
function in detail and their role in the problem.

uniqFlatten:
This function takes a list "x" and flatten it with the function
"prop.next", at the same time it skips elements which have the same
key (prop.key x) and it returns the list of values (prop.value x).
This function handle the "require" attribute inside the "prop.next"
which retrieves the content of this attribute.  Elements retrieved
from the "require" attribute are candidate to be added in the list if
there no elements with the same "key" is already inside the returned
list.

This function handle the "require" attribute to create a list of
imported elements without duplication.  This function does the same as
a text closure except that you have keys which are using a side effect
of the Nix evaluator.

Nix a lazy language with maximal sharing.  Both features are used
here.  The laziness is used to skip the evaluation of value and the
maximal sharing is used to compare the elements returned by the
imports statements (this is a side effect of the evaluator).  Thus
uniqFlatten is able to remove duplicated imports by comparing
functions & sets implementations.

zip:
This simple function is used to merge all attribute (configuration)
sets into one big configuration set.

mergeOptionSets & filterOptionSets:
These functions are used to merge all configuration sets together with
the zip function.  When they reach an attribute which has an option
definition (result of the function "partition isOption opts") then it
will look into it to know how to merge all elements of the list "opts"
which are not option definitions ( test.wrong == filter (x: !
(isOption x) opts ).

test.right == [] -> no option defined => recursion to merge deepest options.
test.right != [] -> at least one option is defined.
| test.wrong == [] -> no values are defined, use the default value.
| test.wrong != [] -> values are defined, use the merge function of the option.

fixOptionSetsFun:
This function takes the computed configuration (either computed by fix
or one already well defined) and use uniqFlatten to fetch all
configuration sets and merge them with mergeOptionSets or
filterOptionSets.

mkIf & mkThenElse & mkAlways & *If:
These functions are used to factor parts of the code that looks like

{
  foo = if config.qux.enable then .. else ..;
  bar = if config.qux.enable then [..] else []; // most common case.
(else empty [..])
  baz = [..];
}

by

mkIf  config.qux.enable {
  foo = mkThenElse {
    thenPart = ..;
    elsePart = ..;
  };
  bar = [..]; // computed by evalIf
  baz = mkAlways [..];
}

The important point is where the condition is put could lead to an
infinite recursion caused by the evaluation of the config attribute.
So the only possible solution is to use this "mkIf" which allow to
delay the evaluation of the condition until it is evaluated by the
merge function.

delayIf:
It is used to move the mkIf statement deeper inside the attribute set.
 This function is used during the traversal made by mergeOptionSets &
FilterOptionSets.

evalIf:
It is used to evaluate the condition before the merging of the option values.

rmIf:
It is used to ignore a condition.  Its main usage is inside
fixOptionSetsFun to access the "require" attribute without specifying
any mkAlways around it.  So you cannot do conditional import (with
mkIf) has you need to import all files in order to evaluate the
condition.

I hope these descriptions helps you to get a better understanding of the system.

-- 
Nicolas Pierron
http://www.linkedin.com/in/nicolasbpierron
- If you are doing something twice then you should try to do it once.



More information about the nix-dev mailing list