-
-
Notifications
You must be signed in to change notification settings - Fork 19.4k
lib: etc #113661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
lib: etc #113661
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -200,6 +200,46 @@ rec { | |
| let found = filter pred list; | ||
| in if found == [] then default else head found; | ||
|
|
||
| /* Takes a predicate and a list as input and | ||
| returns a list of lists, separated by elements that match the predicate. | ||
|
|
||
| This is analoguous to `builtins.split separator string`, | ||
| with a predicate instead of a separator | ||
| and a list instead of a string. | ||
|
|
||
| Type: splitList :: (a -> bool) -> [a] -> [ (a | [a]) ] | ||
|
|
||
| The returned list always has the form `[ [...] sep [...] ... sep [...] ]` | ||
| i.e. begins with a list, alternates with separators and ends with a list, | ||
| so that the following is true for `res = splitList pred l`: | ||
| - `res` has length `2 * k + 1`, | ||
| where `k` is the number of elements of `l` that | ||
| match `pred`. (ie `splitList pred l`) | ||
| - `elemAt res (2 * n)` is always a list, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add “where |
||
| which contains no element matching `pred`. | ||
| - `elemAt res (2 * n + 1)` is always a single element that matches `pred`. | ||
|
|
||
| Ie, for all `pred` and `l`, let `k = length (filter pred l)`, we have: | ||
| - `length (splitList pred l) = 2 * k + 1`, | ||
| - `isList (elemAt (splitList pred l) (2 * n))`, for all `n <= k`, | ||
| - `pred (elemAt (splitList pred l) (2 * n + 1))`, for all `n < k`, | ||
| - `flatten (map (pick (splitList pred l)) (range 0 (2 * k))) == l`, where | ||
| `pick = r: n: (if mod n 2 == 1 then singleton else id) (elemAt r n)` | ||
|
|
||
| Examples: | ||
| - splitList (x: x == "x") [ "y" "x" "z" "t" ] | ||
| => [ [ "y" ] "x" [ "z" "t" ] ] | ||
| - splitList (x: false) l == [ l ] | ||
| - splitList (x: x < 2) [ 2 1 3 4 0 1 3 1 ] | ||
| => [ [ 2 ] 1 [ 3 4 ] 0 [ ] 1 [ 3 ] 1 [ ] ] | ||
|
|
||
| */ | ||
|
symphorien marked this conversation as resolved.
Outdated
|
||
| splitList = pred: l: | ||
| let loop = (vv: v: l: if l == [] then vv ++ [v] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The parentheses should be unnecessary here. |
||
| else let hd = head l; tl = tail l; in | ||
| if pred hd then loop (vv ++ [ v hd ]) [] tl else loop vv (v ++ [hd]) tl); | ||
| in loop [] [] l; | ||
|
|
||
| /* Return true if function `pred` returns true for at least one | ||
| element of `list`. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { lib }: | ||
| with lib; { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document these individually, so
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I will rename |
||
| /* Constant predicates: true and false */ | ||
| true = p: true; | ||
| false = p: false; | ||
|
|
||
| /* Predicate intersection, union, complement, | ||
| difference and implication */ | ||
| inter = p: q: x: p x && q x; | ||
| union = p: q: x: p x || q x; | ||
| compl = p: x: ! p x; | ||
| diff = p: q: preds.inter p (preds.compl q); | ||
| impl = p: q: x: p x -> q x; | ||
|
|
||
| /* predicate "being equal to x" */ | ||
| equal = x: y: y == x; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| { lib }: | ||
| with lib; { | ||
| /* Emulate a "switch - case" construct, | ||
| instead of relying on `if then else if ...` */ | ||
|
|
||
| /* Usage: | ||
| ```nix | ||
| switch-if [ | ||
| if-clause-1 | ||
| .. | ||
| if-clause-k | ||
| ] default-out | ||
| ``` | ||
| where a if-clause has the form `{ cond = b; out = r; }` | ||
| the first branch such as `b` is true | ||
|
|
||
| Example: | ||
| ```nix | ||
| let alphanum = n: switch-if [ | ||
| { cond = n == 0; out = "zero"; } | ||
| { cond = n == 1; out = "one"; } | ||
| ] "I do not count beyond one"; in | ||
| [ (alphanum 0) (alphanum 1) (alphanum 42) ] | ||
| ``` | ||
| => `[ "zero" "one" "I do not count beyond one" ]` | ||
|
|
||
| */ | ||
| switch-if = c: d: (findFirst (getAttr "cond") {} c).out or d; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use camelCase ( |
||
|
|
||
| /* Usage: | ||
| ```nix | ||
| switch x [ | ||
| simple-clause-1 | ||
| .. | ||
| simple-clause-k | ||
| ] default-out | ||
| ``` | ||
| where a simple-clause has the form `{ case = p; out = r; }` | ||
| the first branch such as `p x` is true | ||
| or | ||
| ```nix | ||
| switch [ x1 .. xn ] [ | ||
| complex-clause-1 | ||
| .. | ||
| complex-clause-k | ||
| ] default-out | ||
| ``` | ||
| where a complex-clause is either a simple-clause | ||
| or has the form { cases = [ p1 .. pn ]; out = r; } | ||
| in which case the first branch such as all `pi xi` are true | ||
|
|
||
| if the variables p are not functions, | ||
| they are converted to a `equal p` | ||
| if `out` is missing then `default-out` is taken | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a use case for a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I have one somewhere, but anyway, this makes the code shorter!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it? I thought removing this would make this code: switch-if (map (cl: { cond = combine cl var;
out = cl.out or default; }) clauses) default;become: switch-if (map (cl: cl // { cond = combine cl var; }) clauses) default;But if you actually use this, never mind. |
||
|
|
||
| Example: | ||
| ```nix | ||
| let test = v1: v2: with versions; switch [ v1 v2 ] [ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what is the purpose of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahah yes this is deadcode. Well spotted! |
||
| { cases = [ (x: x * 2 < 4) 8 ]; out = "small, 8"; } | ||
| { case = [ 42 42 ]; } # no `out` means `default-out` | ||
| { case = l: fold add 0 l > 42; out = "sum over 42"; } | ||
| { case = [ 0 1 ]; out = "zero, one"; } | ||
| ] "weird"; in | ||
| [ (test 1 8) (test 40 3) (test 0 1) (test 42 42) (test 1 0)] | ||
| ``` | ||
| => `[ "small, 8" "sum over 42" "zero, one" "weird" "weird" ]` | ||
| */ | ||
| switch = var: clauses: default: with preds; let | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should only have
Some additional ideas:
Edit: Fixed my
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sternenseemann Don't you like the fact that when some Indeed
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the “magic” is pretty cool, but I'm also a bit cautious in this regard. There is always the danger of for example a function that is partially applied by accident triggering the magic although you didn't want to — resulting in a weird and hard to debug eval error. Not sure what the best call is here. |
||
| compare = f: if isFunction f then f else equal f; | ||
| combine = cl: var: | ||
| if cl?case then compare cl.case var | ||
| else all (equal true) (zipListsWith compare cl.cases var); in | ||
| switch-if (map (cl: { cond = combine cl var; | ||
| out = cl.out or default; }) clauses) default; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,29 @@ | ||||||||||
| /* Version string functions. */ | ||||||||||
| { lib }: | ||||||||||
|
|
||||||||||
| rec { | ||||||||||
| with lib; | ||||||||||
| let | ||||||||||
| truncate = n: v: concatStringsSep "." (take n (splitVersion v)); | ||||||||||
| opTruncate = op: v0: v: let n = length (splitVersion v0); in | ||||||||||
| op (truncate n v) (truncate n v0); | ||||||||||
| in rec { | ||||||||||
|
|
||||||||||
| /* Break a version string into its component parts. | ||||||||||
|
|
||||||||||
| Example: | ||||||||||
| splitVersion "1.2.3" | ||||||||||
| => ["1" "2" "3"] | ||||||||||
|
|
||||||||||
| Remark: the builtins version of `splitVersion` -- which is | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if this isn't a Frenchism. I would use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the problem with "remark", except that it is an understatement. "Disclaimer" would be more appropriate IMO |
||||||||||
| considered first -- has a behaviour which is different from | ||||||||||
| `(lib.splitString ".")`. For example, it also recognizes `-` and | ||||||||||
| transitions between numbers and other strings as delimiters. | ||||||||||
| This behaviour is hence inconsistent between versions of nix and | ||||||||||
| should not be relied upon. | ||||||||||
|
|
||||||||||
| Example (discouraged): | ||||||||||
| splitVersion "1-2a+3" | ||||||||||
| => [ "1" "2" "a+" "3" ] | ||||||||||
| */ | ||||||||||
| splitVersion = builtins.splitVersion or (lib.splitString "."); | ||||||||||
|
|
||||||||||
|
|
@@ -35,15 +51,106 @@ rec { | |||||||||
| */ | ||||||||||
| patch = v: builtins.elemAt (splitVersion v) 2; | ||||||||||
|
|
||||||||||
| /* Get string of the first n parts of a version string. | ||||||||||
|
|
||||||||||
| Example: | ||||||||||
| - truncate 2 "1.2.3-stuff" | ||||||||||
| => "1.2" | ||||||||||
| - truncate 4 "1.2.3-stuff" | ||||||||||
| => "1.2.3.stuff" | ||||||||||
|
Comment on lines
59
to
60
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it be?
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, the pitfall of this truncation function follows from the implementation of builtins.splitVersion "1.2.3-stuff" == [ "1" "2" "3" "stuff" ]In the same way the current (in nixpkgs master branch) lib.versions.majorMinor "1.2-stuff" == "1.2"
lib.versions.majorMinor "1-stuff" == "1.stuff"I merely abstracted away the body of the function to generalize it to I agree this is not intuitive, but that's the best I can do that does not break compatibility and extends the current library quite uniformly.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, maybe add a note on
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe you should also add a note in the documentation of |
||||||||||
| */ | ||||||||||
|
|
||||||||||
| inherit truncate; | ||||||||||
|
|
||||||||||
| /* Get string of the first two parts (major and minor) | ||||||||||
| of a version string. | ||||||||||
|
|
||||||||||
| Example: | ||||||||||
| majorMinor "1.2.3" | ||||||||||
| => "1.2" | ||||||||||
| */ | ||||||||||
| majorMinor = v: | ||||||||||
| builtins.concatStringsSep "." | ||||||||||
| (lib.take 2 (splitVersion v)); | ||||||||||
| majorMinor = truncate 2; | ||||||||||
|
|
||||||||||
| /* Get string of the first three parts (major, minor and patch) | ||||||||||
| of a version string. | ||||||||||
|
|
||||||||||
| Example: | ||||||||||
| majorMinorPatch "1.2.3-stuff" | ||||||||||
| => "1.2.3" | ||||||||||
| */ | ||||||||||
| majorMinorPatch = truncate 3; | ||||||||||
|
|
||||||||||
| /* Version comparison predicates, | ||||||||||
| - isGe v0 v <-> v is greater or equal than v0 [*] | ||||||||||
| - isLe v0 v <-> v is lesser or equal than v0 [*] | ||||||||||
| - isGt v0 v <-> v is strictly greater than v0 [*] | ||||||||||
| - isLt v0 v <-> v is strictly lesser than v0 [*] | ||||||||||
| - isEq v0 v <-> v is equal to v0 [*] | ||||||||||
| - range low high v <-> v is between low and high [**] | ||||||||||
|
|
||||||||||
| [*] truncating v to the same number of digits as v0 | ||||||||||
| [**] truncating v to low for the lower bound and high for the upper bound | ||||||||||
|
|
||||||||||
| Difference with `versionAtLeast` and `versionOlder`: | ||||||||||
| - `versionAtLeast` and `versionOlder` implement order relations on | ||||||||||
| version numbers, which are total assuming the only separator | ||||||||||
| which is used is `.`. | ||||||||||
|
|
||||||||||
| - `isGe`, `isLe`, `isLt`, and `isEq` are not order relations, they | ||||||||||
| are not antisymmetric (e.g. both `isLe "1.1" "1.1.2"` and `isLe | ||||||||||
| "1.1.2" "1.1"` are true). | ||||||||||
| `(isLe x) y` reads «`y` is a version which is lesser or equal to `x`». | ||||||||||
| Contrarily to `versionAtLeast`, it uses the first argument to | ||||||||||
| determine the length to truncate so that `isLe "1.1" "1.1.2"` is | ||||||||||
| true while `versionAtLeast "1.1" "1.1.2"` is false. | ||||||||||
|
|
||||||||||
| The main motivation is to be able to combine and read them nicely | ||||||||||
| with high order function such as: | ||||||||||
| - `filter`, e.g. the result of | ||||||||||
| ``` | ||||||||||
| filter (versionAtLeast "1.0") | ||||||||||
| [ "1.3.1" "1.0.0" "1.0" "0.9.0" "1.0.2" ] | ||||||||||
| ``` | ||||||||||
| is `[ "1.0" "0.9.0"]`. | ||||||||||
| In comparison | ||||||||||
| ``` | ||||||||||
| filter (isLe "1.0") | ||||||||||
| [ "1.3.1" "1.0.0" "1.0" "0.9.0" "1.0.2" ]` | ||||||||||
| ``` | ||||||||||
| returns `[ "1.0.0" "1.0" "0.9.0" "1.0.2" ]`, which can be read | ||||||||||
| naturally in English as «we keep versions that are lesser or | ||||||||||
| equal to "1.0"» and the outcome matches the expectations that | ||||||||||
| "1.0.0" and "1.0.2" belong to the family of "1.0" versions. | ||||||||||
| If you wanted to filter more precisely you should give the | ||||||||||
| patch version of the first argument of `isLe` instead (as in | ||||||||||
| `isLe "1.0.0"`, and if you wanted to exclude all "1.0", then you | ||||||||||
| should use `isLt "1.0"`. | ||||||||||
| - `switch` as in: | ||||||||||
| pkgs/development/coq-modules/mathcomp/default.nix#L21-L22 | ||||||||||
|
|
||||||||||
| Examples: | ||||||||||
| - isGe "8.10" "8.10.1" | ||||||||||
| => true | ||||||||||
| - isLe "8.10" "8.10.1" | ||||||||||
| => true | ||||||||||
| - isGt "8.10" "8.10.1" | ||||||||||
| => false | ||||||||||
| - isGt "8.10.0" "8.10.1" | ||||||||||
| => true | ||||||||||
| - isEq "8.10" "8.10.1" | ||||||||||
| => true | ||||||||||
| - range "8.10" "8.11" "8.11.1" | ||||||||||
| => true | ||||||||||
| - range "8.10" "8.11+" "8.11.0" | ||||||||||
| => false | ||||||||||
| - range "8.10" "8.11+" "8.11+beta1" | ||||||||||
| => false | ||||||||||
| */ | ||||||||||
| isGe = opTruncate versionAtLeast; | ||||||||||
| isGt = opTruncate (flip versionOlder); | ||||||||||
| isLe = opTruncate (flip versionAtLeast); | ||||||||||
| isLt = opTruncate versionOlder; | ||||||||||
| isEq = opTruncate preds.equal; | ||||||||||
| range = low: high: preds.inter (versions.isGe low) (versions.isLe high); | ||||||||||
|
Comment on lines
149
to
154
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find the naming here pretty confusing and it is non obvious to a reader in another file that the version number gets truncated before the predicate is applied. Also what is the exact motivaton for this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The major difference is that The main motivation is to be able to combine and read them nicely with high order function such as
EDIT: I corrected a few typos that made it difficult to understand the first paragraph... should be better now.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree this should be explained in a better way inside the comment in the code. I will try my best to be clearer.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed a new commit with better explanations (basically what is up here modulo transformation from github PR comment to inline code comment). |
||||||||||
|
|
||||||||||
| } | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if we should use a stronger name like
splitIforsplitListIfto disambiguate frombuiltins.split?