Skip to content
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

lib.path.subpath.components: init #242695

Merged
merged 2 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions lib/path/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,27 @@ Decision: All functions remove trailing slashes in their results.

</details>

### Prefer returning subpaths over components
[subpath-preference]: #prefer-returning-subpaths-over-components
Comment on lines +190 to +191
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's why this was added: #244358 (comment)


Observing: Functions could return subpaths or lists of path component strings.

Considering: Subpaths are used as inputs for some functions. Using them for outputs, too, makes the library more consistent and composable.

Decision: Subpaths should be preferred over list of path component strings.

<details>
<summary>Arguments</summary>

- (+) It is consistent with functions accepting subpaths, making the library more composable
- (-) It is less efficient when the components are needed, because after creating the normalised subpath string, it will have to be parsed into components again
- (+) If necessary, we can still make it faster by adding builtins to Nix
- (+) Alternatively if necessary, versions of these functions that return components could later still be introduced.
- (+) It makes the path library simpler because there's only two types (paths and subpaths). Only `lib.path.subpath.components` can be used to get a list of components.
And once we have a list of component strings, `lib.lists` and `lib.strings` can be used to operate on them.
For completeness, `lib.path.subpath.join` allows converting the list of components back to a subpath.
</details>

## Other implementations and references

- [Rust](https://doc.rust-lang.org/std/path/struct.Path.html)
Expand Down
31 changes: 31 additions & 0 deletions lib/path/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,37 @@ in /* No rec! Add dependencies on this file at the top. */ {
${subpathInvalidReason path}''
) 0 subpaths;

/*
Split [a subpath](#function-library-lib.path.subpath.isValid) into its path component strings.
Throw an error if the subpath isn't valid.
Note that the returned path components are also valid subpath strings, though they are intentionally not [normalised](#function-library-lib.path.subpath.normalise).

Laws:

- Splitting a subpath into components and [joining](#function-library-lib.path.subpath.join) the components gives the same subpath but [normalised](#function-library-lib.path.subpath.normalise):

subpath.join (subpath.components s) == subpath.normalise s

Type:
subpath.components :: String -> [ String ]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
subpath.components :: String -> [ String ]
subpath.components :: String -> List String

I wish we didn't repeat Haskell's mistake. At your discretion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of lib.lists uses [ ] so let's stick with that for now. Am wondering what the problem with [ ] in Haskell is though (I only know Idris doesn't support [ ])

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, it's a problem with dependent types, which hints at the more general problem that the list type is not a list.
In Nix we can exemplify this by asking the question what is [ Attrs String ]?
a. A type for lists of any number of elements, each of which is an attrset with string values only,
b1. A type for two-element heterogeneous lists where the first is an attrset and the second is a string,
b2. A type for one-element lists where the element is an attrset with string values only.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That just looks like ambiguous notation to me, same as if you wrote List Attrs String. Needs parenthesis to make sense

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the b1/b2 distinction was a distraction. a/b2 is the one that's the problem: the list type is not a list, so it shouldn't use the notation of the list literal.

Copy link
Member Author

@infinisil infinisil Aug 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I get it now, if both list values and types use the same syntax it's ambiguous and confusing. [ x ] could either be a single-value list with the element x, or if x is a type the type of a list of x's. In comparison [ x y ] can only be one.

So using List x for the type and [ ... ] for values disambiguates them better


Example:
subpath.components "."
=> [ ]

subpath.components "./foo//bar/./baz/"
=> [ "foo" "bar" "baz" ]

subpath.components "/foo"
=> <error>
*/
subpath.components =
subpath:
assert assertMsg (isValid subpath) ''
lib.path.subpath.components: Argument is not a valid subpath string:
${subpathInvalidReason subpath}'';
splitRelPath subpath;

/* Normalise a subpath. Throw an error if the subpath isn't valid, see
`lib.path.subpath.isValid`

Expand Down
13 changes: 13 additions & 0 deletions lib/path/tests/unit.nix
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,19 @@ let
expr = (builtins.tryEval (subpath.normalise "..")).success;
expected = false;
};

testSubpathComponentsExample1 = {
expr = subpath.components ".";
expected = [ ];
};
testSubpathComponentsExample2 = {
expr = subpath.components "./foo//bar/./baz/";
expected = [ "foo" "bar" "baz" ];
};
testSubpathComponentsExample3 = {
expr = (builtins.tryEval (subpath.components "/foo")).success;
expected = false;
};
};
in
if cases == [] then "Unit tests successful"
Expand Down