-
Notifications
You must be signed in to change notification settings - Fork 87
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
Base types for contracts (or contracts seen as refinement types) #420
Comments
I like this a lot (esp the first proposal for the static typing). It has a smart constructor feeling, except built into the language (and with an explicit upcast), which is pretty awesome.
and be able to treat
Maybe we could prevent this by (assuming that
That sounds a bit limiting a priori, but maybe it isn’t too much of an issue in practice (in the worst case this can be worked around with a bit of boilerplate when needed). And because it’s conservative it leaves room for extending it later if needs be. So that’s probably OK. |
This makes sense. As you say this means that |
I do not understand really what
I don't really agree on this, for me it would be very limiting to restrict this feature to builtin types or static types. Not restricting it would be dangerous if someone doesn't observe any good practice with type definition, but would allow to create wrappers and gradually create big abstractions, which why we need a configuration language originally imo. I do not really like the |
I don't know. I just recently started dogfooding, but I currently haven't had the use for a contract based over another custom contract in practice: it's always mostly refined over base types like Bottom line is, refinement over general custom contracts is not out of the question, but we need good evidence that this actually useful in practice, which is not obvious to me at the time being.
Note that the last suggestion is not possible, because it would mean that |
I'm curious (maybe a little confused even) about what is precisely in this issue. I assume that we can already use types as values (their value is the underlying contract). If this is the case, there is no need for a special syntax. In fact, I've opined similarly in a different PR (I don't remember what the actual subject was there): there is a bunch of PRs which seem to call for being special cases of a more general solution. I'd rather try to keep Nickel fairly minimal and avoid having too many special cases. |
There isn't, actually. There is currently no way of getting the contract corresponding to an arbitrary type expression.
For #461, imagining we have this operator to derive contracts from an arbitrary type expression
Which would have the same dynamic semantics as the proposed
However, it wouldn't give anything on the static type side, as everything happens on the side of values. So the typechecker wouldn't be able to leverage the information that It's true that for the boilerplate part, a "naive intersection" operator at the value levels plus an "extract contract" operator could be combined to achieve the same. Although, to be honest, the "naive intersection" operator looks even more like a convenience for a specific case, as we can very well already write as a function |
Maybe this is something that should be fixed, then. Since it seems to be a core component of a number of issues.
Fair enough, but I don't think that your proposal gives a straightforward way to a solution for static typing. Would it be worth introducing a syntax for subtypes such as I don't imagine that we could use arbitrary types as the base type. Subtypes of |
Is your feature request related to a problem? Please describe.
Very often, user-defined contracts start along these lines:
That is, most contracts accept values that are in a subset of an existing builtin type (they are (dynamic) refinement types). This boilerplate feels like it could be added automatically by declaring explicitly a base type for a contract. Moreover, this information can then be leveraged by the type system:
#Port
to aNum
, but not the other way around (this sets#Port
as a subtype ofNum
).#Port
toNum
as in 1., as well as the reverseNum
to#Port
but protecting the unsafe conversion by inserting automatically a contract check. This corresponds more to the philosophy of traditional gradual typing which is to statically allow casts and insert checks automatically between "compatible types" (casts that have a chance of success), but would depart from Nickel's current design to require any statically unsafe cast to be written explicitly.#Port
s asNum
s staticallyDescribe the solution you'd like
A syntax to declare that a contract is a refinement of an existing builtin static type, as it is the case for example in the system of Well-typed program can't be blamed (although they don't do it exactly for the same reasons). Syntax suggestion (
<:
is not taken and fit well with the subtyping interpretation):Dynamically, this would wrap the implementation of
Port
with a code that checks if value is a string in the first place. Something like:Statically, it can be interpreted as any of the alternatives mentioned above, but this can be decided later. We could probably actually support having any contract (including all static types that are convertible to contracts) as a base by defining
refine
as:However, this can quickly leads to cyclic contracts, that would both loop dynamically as well as introduce cycles in the subtyping relation which may even make typechecking loop:
In practice, restricting to 0-ary types (that is
Num
,Str
andBool
) should cover most use cases. Extending to any "static" type (which doesn't contain any user-defined contract as a subtype) is probably fine.Describe alternatives you've considered
refine
combinator in the stdlib, so that users can reduce boilerplate without introducing a new specific syntax. One downside is that the information about the base type of a contract can't be leveraged by the typechecker in the future.The text was updated successfully, but these errors were encountered: