-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
lib.attrsets.matchAttrTag: init #304328
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.attrsets.matchAttrTag: init #304328
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |||||
|
|
||||||
| let | ||||||
| inherit (builtins) head length; | ||||||
| inherit (lib.asserts) assertMsg; | ||||||
| inherit (lib.generators) toPretty; | ||||||
| inherit (lib.trivial) mergeAttrs warn; | ||||||
| inherit (lib.strings) concatStringsSep concatMapStringsSep escapeNixIdentifier sanitizeDerivationName; | ||||||
| inherit (lib.lists) foldr foldl' concatMap elemAt all partition groupBy take foldl; | ||||||
|
|
@@ -1726,6 +1728,75 @@ rec { | |||||
| if path == [] then "<root attribute path>" | ||||||
| else concatMapStringsSep "." escapeNixIdentifier path; | ||||||
|
|
||||||
| /** | ||||||
| Turns a list of strings into a human-readable description of those | ||||||
| strings represented as an attribute path. The result of this function is | ||||||
| not intended to be machine-readable. | ||||||
| Create a new attribute set with `value` set at the nested attribute location specified in `attrPath`. | ||||||
|
|
||||||
| # Inputs | ||||||
|
|
||||||
| `context` (String) | ||||||
|
|
||||||
| : The error context in case a tag doesn't have a handler or the value is not valid. | ||||||
|
|
||||||
| `value` (AttrsOf Any) | ||||||
|
|
||||||
| : The value to match. | ||||||
|
|
||||||
| `handlers` (AttrsOf (Any -> Any)) | ||||||
|
|
||||||
| : An attribute set containing a handler function for each case. | ||||||
|
|
||||||
| # Type | ||||||
|
|
||||||
| ``` | ||||||
| matchAttrTag :: String -> AttrsOf Any -> AttrsOf (Any -> Any) -> Any | ||||||
| ``` | ||||||
|
|
||||||
| # Examples | ||||||
| :::{.example} | ||||||
| ## `lib.attrsets.matchAttrTag` usage example | ||||||
|
|
||||||
| ```nix | ||||||
| matchAttrTag "test" { n = 10; } { | ||||||
| n = number: "It's the number ${toString number}"; | ||||||
| s = str: "It's the string ${str}"; | ||||||
| } | ||||||
| => "It's the number 10" | ||||||
|
|
||||||
| matchAttrTag "test" { s = "Paul"; } { | ||||||
| n = number: "It's the number ${toString number}"; | ||||||
| s = str: "It's the string ${str}"; | ||||||
| } | ||||||
| => "It's the string Paul" | ||||||
|
|
||||||
| matchAttrTag "test" { unknown = null; } { | ||||||
| n = number: "It's the number ${toString number}"; | ||||||
| s = str: "It's the string ${str}"; | ||||||
| } | ||||||
| => error: test: No handler for tag "unknown" | ||||||
|
|
||||||
| matchAttrTag "test" { n = 10; s = "Paul"; } { | ||||||
| n = number: "It's the number ${toString number}"; | ||||||
| s = str: "It's the string ${str}"; | ||||||
| } | ||||||
| => error: test: Value has not exactly one tag: [ "n" "s" ] | ||||||
| ``` | ||||||
|
|
||||||
| ::: | ||||||
| */ | ||||||
| matchAttrTag = | ||||||
| context: value: handlers: | ||||||
|
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.
Suggested change
Often, the
Member
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 the most common case is to not curry tbh, and you can still use match value {
Foo { .. } => 0
Bar { .. } => 1
}case value of
Foo { .. } -> 0
Bar { .. } -> 1So I'd really prefer leaving it like that
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 annoying thing is that if you do want to lib.flip, you have to write it like
Member
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 guess if we go for #304328 (comment) this won't be a problem anymore :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. Pipe friendly: map (matchAttrTag "context") myListand value |> matchAttrTag "context" {
...
}Not pipe friendly: map (flip (matchAttrTag "context")) myListand matchAttrTag "context" value {
...
}
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. Also, fwiw, avoiding |
||||||
| let | ||||||
| tagList = attrNames value; | ||||||
| # `lib.types.attrTag` guarantees that the resulting value only has exactly one key | ||||||
| tag = head tagList; | ||||||
| handler = handlers.${tag} or (throw "${context}: No handler for tag \"${tag}\""); | ||||||
| in | ||||||
| assert assertMsg (length tagList == 1) | ||||||
| "${context}: Value has not exactly one tag: ${toPretty { multiline = false; } tagList}"; | ||||||
| handler value.${tag}; | ||||||
|
|
||||||
| /** | ||||||
| Get a package output. | ||||||
|
|
||||||
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.
"test"doesn't seem like a great example. It'd help to have a more realistic use case. When it's a realistic example, it's easier to see what's expected from the context.When this error occurs in the context of a module that defines an option of type
attrTag ..., this case will only occur due to programming error, and I don't think the module author should have the foresight to clarify that in the context.In other cases, you probably do want the context to be quite general.
Maybe we should have another function that takes an option instead of its value? Doesn't work for attrTags when used as part of type constructors like
attrsOfthough.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.
What if the merged result of
attrTagis the matching function itself 🤔This would be doable effectively in a backwards-compatible way using
__functor! And this way we should be able to get a good error message for free. Not a big fan of__functorin general, but in this case I'm really wondering..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.
For my use case, settings, I need to not have it:
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 guess it also wouldn't be very backwards compatible, because any code that asserts for the attribute set to only have one attribute would fail.