lib: introduce forEach = flip map (#64723)

* lib: introduce `foreach` = flip map

The main purpose is to bring attention to `flip map`, which improves
code readablity. It is useful when ad-hoc anonymous function
grows two or more lines in `map` application:

```
      map (lcfg:
        let port = lcfg.port;
            portStr = if port != defaultPort then ":${toString port}" else "";
            scheme = if cfg.enableSSL then "https" else "http";
        in "${scheme}://cfg.hostName${portStr}"
      ) (getListen cfg);
```
Compare this to `foreach`-style:
```
      foreach (getListen cfg) (lcfg:
        let port = lcfg.port;
            portStr = if port != defaultPort then ":${toString port}" else "";
            scheme = if cfg.enableSSL then "https" else "http";
        in "${scheme}://cfg.hostName${portStr}"
      );
```
This is similar to Haskell's `for` (http://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Traversable.html#v:for)

* mass replace "flip map -> foreach"

See `foreach`-introduction commit.
```
rg 'flip map ' --files-with-matches | xargs sed -i 's/flip map /foreach /g'
```

* Revert "mass replace "flip map -> foreach""

This reverts commit 3b0534310c.

* mass replace "flip map -> forEach"

See `forEach`-introduction commit.
```
rg 'flip map ' --files-with-matches | xargs sed -i 's/flip map /forEach /g'
```

* rename foreach -> forEach

* and one more place

* add release notes
This commit is contained in:
Danylo Hlynskyi 2019-08-18 18:47:57 +03:00 committed by GitHub
commit d09b4e3c87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 53 additions and 28 deletions

View file

@ -71,7 +71,7 @@ let
zipAttrsWithNames zipAttrsWith zipAttrs recursiveUpdateUntil
recursiveUpdate matchAttrs overrideExisting getOutput getBin
getLib getDev chooseDevOutputs zipWithNames zip;
inherit (lists) singleton foldr fold foldl foldl' imap0 imap1
inherit (lists) singleton forEach foldr fold foldl foldl' imap0 imap1
concatMap flatten remove findSingle findFirst any all count
optional optionals toList range partition zipListsWith zipLists
reverseList listDfs toposort sort naturalSort compareLists take

View file

@ -21,6 +21,19 @@ rec {
*/
singleton = x: [x];
/* Apply the function to each element in the list. Same as `map`, but arguments
flipped.
Type: forEach :: [a] -> (a -> b) -> [b]
Example:
forEach [ 1 2 ] (x:
toString x
)
=> [ "1" "2" ]
*/
forEach = xs: f: map f xs;
/* right fold a binary function `op` between successive elements of
`list` with `nul' as the starting value, i.e.,
`foldr op nul [x_1 x_2 ... x_n] == op x_1 (op x_2 ... (op x_n nul))`.

View file

@ -424,6 +424,18 @@
installer after creating <literal>/var/lib/nextcloud</literal>.
</para>
</listitem>
<listitem>
<para>
There exists now <literal>lib.forEach</literal>, which is like <literal>map</literal>, but with
arguments flipped. When mapping function body spans many lines (or has nested
<literal>map</literal>s), it is often hard to follow which list is modified.
</para>
<para>
Previous solution to this problem was either to use <literal>lib.flip map</literal>
idiom or extract that anonymous mapping function to a named one. Both can still be used
but <literal>lib.forEach</literal> is preferred over <literal>lib.flip map</literal>.
</para>
</listitem>
</itemizedlist>
</section>
</section>

View file

@ -54,11 +54,11 @@ rec {
machinesNumbered = zipLists machines (range 1 254);
nodes_ = flip map machinesNumbered (m: nameValuePair m.fst
nodes_ = forEach machinesNumbered (m: nameValuePair m.fst
[ ( { config, nodes, ... }:
let
interfacesNumbered = zipLists config.virtualisation.vlans (range 1 255);
interfaces = flip map interfacesNumbered ({ fst, snd }:
interfaces = forEach interfacesNumbered ({ fst, snd }:
nameValuePair "eth${toString snd}" { ipv4.addresses =
[ { address = "192.168.${toString fst}.${toString m.snd}";
prefixLength = 24;
@ -88,7 +88,7 @@ rec {
"${config.networking.hostName}\n"));
virtualisation.qemu.options =
flip map interfacesNumbered
forEach interfacesNumbered
({ fst, snd }: qemuNICFlags snd fst m.snd);
};
}

View file

@ -102,7 +102,7 @@ let
# builtins multiply by 4 the memory usage and the time used to compute
# each options.
tryCollectOptions = moduleResult:
flip map (excludeOptions (collect isOption moduleResult)) (opt:
forEach (excludeOptions (collect isOption moduleResult)) (opt:
{ name = showOption opt.loc; } // builtins.tryEval (strict opt.value));
in
keepNames (

View file

@ -276,7 +276,7 @@ with lib;
throw "services.redshift.longitude is set to null, you can remove this"
else builtins.fromJSON value))
] ++ (flip map [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
] ++ (forEach [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
"jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
"snmpExporter" "unifiExporter" "varnishExporter" ]
(opt: mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''

View file

@ -225,7 +225,7 @@ in
''
maxstartdelay = ${toString cfg.maxStartDelay}
${flip concatStringsSep (flip map (attrValues cfg.ups) (ups: ups.summary)) "
${flip concatStringsSep (forEach (attrValues cfg.ups) (ups: ups.summary)) "
"}
'';

View file

@ -15,7 +15,7 @@ let
++ cfg.extraConfigFiles;
devices = attrValues (filterAttrs (_: i: i != null) cfg.interface);
systemdDevices = flip map devices
systemdDevices = forEach devices
(i: "sys-subsystem-net-devices-${utils.escapeSystemdPath i}.device");
in
{

View file

@ -7,7 +7,7 @@ let
inherit (lib) concatStringsSep optionalString;
cfg = config.services.hylafax;
mapModems = lib.flip map (lib.attrValues cfg.modems);
mapModems = lib.forEach (lib.attrValues cfg.modems);
mkConfigFile = name: conf:
# creates hylafax config file,

View file

@ -502,7 +502,7 @@ in
assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
message = "cannot enable X11 forwarding without setting xauth location";}]
++ flip map cfg.listenAddresses ({ addr, ... }: {
++ forEach cfg.listenAddresses ({ addr, ... }: {
assertion = addr != null;
message = "addr must be specified in each listenAddresses entry";
});

View file

@ -129,7 +129,7 @@ in
assertion = cfg.killer != null -> cfg.killtime >= 10;
message = "killtime has to be at least 10 minutes according to `man xautolock`";
}
] ++ (lib.flip map [ "locker" "notifier" "nowlocker" "killer" ]
] ++ (lib.forEach [ "locker" "notifier" "nowlocker" "killer" ]
(option:
{
assertion = cfg."${option}" != null -> builtins.substring 0 1 cfg."${option}" == "/";

View file

@ -78,7 +78,7 @@ let
in imap1 mkHead cfg.xrandrHeads;
xrandrDeviceSection = let
monitors = flip map xrandrHeads (h: ''
monitors = forEach xrandrHeads (h: ''
Option "monitor-${h.config.output}" "${h.name}"
'');
# First option is indented through the space in the config but any

View file

@ -684,7 +684,7 @@ in
assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint;
message = "EFI paths must be absolute, not ${args.efiSysMountPoint}";
}
] ++ flip map args.devices (device: {
] ++ forEach args.devices (device: {
assertion = device == "nodev" || hasPrefix "/" device;
message = "GRUB devices must be absolute paths, not ${device} in ${args.path}";
}));

View file

@ -74,7 +74,7 @@ in
enable = true;
networks."99-main" = genericNetwork mkDefault;
}
(mkMerge (flip map interfaces (i: {
(mkMerge (forEach interfaces (i: {
netdevs = mkIf i.virtual ({
"40-${i.name}" = {
netdevConfig = {
@ -90,7 +90,7 @@ in
name = mkDefault i.name;
DHCP = mkForce (dhcpStr
(if i.useDHCP != null then i.useDHCP else cfg.useDHCP && interfaceIps i == [ ]));
address = flip map (interfaceIps i)
address = forEach (interfaceIps i)
(ip: "${ip.address}/${toString ip.prefixLength}");
networkConfig.IPv6PrivacyExtensions = "kernel";
} ];
@ -102,7 +102,7 @@ in
Kind = "bridge";
};
};
networks = listToAttrs (flip map bridge.interfaces (bi:
networks = listToAttrs (forEach bridge.interfaces (bi:
nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
DHCP = mkOverride 0 (dhcpStr false);
networkConfig.Bridge = name;
@ -173,7 +173,7 @@ in
};
networks = listToAttrs (flip map bond.interfaces (bi:
networks = listToAttrs (forEach bond.interfaces (bi:
nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
DHCP = mkOverride 0 (dhcpStr false);
networkConfig.Bond = name;

View file

@ -926,7 +926,7 @@ in
warnings = concatMap (i: i.warnings) interfaces;
assertions =
(flip map interfaces (i: {
(forEach interfaces (i: {
# With the linux kernel, interface name length is limited by IFNAMSIZ
# to 16 bytes, including the trailing null byte.
# See include/linux/if.h in the kernel sources
@ -934,12 +934,12 @@ in
message = ''
The name of networking.interfaces."${i.name}" is too long, it needs to be less than 16 characters.
'';
})) ++ (flip map slaveIfs (i: {
})) ++ (forEach slaveIfs (i: {
assertion = i.ipv4.addresses == [ ] && i.ipv6.addresses == [ ];
message = ''
The networking.interfaces."${i.name}" must not have any defined ips when it is a slave.
'';
})) ++ (flip map interfaces (i: {
})) ++ (forEach interfaces (i: {
assertion = i.preferTempAddress -> cfg.enableIPv6;
message = ''
Temporary addresses are only needed when IPv6 is enabled.
@ -967,8 +967,8 @@ in
"net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
"net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
} // listToAttrs (flip concatMap (filter (i: i.proxyARP) interfaces)
(i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true)))
// listToAttrs (flip map (filter (i: i.preferTempAddress) interfaces)
(i: forEach [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true)))
// listToAttrs (forEach (filter (i: i.preferTempAddress) interfaces)
(i: nameValuePair "net.ipv6.conf.${i.name}.use_tempaddr" 2));
# Capabilities won't work unless we have at-least a 4.3 Linux
@ -1050,7 +1050,7 @@ in
${cfg.localCommands}
'';
};
} // (listToAttrs (flip map interfaces (i:
} // (listToAttrs (forEach interfaces (i:
let
deviceDependency = if (config.boot.isContainer || i.name == "lo")
then []

View file

@ -21,7 +21,7 @@ let
useNetworkd = networkd;
firewall.checkReversePath = true;
firewall.allowedUDPPorts = [ 547 ];
interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
interfaces = mkOverride 0 (listToAttrs (forEach vlanIfs (n:
nameValuePair "eth${toString n}" {
ipv4.addresses = [ { address = "192.168.${toString n}.1"; prefixLength = 24; } ];
ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ];

View file

@ -56,7 +56,7 @@ let
init = let
init = builtins.replaceStrings [ "\n" ] [ ";" ] (config.init or "");
mkScript = drv: lib.flip map drv.scripts (script: "/script load ${drv}/share/${script}");
mkScript = drv: lib.forEach drv.scripts (script: "/script load ${drv}/share/${script}");
scripts = builtins.concatStringsSep ";" (lib.foldl (scripts: drv: scripts ++ mkScript drv)
[ ] (config.scripts or []));

View file

@ -58,7 +58,7 @@ let
versions = [ "13.8.0" "13.9.0" "13.9.1" ];
in
lib.listToAttrs
(lib.flip map versions
(lib.forEach versions
(v: lib.nameValuePair v (throw "Unsupported citrix_receiver version: ${v}")));
in
deprecatedVersions // supportedVersions;

View file

@ -62,7 +62,7 @@ let
versions = [ ];
in
lib.listToAttrs
(lib.flip map versions
(lib.forEach versions
(v: lib.nameValuePair v (throw "Unsupported citrix_workspace version: ${v}")));
in
deprecatedVersions // supportedVersions;

View file

@ -4,7 +4,7 @@
let
hunspellDirs = with lib; makeSearchPath ":" (flatten (flip map langs (lang: [
hunspellDirs = with lib; makeSearchPath ":" (flatten (forEach langs (lang: [
"${hunspellDicts.${lang}}/share/hunspell"
"${hunspellDicts.${lang}}/share/myspell"
"${hunspellDicts.${lang}}/share/myspell/dicts"