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

Generative labels #1169

Open
wants to merge 72 commits into
base: master
Choose a base branch
from
Open

Generative labels #1169

wants to merge 72 commits into from

Conversation

dhil
Copy link
Member

@dhil dhil commented Jan 31, 2023

This PR is a rebase of #1148 on top of the latest changes to master -- in particular those made to effect handlers.

This PR also generalises the previous work the following ways:

  • fresh declarations can be made in any binding contexts (just like fun, var, etc.)
  • fresh does no longer equipped with an explicit scope, wherein the fresh labels are defined, instead it now follows the standard block-structured lexical scope of Links.

This PR also fixes a regression for session exceptions introduced by one of the previous patches, which made a change to the operation syntax at the type level. However, this change was never propagated properly, as a result session exceptions continued to use the reinterpretation of the function arrow.

Orbion-J and others added 30 commits May 13, 2022 14:55
This reverts commit 49a9a61.
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
Co-authored-by: Daniel Hillerström <[email protected]>
@dhil dhil mentioned this pull request Jan 31, 2023
This commit fixes a regression introduced by the previous commit,
wherein numeric labels wouldn't get looked up correctly in
`Label.{Map,Set}`.

This commit also generalises `fresh` such that it can be used locally
within a binding context. Furthermore, it changes the syntax such that
it no longer introduces encloses a list of bindings, but instead uses
the standard block-structured scope of Links.
@dhil
Copy link
Member Author

dhil commented Feb 1, 2023

The feature, as implemented by this patch, does not interact well with some of Links' other features, e.g. modules. However that shouldn't be a blocker. Another thing, after doing some thinking os that I would like to repurpose new for label generation, then we can avoid introducing fresh as a keyword.

@dhil
Copy link
Member Author

dhil commented Feb 2, 2023

It is worth remarking that this patch further widens the gap between type-inferable expressions and type-ascribable expressions. For instance,

links> fun () { fresh `Foo; do `Foo }
fun : () {`Foo:() => a|_}-> a

The programmer can never manually ascribe the effect-type to the above function, because the label \Foo` is not in scope at the point where the type signature would be written down, e.g.

links> fun () { fresh `Foo; do `Foo } : () {`Foo: () => a |_}-> a;
***: Fatal error : The local label `Foo is not bound

A similar problem also exists in SML with local datatype definitions, e.g.

- fn () => let datatype t = Unit in Unit end;;
val it = fn : unit -> ?.t

@slindley
Copy link
Contributor

slindley commented Feb 5, 2023

The new function creates a new access point. We cannot also use it for creating a fresh effect.

@dhil
Copy link
Member Author

dhil commented Feb 5, 2023

The new function creates a new access point. We cannot also use it for creating a fresh effect.

Of course we can. The question is whether we should.

@slindley
Copy link
Contributor

slindley commented Feb 6, 2023

The new function creates a new access point. We cannot also use it for creating a fresh effect.

Of course we can. The question is whether we should.

Well... it's true that we could in theory (with a bit of care to disambiguate syntax) choose to overload new as a keyword for one thing and a built-in function for another, and indeed we do just that for receive (a keyword for a blocking receive from the current mailbox versus a function for receiving a message through a session endpoint).

Here's the relevant parser code for supporting receive as a function:

/* HACK: allows us to support both mailbox receive syntax
and receive for session types. */
| RECEIVE                                                      { with_pos $loc (Var "receive") }

This is indeed a hack... and arguably bad language design. But I could imagine a more systematic way of allowing certain suitably contextually-distinguished keywords (e.g. those that must appear before an open brace character such as spawn) to be reused as function names.

Copy link
Contributor

@frank-emrich frank-emrich left a comment

Choose a reason for hiding this comment

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

In general, I'm having troubles trying to understand how exactly the feature is supposed to work, even after reading the original PR and trying to understand what may have changed in this version. I would appreciate an up-to-date description.

In particular, I'm interested in understanding how the removal of local labels from types and datatype definitions is supposed to work (erase_local_labels_from_tycon and erase_local_labels_from_type in Typesugar).

Also, do we have an idea if this negatively interacts with Remy-style rows once we actually implement them?

(function
| Types.Present t ->
extract t
| _ -> assert false)
(* | Types.Present t -> *)
Copy link
Contributor

Choose a reason for hiding this comment

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

should this be here?

@@ -18,9 +18,9 @@ type tyvar = Quantifier.t
type tyarg = Types.type_arg
[@@deriving show]

type name_set = Utility.stringset
type name_set = Label.Set.t
Copy link
Contributor

Choose a reason for hiding this comment

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

Are all of these really labels? For example, name_map is used by the XmlNode constructor, and I'm not sure if we consider the names in their to be "labels".

type t =
| Local of local
| Global of global
| Number of int
Copy link
Contributor

Choose a reason for hiding this comment

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

After some offline discussion with @dhil it seems like Number is just a special case of Global, with a more efficient internal representation. I think we should therefore rename Number to Global_number and make the latter and Global behave equivalently outside of this module. Further, of_int should be make_global_of_int instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the naming can be better here too:

type t =
   | Bound of ...
   | Textual of ...
   | Number of ...

let bind_labels context ls = {context with label_env = Label.Env.bind_labels ls context.label_env}
(* let unbind_labels context ls = {context with label_env = Label.Env.unbind_labels ls context.label_env} *)

(* let extend = Types.extend_typing_environment *)
Copy link
Contributor

Choose a reason for hiding this comment

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

debugging code?

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure. But it can be deleted.

} ;
Some (`Mutual (qs, tygroup))

(* let erase_local_labels labels decls ctx = *)
Copy link
Contributor

Choose a reason for hiding this comment

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

leftover code

| _ -> dt
in
try Some (e dt)
with CannotErase -> None
Copy link
Contributor

Choose a reason for hiding this comment

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

using exceptions for control flow?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes.

@@ -4898,12 +5127,27 @@ and type_binding : context -> binding -> binding * context * Usage.t =
let () = unify pos ~handle:Gripers.bind_exp
(pos_and_typ e, no_pos Types.unit_type) in
Exp (erase e), empty_context, usages e
| FreshLabel ls ->
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand what's happening here. It seems that at this point, some local labels ls are brought into scope, but then we try to remove occurences of these labels from types in context, which is the typing context before ls are brought into scope?

Is similar logic necessary for when labels are brought into scope using the fresh keyword in, say, a function definition? I assume the code here is only run for standalone fresh declarations, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think there is a bug here actually. The labels are bought into to scope, but I think they should be removed when the block is exited. For this to work correctly, I think we need to track the "level" (i.e. block nesting depth) that the labels were introduced at.

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.

4 participants