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
Going further with a builtin-based prelude #847
Comments
More possible issues: This breaks most/all Dhall code in existence.
Running Dhall code would require internet access in many more cases.
|
How would normalized forms look like?Normalized forms for these two inputs will be equivalent: with prelude https://prelude.dhall-lang.org/v10.0.0/package.dhall
List/map let builtin List : Type → Type
let builtin List-fold : ∀(a : Type) → List a → ∀(list : Type) → ∀(cons : a → list → list) → ∀(nil : list) → list
in λ(a : Type)
→ λ(b : Type)
→ λ(f : a → b)
→ λ(xs : List a)
→ List-fold
a
xs
(List b)
(λ(x : a) → λ(`as` : List b) → [ f x ] # `as`)
([] : List b) I'm not sure how exactly builtins should be normalized so the second snippet might not be the final normalized form. I have just realized that this would require that the signature of |
Oh, I guess builtins will just be inlined since they have quasi-structural equality, just like union types are inlined. Then the final normalized form looks like: λ(a : Type)
→ λ(b : Type)
→ λ(f : a → b)
→ λ(xs : (builtin List : Type → Type) a)
→ (builtin List-fold
: ∀(a : Type)
→ (builtin List : Type → Type) a
→ ∀(list : Type)
→ ∀(cons : a → list → list)
→ ∀(nil : list)
→ list)
a
xs
((builtin List : Type → Type) b)
(λ(x : a) → λ(`as` : (builtin List : Type → Type) b) → [ f x ] # `as`)
([] : (builtin List : Type → Type) b) This is not pretty at all. Can we omit those type declarations? I guess we can't without type inference. But if we have type inference and if we are allowed to shorten λ(a : Type)
→ λ(b : Type)
→ λ(f : a → b)
→ λ(xs : !List a)
→ !List-fold
a
xs
(!List b)
(λ(x : a) → λ(`as` : !List b) → [ f x ] # `as`)
([] : !List b) This all might be going a bit too far 🤔 |
I have never used E.g. I would have assumed that normalizing |
What about literals?The following judgments would hold: 1 : (builtin Natural : Type)
[ 1 ] : (builtin List : Type -> Type) (builtin Natural : Type)
"foo" : (builtin Text : Type) This allows users of a Dhall interpreter to pick their own internal representation for In fact, regardless of the fate of the Builtin-Based Prelude Proposal (henceforth BBPP), I think we should totally extend #800 to include builtin declarations for types so that we can embed custom data structures into Dhall and enjoy fast operations on them. |
@matheus23 thanks for the initial idea! |
Further still, this provides a cleaner path for backwards-incompatible changes to builtins. Let's say that for some silly reason we have to change the behavior of -- prelude.dhall
{ Text = builtin Text : Type
, modules =
{ Text =
{ show = builtin Text/show : Text -> Text
, ... } Standard v11 calls -- prelude.dhall
{ Text = builtin Text : Type
, modules =
{ Text =
{ show = builtin Text/show_v2 : Text -> Text
, ... } However, as long as the interpreter provides both builtins, it will actually support both versions of the standard – and you will be able to upgrade the interpreter while still sticking to an older version of the standard, behavior-wise. We will also be able to deprecate and remove functions while still giving users a way to upgrade to a newer version of the standard gradually – i.e. "from now on all new modules should use standard v11" instead of "we have to upgrade everything to v11 or we can't use the new interpreter". |
It also looks like turning keywords/operators into untyped builtins will kill a lot of normalization opportunities – e.g. (But again, I don't know what people use normalization for.) |
Similar to my comment in #800 (comment) I think we should begin from requirements before considering the design My understanding is that the requirements are what you listed underneath the "what we get in return" section, which I will summarize here:
For each requirement I'll first comment on whether I agree with the requirement and then comment on what I believe is the simplest solution that addresses the stated requirement We would like to document built-insI believe the idiom of re-exporting built-ins from the Prelude satisfies this requirement, because we can document the re-exported built-in. For example: We would like to make import lists less granularI question this requirement because I'm not convinced that granular imports are the norm. As a package consumer, I do commonly import the Prelude as a whole (or However, I don't believe the stated design would fix the latter issue: avoiding cyclic imports would still require people to use more granular imports when authoring packages. We would like to make the language tooling more version agnosticI think this is a good goal. More generally, it would be nice if we streamlined the process of adding new builtins to the language by having a uniform way to do so. However, I think #800 addresses a lot of this requirement already. More convenient unqualified importsI question this requirement because I'm not a fan of open imports. In my experience, open imports make it difficult for people unfamiliar with the code to tell where identifiers come from if you have multiple open imports in scope. |
Ideally, in a form like
The import would never be resolved in the first place. Similarly, in
Only the files I don’t know how feasible that is for the dhall standard, but it’s one of the big advantages of interleaving imports with evaluation (like how the nix or the bazel evaluator work). |
Groundwork
Let's pretend that we have implemented #800 and now we have the ability to supply type signatures and documentation for built-ins.
Let's further pretend that #835 got in and now all built-ins (like
List/head
) live in the Dhall prelude, pretty much likeInt64#
lives in the Haskellbase
even though it's a built-in.While we're at it, let's make it so that
List
and friends can be both types and records, so thatList
andList.map
are both accepted. (Don't know how.)Magical prelude import syntax
Then let's implement syntax for importing the prelude, e.g.
with prelude
:Any expression can have not more than one
with prelude
parent. (You can't saywith prelude ... with prelude ...
.)Note: downloading the whole prelude might be slow, unless we use HTTP/2 or consolidate the prelude into a few modules. It only has to be done once, though.
Magic is bad, but here's what we get in return
The prelude proposal provides a bunch of major benefits at once:
IDEs can easily show you documentation for built-ins like
Natural
because they are not exactly built-ins anymore – they are defined entities, with documentation, tests, etc. This works out well for Haskell. Partly solves User documentation of built-in types and functions #766.Currently people mostly seem to import individual functions instead of
let Prelude = ...
. You can't even havelet List = https://prelude.dhall-lang.org/List/package.dhall
because it's a parse error. Per-function imports are kinda annoying, and this proposal kills per-function imports in most cases.IDE plugins and syntax highlighters can highlight local definitions vs Prelude definitions without having to consult the Dhall language server, because now everything not explicitly defined with
let
somewhere above is a Prelude definition.Better ergonomics for heavy DSLs like dhall-kubernetes – I no longer need to type
kubernetes.
everywhere and I still know which bindings are local vs which come from dhall-kubernetes (assuming the idea about syntax highlighting is picked up).As well as some minor benefits:
Discussion: fallback import searches that add .dhall #680 ("fallback import searches that add .dhall") is not needed anymore.
You can always say what version of Dhall standard is used by a piece of code.
You can look at https://prelude.dhall-lang.org/v10.0.0/package.dhall and see exactly what builtins are at your disposal. Also useful in case of codebases with heavy use of custom builtins as per Proposal: "forward declarations" for custom built-ins #800, because now there's a natural place for them.
We can make Dhall interpreter writers' lives easier – see Turn List.map into a builtin #741. Let's say we declare all prelude functions as something like
let map = (builtin List/map : <type>) ? <non-builtin implementation>
. Now every interpreter writer can either provide a fastList/map
builtin, or spend effort on making the non-builtin implementation perform well, or do nothing.So, interpreter writers can start by implementing as few built-ins as possible, and then decide whether their effort would be better spent on implementing N builtins – or making the interpreter smarter. (And "do nothing" remains an option!)
Going further (optional): stripping keywords of their special status
It's not great that
toMap
,Some
,merge
are keywords (for a bunch of reasons). The proposal above won't help us get rid of them, unless we augment #800 with support for untyped builtins.The idea is that you can say:
Then the interpreter will have to provide a typing judgment for the builtin during the typechecking phase.
Going further still: stripping operators of their special status
Mostly same as above. Since we want to prevent users from defining operators (?), we can specify that only builtins can be operators.
In its current incarnation,
dhall format
will not be able to format code without resolving imports (it needs to know operator precedences), which isn't great. However, e.g. Ormolu actually manages to format Haskell code without knowing operator precedences (tweag/ormolu#353), so dhall-lang/dhall-haskell#1496 + some heuristics might turn out to be good enough. Or we could hardcode information about existing operators into the formatter.This will also let us fix things like #701 in a backwards-compatible manner.
How can
List
andList.map
coexist?I don't know how to allow
List
to be used as a type while still allowingList.map
.Perhaps we can go back to
List/map
but specify thatfoo/bar
is a special form meaningmodules.foo.bar
(thanks toT::{x}
for the inspiration). Then the prelude would look like this:This would make it impossible to provide your own
List/foo
names without overriding the prelude (which can be done by using a recursive merge). I don't know whether it's a good thing or a bad thing.The text was updated successfully, but these errors were encountered: