-
-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
Show resolved
Hide resolved
|
||
| 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" | ||||||||||
|
||||||||||
| - truncate 4 "1.2.3-stuff" | |
| => "1.2.3.stuff" | |
| - truncate 3 "1.2.3-stuff" | |
| => "1.2.3-stuff" |
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.
Well, the pitfall of this truncation function follows from the implementation of builtins.splitVersion, which has the following behaviour
builtins.splitVersion "1.2.3-stuff" == [ "1" "2" "3" "stuff" ]In the same way the current (in nixpkgs master branch) majorMinor implementation satisfies the following:
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 majorMinorPatch.
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.
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.
I see, maybe add a note on splitVersion in the documentation.
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.
done
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.
Maybe you should also add a note in the documentation of truncate that it doesn't preserve the delimiter type explicitly.
Outdated
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.
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 versionAtLeast etc. can already deal with versions of different lengths.
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.
The major difference is that versionAtLeast implements an order relation on version numbers (which is total assuming the only separator which is used is .), while the predicates I implement here are not order relations, they are not antisymmetric (in the sense that both isLe "1.1" "1.1.2" and isLe "1.1.2" "1.1" are true) and (isLe x) y should be read as «y is a version which is lower or equal to x». Contrarily to versionAtLeast, its analogous isLe uses its 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 offilter (versionAtLeast "1.0") [ "1.3.1" "1.0.0" "1.0" "0.9.0" "1.0.2" ], which is[ "1.0" "0.9.0" ]is very unnatural to me, both in readingversionAtLeastthe wrong way around and the fact that I would like1.0.2to be included in the output: indeed "1.0.2" is part of the family of "1.0" versions! In comparisonfilter (isLe "1.0") [ "1.3.1" "1.0.0" "1.0" "0.9.0" "1.0.2" ]reads more naturally (we keep versions that are lesser or equal to "1.0") and the outcome[ "1.0.0" "1.0" "0.9.0" "1.0.2" ]matches my expectations: if I wanted to filter more precisely I would give the patch version of the first argument ofisLeinstead, and if I wanted to exclude all "1.0", then I would useisLt "1.0".switch(defined in this PR), for examplenixpkgs/pkgs/development/coq-modules/mathcomp/default.nix
Lines 21 to 22 in 3daa06c
defaultVersion = with versions; switch coq.coq-version [ { case = isGe "8.13"; out = "1.12.0"; } # lower version of coq to 8.10 when all mathcomp packages are ported
EDIT: I corrected a few typos that made it difficult to understand the first paragraph... should be better now.
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.
I agree this should be explained in a better way inside the comment in the code. I will try my best to be clearer.
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.
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?