We want nixos modules to be easy to write. The whole nixos configuration is awesome. However I’d shift focus from “reducing attr path typing” to “be readable and comprehensible by the average programmer knowing a couple of mainstream languages”.

I talked to Pierron on irc and he at least partially agreed on this being a potential issue.

What’s the trouble exactly?

mkIf c {
foo = mkThenElse {
// if
}
{
// else
}
}
is counter intuitive because the else branch is contained within what you’d call the if branch in all the languages I know. Usually the if and else branches have the same level rather then one being contained in the other.

The fix would be easy: drop (mkThenElse attr_if attr_else), condition taken from outer mkIf and introduce a (mkIfElse cond attr_if attr_else) along with a mkMerge [a_1 a_2 .. ] which behaves the way everyone would expect. That Pierron himself wasn’t very sure about whether mkAlways means “always” when being used in mkThenElse part could be treated as sign that mkAlways can be dropped as well.

usage counts (nixos 12 Aug 2011):
mkAlways: 2
mkThenElse: 1

for mkAlways it looks like there is no need to use it very often.
For mkThenElse the rarely has to be used because “else” often means that you could write an alternative module instead. I’d also argue hat its not used often because its hard to understand.

Why do we need a if .. else at all? Because you can’t declare multiple configs within one module which would allow you using mkIf only [1]. For this reason I’d recommend introducing mkMerge.

My attempt to compare the current system with the recommended changes can be seen at [2]. Note that I recommend using full paths for most options because this will make it easier to recognize then. Lookup can be automated easier as well.

[1]: Its not true. You could use config { require [{ config = config1} {config = config2}]} but Pierron told me that support may be dropped because its hard to track down if evaluation error occur.

[2]:

 The naming of mkIf and mkThenElse is a bit unlicky. So let's have a look
 at a example demonstrating its usage.
 The idea is to reduce writing paths. Not sure wether that choice can still
 be considered the being the luckiest
 

 mkIf c1 {

  when_c1 = ".."; # obvious

  foo = mkIf c2 {

    always = mkAlways "ignores c2 ! This this is counter intuitive! Bad thing"

    if_c1_and_c2_A = "if c1 and c2, That's what I'd expect";

    d.z = mkThenElse {
            dz_if_c1_and_c2 = ..
          }
          {
            // if c1 and not c2. What? I'm in if c and c2 branch. So mkThenElse is doing
            // something which is very hard to understand
            dz_c1_and_not_c2 = "...";

            // and this must be tested :) Pierron does not know himself
            always_questionable = mkAlways "..."
          }
  }
 }

 If you don't have foo it feels more naturally:
 config = mkIf c1 (mkThenElse {
    // if
    } {
    // else
    }
   )

Alternative ways to implement a if .. else (could be done!):
config = mkMerge [{
  foo.always = ...
  foo.d.z.always_questionable # ? If this is not the case mkAlways has a bad name
} 
(mkIf c1 {
  when_c1 = "..";

  foo = mkIfElse c2 {
    d.z.dz_if_c1_and_c2 = ..
  } {
    d.z.dz_c1_and_not_c2 =
  }
  })
]

# because full paths are the key to understand what's going on I'm tempted
# to propagate this. That's because we can make editors jump to
# documentation trivially etc.
config = mkMerge [{
  foo.always = ...
  foo.d.z.always_questionable # ? If this is not the case mkAlways has a bad name
} 
(mkIf c1 {
  when_c1 = "..";
})
(mkIf c1 && mkIfElse {
  foo.d.z.dz_if_c1_and_c2 = ..
  }
  {
  foo.d.z.dz_c1_and_not_c2 =
  }
)
]
Submitted by Marc Weber on 12 August 2011 at 17:26

On 12 August 2011 at 17:43 Nicolas Pierron tagged !nbp

Log in to post comments