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

Sealed functions #758

Open
icatalud opened this issue Dec 20, 2019 · 4 comments
Open

Sealed functions #758

icatalud opened this issue Dec 20, 2019 · 4 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@icatalud
Copy link

Functions declared sealed are constrained to:

  • Access only immutable values outside of their local scope
  • Only invoke sealed functions

The introduction of sealed functions makes it possible to define computed constants.

@icatalud icatalud added the feature Proposed language feature that solves one or more problems label Dec 20, 2019
@lrhn
Copy link
Member

lrhn commented Jan 2, 2020

You need to define "immutable values" somehow. If it means only accessing const variables, then it's trivially true, but otherwise being immutable is not a property of an object (value) that can be detected statically. In general, no non-trivial property of a value is statically detectable if the value itself isn't known at compile-time, which means it's a constant.

Can instance methods be sealed? Must overriding methods in subclasses also be sealed? Must classes implementing the interface also make the member sealed? (In which case, they cannot be implemented using noSuchMethod, and therefore must be there).

(Apart from that, sealed sounds like something else, this sounds more like C++ const functions).

@icatalud

This comment has been minimized.

@icatalud

This comment has been minimized.

@icatalud
Copy link
Author

icatalud commented Jan 6, 2020

Sealability definition

A specification and explanation of the implications from the idea proposed at the beginning of the issue.

Conventions:

  • It is assumed that methods receive this as a hidden input (like python self).
  • Functions: static functions, methods, getters, setters, constructors, closures (anything that is invoked).

i. Definitions

A. Sealed views (SV) (inspired by #125, #225)

All types have a sealed view (SV) subset interface that corresponds to the methods that fulfill two conditions:

  1. They are sealed
  2. Input this is a sealed view

By definition:

  1. Default getters fulfill (1) and (2)
  2. Default setters fulfill (1)
  3. Invocations on sealed views return sealed views

The declaration of a sealed view must be explicit, it could either be a sub-interface denoted by some symbol or a sealed keyword that adds to final, var, const.

Side note (detail about sealed views as parameters, may be skipped)
Sealed inputs should be interpreted as <sealed or not>, in reality they represent the two types. Imagine the function has a special definition for every different combination of the parameters sealed - non sealed, where their return type can be an SV or not depending on how it is inferred in the function body for each of the cases, unless the return type is explicitly an SV or fixed type. This is important, because passing sealed parameters defines a contract on how the parameters are going to be used, but it shouldn’t constraint the function to only return values retrieved from the parameters as views, because that severely cripples the versatility of defining sealed views as input type because of (5), when the reality is that they should be used whenever it is possible as they are a great way to document the intention of a function. The idea of sealed views as parameters is to guarantee that only the sealed view interface is going to be used but not to constraint the possible return values of the function which can be values retrieved from SV inputs.

That consideration is a must at least with the this input, because otherwise getters could only return sealed views and the whole proposal is invalidated (it becomes impossible to modify anything).

Side note (may be skipped)
A sub-interface is feasible and reasonable, because it should be possible to extend or implement classes by sealing their return types, but when doing so they become illegal candidates for their implemented type. A keyword adds unnecessary complexity if sealed views were rarely used. But on the other hand they should be used frequently as a way to document functions. For example sealed List<Foo> foo means nor the list nor anything inside Foo will be written (with an interface approach it could be something like $List<Foo> foo).

B. Sealed functions (SF) (inspired by #704)

A sealed function fulfills these conditions:

  1. Invocations must be to sealed functions
  2. Vars outside of their local scope are accessed as sealed views
  3. Vars outside of their local scope cannot be written

Convenient classifications of sealed functions:

  • Self sealed: this is sealed
  • Strong sealed: All inputs are sealed
  • Weak sealed: At least one input is not sealed

Sealed functions should be explicitly declared, because it makes the intention of an interface transparent, and when overriding methods sealability should be respected or constrained. If sealability of functions is implicit and a child class is overriding methods with a non sealed body, how should the analyzer proceed if all methods of the parent class are unintentionally sealed?

Side note (may be skipped)
Defining getters becomes inconvenient with SF, specially because wrapper getters would break the default expected sealability unless they are declared sealed. Probably a rule for making special concessions needs to be made. On the other hand, there is no real difference between getters, setters and methods besides the notation. This is an intuitive distinction between them:

  • Getter: A function that retrieves a value without leaving traces (it makes no changes in the state of the program). This is equivalent to the definition (A.3).
  • Setter: A function that mutates data reachable from this. Like (A.4).
  • Method: Can do anything.

Enforcing those constraints is very useful as a declaration of intention, it makes their existence meaningful, because otherwise they are purely cosmetic features.

ii. Implications

a. The scope of effect in the state of the program from a sealed function must be a subset (inclusive) from the scope of effect of the function that invoked it.

Setters are not reachable through SVs (A.4) and SVs return only SVs (A.5). References to objects created inside an SF are lost when the function finishes executing if it can only access external references as SVs (B.3). The only way a sealed function can have access to an external reference that can be written is by receiving it as parameter (B.2), but that parameter came from the function that invoked it.

b. A strong sealed function cannot modify the state of the program

A strong sealed function does not receive any regular reference as input parameter, therefore it has no scope of effect in the state of the program (no external setters are reachable) and neither do the sealed function invocations from it, because they must have a subset of the scope of effect of the root sealed function invocation (a).

iii. Applications

Computed constants: A strong sealed function is an encapsulated environment that cannot alter the state of the program (b). Their output is constant on a program that hasn’t received any external input.

Communication between isolates #125: Sealed views are a natural way to share values from one isolate to another, because the receiver of the sealed view cannot perform any mutations through it. Sealed views are not immutable but it is guaranteed that only the isolate that created it can perform mutations of values that can be reached through the view. This could be exploited positively by creating special interfaces on top of it, given that the sealed view effectively opens a channel of communication from one isolate to another. Immutability rules of certain fields like final fields still apply. In #225 there is a proposal to make freezable objects which is symbiotic with the idea of passing objects from one isolate to another since the receiver of a view could check for frozen objects and know that their shallow state is not going to change.

Initialization: If besides the invocation by declarations of computed constant values it was allowed to invoke strongly sealed functions directly in the source files, it would be possible to perform constant assertions #625. Strictly speaking constant assertions can be performed during the computation of a constant, but it’s more readable and elegant to be able to declare a specialized function for it. Sealed functions cannot write vars outside of their function scope (B.3), but what if static non-nullable types were allowed to be declared without a value and these vars were the exception that sealed functions could write only if they are uninitialized (when a non-nullable is null)? If at the end of initialization any non-nullable vars were null, it would be an error. This implies that if a non-nullable static var is declared without value it must be initialized, which is informative and it is also ensured that it is not initialized twice, because that would also produce an error.

Function contracts: Sealed views serve as a great way of documenting that a var and all values retrieved from it (in a deep sense) are to be used in read-only operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

2 participants