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

Implement importing paths as Location #585

Merged
merged 9 commits into from
Jun 25, 2019
Merged

Implement importing paths as Location #585

merged 9 commits into from
Jun 25, 2019

Conversation

f-f
Copy link
Member

@f-f f-f commented Jun 10, 2019

Fix #71

This standardizes native support for reading import locations into Dhall, as specified by @Gabriel439 in #71 (comment)

So e.g. if you do

./some/import as Location

..then you'd have it read in Dhall as

< Local : Text | Remote : Text | Environment : Text | Missing >.Local "./some/import"

The main reason why this is useful as a builtin is that we can exploit the canonicalization and chaining that Dhall uses for imports so that relative paths stay correct over relative importing of other Dhall expressions. This sounds like a mouthful, so here's an example (there is also a test about this):

  • if ./nested/import.dhall contains missing
  • if ./nested/importLocation.dhall contains ./import.dhall as Location
  • and here.dhall contains ./nested/importLocation.dhall (note that this is a normal import)
  • then resolving here.dhall in the current directory will give:
< Local : Text | Remote : Text | Environment : Text | Missing >.Local "./nested/import.dhall"

-- Note: `./nested/import.dhall` is never resolved!

I also noted that missing will not fail to resolve when imported as Location, so e.g. the ? operator will not have the usual behaviour (there's a test about this too)

(missing as Location) ? 42

-- This resolves to:
< Local : Text | Remote : Text | Environment : Text | Missing >.Missing

Things I'm unsure of in this PR:

  • I was lazy and did not introduce a judgement to convert from non-Dhall import locations to the Dhall value containing the import location. I'm also afraid this might mean formalizing how imports need to be formatted?
  • Maybe though it's enough to specify that locations are to be represented as they are parsed, except that this doesn't hold, because the paths are being canonicalized and chained. But maybe it's ok to slightly handwave here, as I feel there are not many other interpretations

@f-f
Copy link
Member Author

f-f commented Jun 10, 2019

Note that there's no B side of the parser test yet as there's no implementation to produce it yet 😄

standard/imports.md Outdated Show resolved Hide resolved

parent </> import₀ = import₁
canonicalize(import₁) = child
ε ⊢ child : < Local : Text | Remote : Text | Environment : Text | Missing >
Copy link
Member

@ocharles ocharles Jun 10, 2019

Choose a reason for hiding this comment

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

I'm not sure this is really what I'm after. Firstly, I want normalisation to rule out impossible paths, so Missing is no good. Next, it seems like Local doesn't produce an absolute path, which is also what I'm interested in (though maybe less so than ruling out missing files).

But can't I recover Missing myself with ?? Can't I write

let add-missing : Location -> Location-With-Missing = ...
in (add-missing (./foo.dhall as Location)) ? Location-With-Missing.Missing

Copy link
Member Author

Choose a reason for hiding this comment

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

@ocharles would you want Dhall to check if a file exists or not? I would like to represent possibly non-existent paths (e.g. for the case in which you're configuring a tool that will create these paths)

Re making the paths absolute: I don't have a strong opinion on it as it's just about attaching the CWD to the relative paths, I just feel that it would be special casing, and I have the same feeling about failing on the missing keyword instead of including it as a constructor.
Do you have a use-case for which you cannot handle Missing and you cannot handle relative paths?

Copy link
Member

@ocharles ocharles Jun 10, 2019

Choose a reason for hiding this comment

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

would you want Dhall to check if a file exists or not?

Yes.

I would like to represent possibly non-existent paths (e.g. for the case in which you're configuring a tool that will create these paths)

Ok, but I think that is covered by using ? missing as mentioned in my comment above.

I will have to page in exactly how I want to use this feature to see if I can handle Missing. It's probably stuff in my Dhall interpreter, so that could throw errors accordingly at runtime. I would, naturally, prefer to avoid runtime errors though.

Copy link
Member

Choose a reason for hiding this comment

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

I would like to represent possibly non-existent paths (e.g. for the case in which you're configuring a tool that will create these paths)

Ok, on further thought I think I see what you're getting at. We'd like to be able to write

{ config-dir = ./config as Location }

Under my proposal, that is an import failure if ./config doesn't already exist. Using ? we don't really have the tools to construct a Location, the best we can do is:

{ config-dir = (< Existing = ./config as Location | New : Text >) ? (< New = "./config" | Existing : Location >) }

which is a bit meh.

However, what if we had syntax for constructing Locations? Then we could

{ config-dir = Location "./config" }

The lack of an import here clarifies that this Location might not exist.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, but I think that is covered by using ? missing as mentioned in my comment above

I don't see how? I want the path information, not a Missing. The Missing constructor would only be used to represent the missing keyword

Copy link
Member

Choose a reason for hiding this comment

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

Yea, I think I made a mistake in that original comment. I replied to myself with hopefully a bit more clarification.

Copy link
Member Author

@f-f f-f Jun 10, 2019

Choose a reason for hiding this comment

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

I see. But what's wrong in them looking like imports? The semantics are very close - they are parsed like imports and get canonicalized and chained at import-resolution time - and the only thing that would change would be the semantics of ?, which will never fail on Locations

EDIT: and right, the other difference which is related to the change of the ? semantics is that the paths might not exist, which is why you want to differentiate them, I see now

Copy link
Contributor

Choose a reason for hiding this comment

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

One suggestion is that as Location could also return another Bool field specifying whether or not the file was present. Then, later on, once we have dependent types you could assert that the file was present using something like this:

let path = ./example.dhall as Location

let _ = Assert : path.present  True

Copy link
Member Author

@f-f f-f Jun 15, 2019

Choose a reason for hiding this comment

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

So we had a chat with @ocharles yesterday, and I think we'd like different enough features that they'd warrant different syntax (rather than trying to cram everything in the same syntax):

  • "Path representation":
    • this is just about representing locations in Dhall
    • the syntax could be ./some/import as Location
    • it would return a union like proposed by @Gabriel439 above < Local : Text | Remote : Text | Environment : Text | Missing >
    • it would only canonicalize and chain
    • it would never resolve (as it's only about representing locations), so it would never fail
    • this is what this PR implements
  • "Absolute paths resolution":
    • this is about having a path resolve to its absolute representation
    • the syntax could be ./some/path as AbsolutePath
    • it would return a Text with the absolute path to a file and it would do the full blown canonicalization, chaining and resolution
    • it would fail if the file doesn't exist, if the file is on a remote location, and in case of missing
    • this is what @ocharles would like to have (please correct any inaccuracies)

Co-Authored-By: Ollie Charles <ollie@ocharles.org.uk>
@Gabriella439
Copy link
Contributor

Another thing we could do is have the type of the result depend on the type of the import

For example:

  • an absolute path or home-anchored path could always return a < Local : Text > (or just Text)
  • a missing could always return a < Missing >
  • an environment variable could always return a < Environment : Text > (or just Text)
  • a URL could always return a < Remote : Text > (or just Text)
  • a relative path could return a < Local : Text | Remote : Text >

@singpolyma
Copy link
Collaborator

Remote : Text seems like a waste when the URL has already been parsed. Why not use URL : { scheme : Text, authority : Text, path: [Text], query: Text } sort of thing if we're going to expose bits of the AST this way?

@philandstuff
Copy link
Collaborator

Remote : Text seems like a waste when the URL has already been parsed. Why not use URL : { scheme : Text, authority : Text, path: [Text], query: Text } sort of thing if we're going to expose bits of the AST this way?

I can think of two reasons against this:

  • I’m hoping to get rid of URL parsing as part of Proposal: return to standard RFC 3986 URLs #581
  • if you have a { scheme : Text, authority : Text, path: [Text], query: Text }, Dhall doesn’t give you the tools to turn it back into a URL, which is probably a quite important use case. In particular, you can’t percent-encode the raw path segments.

@singpolyma
Copy link
Collaborator

singpolyma commented Jun 15, 2019 via email

@ocharles
Copy link
Member

@singpolyma We did have a little chat about what is essentially a "flag" to the import type, but I'm not huge on - especially as you could just have distinctly named import types and achieve the same thing.

Copy link
Contributor

@Gabriella439 Gabriella439 left a comment

Choose a reason for hiding this comment

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

We're at the 7 day mark for making a decision, so I'll decide to approve. I feel that if we have additional substantive changes we wish to make to improve this feature they should be made as subsequent proposals.

I think the discussion has strayed a bit from the problem that @f-f was originally trying to solve for spago, which I understood as: "provide a path that is referentially transparent". I support that goal because providing language support for referentially transparent paths is a security feature: if users try to refer to paths as Text fields within their configurations then it risks the host program accidentally reading in the wrong file if the Dhall configuration is interpreted from the wrong location.

As @singpolyma noted: there is a separate discussion about whether or not we should resolve the path to verify that it is there, but I think that should be out of scope for this proposal because:

  • (A) it's not addressing the original requirement
  • (B) it's not a feature that spago needs and I haven't seen an open source tool that does need it (yet)
  • (C) we can always add it later via a new keyword as noted in this thread, so we're not painting ourselves into a corner by not supporting it yet

@ocharles
Copy link
Member

It's good that we've got some approval on this, but I'm still a bit unsure on this. I'll try and double check this again tomorrow, and see how it reconciles with mine and @f-f's discussion at ZuriHac.

@ocharles
Copy link
Member

I'm still pretty uneasy with the semantics of missing as Location, particularly due to its interaction with ?. What is the motivation for resolving missing?

@f-f
Copy link
Member Author

f-f commented Jun 19, 2019

I'm still pretty uneasy with the semantics of missing as Location, particularly due to its interaction with ?. What is the motivation for resolving missing?

I don't have a strong opinion on it, but I feel it should be there for the same reason we have it in the language in the first place: completeness. More specifically:

  • it should be there to cover the whole range of possible import types
  • it should not fail, because failing on missing would imply that we'd do resolution here, while we don't

On the other hand I agree with you that it's a bit weird that missing, the import that should always fail resolution, seems to change behaviour once you add as Location (but it's not very weird once you frame as Location as a "pure operation" that doesn't do resolution)

@f-f
Copy link
Member Author

f-f commented Jun 25, 2019

I finally managed to put together a proof of concept of this in dhall-lang/dhall-haskell#1019

The implementation was simpler than I expected, as all the machinery is already there

@joneshf
Copy link
Collaborator

joneshf commented Jun 26, 2019

Do I understand correctly that this change means you can concatenate strings together to import expressions? Or, is this something else entirely?

@f-f
Copy link
Member Author

f-f commented Jun 26, 2019

@joneshf this is something entirely different: you can think of it as "reflection for imports", where at "runtime" you have an import path (or url, etc) available as a Text, properly canonicalized and chained (if you don't want that you can always just use a string).
Once you get this representation though, you cannot go back and compose another import, because the import phase happens before runtime

So this is effectively just a "smart newtype" for representing locations (paths and URLs) in Dhall

@joneshf
Copy link
Collaborator

joneshf commented Jun 26, 2019

Gotcha. Thanks for explaining!

@f-f
Copy link
Member Author

f-f commented Jun 26, 2019

You're welcome 🙂

f-f added a commit that referenced this pull request Jun 27, 2019
A couple of fixes for tests:
- rename the `asLocation` parser test I introduced in #585 to the correct path
- bind the variables in the normalization tests introduced in #592
  (normalization tests cannot contain unbound variables because it makes 
  sense to normalize an expression only if it typechecks first)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Import path as absolute path to file
6 participants