diff --git a/content/reference/functions/_index.markdown b/content/reference/functions/_index.markdown index ef47ea273..84715d090 100644 --- a/content/reference/functions/_index.markdown +++ b/content/reference/functions/_index.markdown @@ -197,6 +197,45 @@ Before 3.10, the same call would have resulted in running the `format()` function **before** the list is traversed, which is almost never what the user wants. +### Short-circuit evaluation + +Some functions that return `boolean` stop evaluating as soon as the result is +known, without looking at the rest of their arguments or list elements. This is +called short-circuit evaluation. + +`and()` and `or()` evaluate their arguments strictly from left to right and +stop at the first argument that decides the result: the first false argument +for `and()`, the first true argument for `or()`. Function calls in the remaining +arguments are not called. This works like `&&` and `||` in C or shell, so you +can put cheap checks first, or use an earlier argument to guard a later one: + +```cf3 +bundle agent main +{ + classes: + # filestat() is only called if the file exists. + "big_config" + if => and( + fileexists("/etc/myapp.conf"), + isgreaterthan(filestat("/etc/myapp.conf", "size"), "1024") + ); +} +``` + +As described in [Function skipping][Functions#function skipping], `and()` and +`or()` are still skipped while any argument contains an unresolved variable, +even an argument after the one that decides the result. That check expands +variables, it does not call functions. + +Functions that go through a list, or the lines of a file, stop at the first +element that decides the result: + +- `some()`, `none()`, `reglist()`, and `regarray()` stop at the first matching + element. +- `every()` stops at the first element that doesn't match. +- `filesexist()` stops at the first file that can't be accessed. +- `regline()` stops at the first matching line. + ## List of all functions There are a large number of functions built into CFEngine. The following diff --git a/content/reference/functions/and.markdown b/content/reference/functions/and.markdown index 4689a5559..50539976c 100644 --- a/content/reference/functions/and.markdown +++ b/content/reference/functions/and.markdown @@ -13,6 +13,9 @@ any argument evaluates to false. **Arguments**: A list of classes, class expressions, or functions that return classes. +`and()` evaluates its arguments left to right and will return as soon as one is +false. See [short-circuit evaluation][Functions#short-circuit evaluation]. + **Example:** ```cf3 {skip TODO} @@ -25,9 +28,11 @@ commands: **Notes:** Introduced primarily for use with `if` and `unless` promise attributes. -**See also:** `or()`, `not()` +**See also:** `or()`, `not()`, [short-circuit evaluation][Functions#short-circuit evaluation] **History:** - Introduced in 3.2.0, Nova 2.1.0 (2011) - Return type changed from `string` to `boolean` in 3.17.0 (2020) (CFE-3470) +- Stops evaluating arguments at the first false one in 3.29.0 (CFE-4272). + Previously all arguments, including function calls in them, were evaluated. diff --git a/content/reference/functions/every.markdown b/content/reference/functions/every.markdown index fa7bb4f73..1b196d2e6 100644 --- a/content/reference/functions/every.markdown +++ b/content/reference/functions/every.markdown @@ -20,6 +20,8 @@ the [unanchored][unanchored] `regex`. `[a-zA-Z0-9_$(){}\[\].:]+`. It can be a data container or a regular list. +`every()` will return as soon as an element doesn't match. + **Example:** {{< CFEngine_include_snippet(every.cf, #\+begin_src cfengine3, .*end_src) >}} @@ -30,4 +32,4 @@ Output: **History:** The [collecting function][Functions#collecting functions] behavior was added in 3.9. -**See also:** [About collecting functions][Functions#collecting functions], `filter()`, `some()`, and `none()`. +**See also:** [About collecting functions][Functions#collecting functions], [short-circuit evaluation][Functions#short-circuit evaluation], `filter()`, `some()`, and `none()`. diff --git a/content/reference/functions/filesexist.markdown b/content/reference/functions/filesexist.markdown index 47b0fa0b8..0e181051b 100644 --- a/content/reference/functions/filesexist.markdown +++ b/content/reference/functions/filesexist.markdown @@ -19,6 +19,8 @@ this function to return true. - list : The name of the list variable or data container to check, in the range `[a-zA-Z0-9_$(){}\[\].:]+` +`filesexist()` will return as soon as a file can't be accessed. + **Example:** {{< CFEngine_include_snippet(filesexist.cf, #\+begin_src cfengine3, .*end_src) >}} @@ -29,4 +31,4 @@ Output: **History:** The [collecting function][Functions#collecting functions] behavior was added in 3.12. -**See also:** [About collecting functions][Functions#collecting functions], `grep()`, `every()`, `some()`, and `none()`. +**See also:** [About collecting functions][Functions#collecting functions], [short-circuit evaluation][Functions#short-circuit evaluation], `grep()`, `every()`, `some()`, and `none()`. diff --git a/content/reference/functions/none.markdown b/content/reference/functions/none.markdown index cab678d62..143ac29c0 100644 --- a/content/reference/functions/none.markdown +++ b/content/reference/functions/none.markdown @@ -16,6 +16,8 @@ expression `regex`. The regular expression is [unanchored][unanchored]. +`none()` will return as soon as any element matches. + **Example:** {{< CFEngine_include_snippet(none.cf, #\+begin_src cfengine3, .*end_src) >}} @@ -24,6 +26,9 @@ Output: {{< CFEngine_include_snippet(none.cf, #\+begin_src\s+example_output\s*, .*end_src) >}} -**History:** The [collecting function][Functions#collecting functions] behavior was added in 3.9. +**History:** + +- The [collecting function][Functions#collecting functions] behavior was added in 3.9. +- Returns as soon as an element matches in 3.29.0 (CFE-4272). -**See also:** [About collecting functions][Functions#collecting functions], `filter()`, `every()`, and `some()`. +**See also:** [About collecting functions][Functions#collecting functions], [short-circuit evaluation][Functions#short-circuit evaluation], `filter()`, `every()`, and `some()`. diff --git a/content/reference/functions/not.markdown b/content/reference/functions/not.markdown index 9d1862c00..47f3eb44f 100644 --- a/content/reference/functions/not.markdown +++ b/content/reference/functions/not.markdown @@ -7,8 +7,8 @@ aliases: {{< CFEngine_function_prototype(expression) >}} -**Description:** Returns `any` if all arguments evaluate to false and `!any` if -any argument evaluates to true. +**Description:** Returns `any` if the argument evaluates to false and `!any` if +it evaluates to true. {{< CFEngine_function_attributes(expression) >}} diff --git a/content/reference/functions/or.markdown b/content/reference/functions/or.markdown index 6ad5593d0..766c32d54 100644 --- a/content/reference/functions/or.markdown +++ b/content/reference/functions/or.markdown @@ -8,11 +8,14 @@ aliases: {{< CFEngine_function_prototype(...) >}} **Description:** Returns `any` if any argument evaluates to true and `!any` if -any argument evaluates to false. +all arguments evaluate to false. **Arguments**: A list of classes, class expressions, or functions that return classes. +`or()` evaluates its arguments left to right and will return as soon as one is +true. See [short-circuit evaluation][Functions#short-circuit evaluation]. + **Example:** ```cf3 {skip TODO} @@ -25,9 +28,11 @@ commands: **Notes:** Introduced primarily for use with `if` and `unless` promise attributes. -**See also:** `and()`, `not()` +**See also:** `and()`, `not()`, [short-circuit evaluation][Functions#short-circuit evaluation] **History:** - Introduced in 3.2.0, Nova 2.1.0 (2011) - Return type changed from `string` to `boolean` in 3.17.0 (2020) (CFE-3470) +- Stops evaluating arguments at the first true one in 3.29.0 (CFE-4272). + Previously all arguments, including function calls in them, were evaluated. diff --git a/content/reference/functions/regarray.markdown b/content/reference/functions/regarray.markdown index 855595516..0d47cd9b5 100644 --- a/content/reference/functions/regarray.markdown +++ b/content/reference/functions/regarray.markdown @@ -12,6 +12,8 @@ aliases: {{< CFEngine_function_attributes(array, regex) >}} +`regarray()` will return as soon as any element matches. + **Example:** {{< CFEngine_include_snippet(regarray.cf, #\+begin_src cfengine3, .*end_src) >}} @@ -19,3 +21,5 @@ aliases: Output: {{< CFEngine_include_snippet(regarray.cf, #\+begin_src\s+example_output\s*, .*end_src) >}} + +**See also:** [short-circuit evaluation][Functions#short-circuit evaluation] diff --git a/content/reference/functions/regline.markdown b/content/reference/functions/regline.markdown index e1271407e..8817f0704 100644 --- a/content/reference/functions/regline.markdown +++ b/content/reference/functions/regline.markdown @@ -15,6 +15,8 @@ true result. {{< CFEngine_function_attributes(regex, filename) >}} +`regline()` will return as soon as a line matches. + **Examples:** This example shows a way to determine if IPV4 forwarding is enabled or not. @@ -53,4 +55,4 @@ bundle edit_line upgrade_cfexecd } ``` -**See also:** `regcmp()`, `strcmp()` +**See also:** `regcmp()`, `strcmp()`, [short-circuit evaluation][Functions#short-circuit evaluation] diff --git a/content/reference/functions/reglist.markdown b/content/reference/functions/reglist.markdown index 6d3b9e6c2..1b0bc0ac1 100644 --- a/content/reference/functions/reglist.markdown +++ b/content/reference/functions/reglist.markdown @@ -14,6 +14,8 @@ aliases: {{< CFEngine_function_attributes(list, regex) >}} +`reglist()` will return as soon as any item matches. + **Example:** {{< CFEngine_include_snippet(reglist.cf, #\+begin_src cfengine3, .*end_src) >}} @@ -26,6 +28,9 @@ In the example above, the IP address in `$(sys.ipv4[eth0])` must be `escape`d, so that the (.) characters in the IP address are not interpreted as the regular expression "match any" characters. -**History:** The [collecting function][Functions#collecting functions] behavior was added in 3.9. +**History:** + +- The [collecting function][Functions#collecting functions] behavior was added in 3.9. +- Returns as soon as an item matches in 3.29.0 (CFE-4272). -**See also:** `getindices()`, `getvalues()`, [about collecting functions][Functions#collecting functions], and `data` documentation. +**See also:** [short-circuit evaluation][Functions#short-circuit evaluation], `getindices()`, `getvalues()`, [about collecting functions][Functions#collecting functions], and `data` documentation. diff --git a/content/reference/functions/some.markdown b/content/reference/functions/some.markdown index e5c80c2c4..e41801bb0 100644 --- a/content/reference/functions/some.markdown +++ b/content/reference/functions/some.markdown @@ -18,8 +18,8 @@ aliases: It's convenient to set a class to `not => some(".*", mylist)` in order to check if `mylist` is empty. Since `some()` returns as soon as -possible, that is better than using `length()` or `every()` or -`none()` which must traverse the entire list. +possible, that is better than using `length()`, which must traverse the +entire list. **Example:** @@ -31,4 +31,4 @@ Output: **History:** The [collecting function][Functions#collecting functions] behavior was added in 3.9. -**See also:** [About collecting functions][Functions#collecting functions], `filter()`, `every()`, and `none()`. +**See also:** [About collecting functions][Functions#collecting functions], [short-circuit evaluation][Functions#short-circuit evaluation], `filter()`, `every()`, and `none()`. diff --git a/generator/_references.md b/generator/_references.md index 925b03980..aadcab07b 100644 --- a/generator/_references.md +++ b/generator/_references.md @@ -56,6 +56,8 @@ [mpf-services-autorun]: /reference/masterfiles-policy-framework/services-autorun [package-modules-the-api]: /reference/language-concepts/modules/package-module-api#the-api [Functions#collecting functions]: /reference/functions#collecting-functions +[Functions#short-circuit evaluation]: /reference/functions#short-circuit-evaluation +[Functions#function skipping]: /reference/functions#function-skipping [guest_environments]: /reference/promise-types/guest_environments [defaults]: /reference/promise-types/defaults [getgrgid()]: https://linux.die.net/man/3/getgrgid