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

Support for dependent pattern matching #2256

Merged
merged 50 commits into from
Apr 1, 2021
Merged

Support for dependent pattern matching #2256

merged 50 commits into from
Apr 1, 2021

Conversation

aseemr
Copy link
Collaborator

@aseemr aseemr commented Mar 19, 2021

Overview

This PR introduces support for dependent pattern matching in F*. With SMT support available in deciding the relations between types, we haven't felt much need for it. However, in Steel like DSLs where typing relations are mostly handled using unification, we have been working around using handwritten cond combinator (that too only for boolean), and using them. For example, Steel.Effect.fsti defines:

val cond (#a:Type)
         (b:bool)
         (p: bool -> slprop)
         (q: bool -> a -> slprop)
         (then_: (unit -> SteelT a (p true) (q true)))
         (else_: (unit -> SteelT a (p false) (q false)))
  : SteelT a (p b) (q b)

which is then used in Steel.Primitive.ForkJoin as follows:

 cond b (pre t) (post p) (join_case_true t) (join_case_false t (join #p))

where join_case_true and join_case_false are hoisted to the top-level to get better unification etc.

With this PR, we can now annotate match and if with a returning annotation that supplies a computation type, and get the same effect. So the code in Steel.Primitive.ForkJoin can now be written as 6b94c39.

How does it work

When typechecking

match x returns C with
| P_i -> e_i

where C is a computation type annotation, we typecheck e_i as:

G, x |- e_i <: C[P_i/x]

I.e. with an expected computation type C where the scrutinee x has been substituted by the pattern P_i. After typechecking the branches, the final computation type for the match expression is C.

In our small example above, we will typecheck the then branch with expected computation type as (St b)[true/b] i.e. St true, and similarly for the else branch. The computed and expected computation types for the branches are then syntactically same, and hence no smt is required.

Syntax etc.

Both match and if can now be optionally annotated with returns C or returns t:

if e returns C then e1 else e2

and

match e returns C with | P-i -> e_i

Enhancements

While the current PR state is enough for examples such as Steel.Primitive.ForkJoin, it can be enhanced on the lines of as and in annotations in Coq.

  • Right now we enforce that if you are using returning annotation, the scrutinee must be a variable. This restriction can go with an as annotation.
  • To get more help from inductive type indices, we could also add the support for in annotations.
  • Syntax discussions

@aseemr aseemr requested a review from nikswamy March 19, 2021 09:05
@aseemr aseemr mentioned this pull request Mar 19, 2021
48 tasks
@@ -1004,32 +1004,89 @@ and tc_maybe_toplevel_term env (e:term) : term (* type-checked
check_inner_let_rec env top

and tc_match (env : Env.env) (top : term) : term * lcomp * guard_t =

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

cc @nikswamy , the main change in the PR

@@ -208,7 +208,7 @@ let rec inspect_ln (t:term) : term_view =
| BU.Inl bv -> Tv_Let (true, lb.lbattrs, bv, lb.lbdef, t2)
end

| Tm_match (t, brs) ->
| Tm_match (t, _, brs) -> //AR: TODO: expose the annotation in the reflection API
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would be great to address this now, otherwise we'll forget and not do it until a user complains about it

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

(match asc_opt with
| None -> ""
| Some (asc, tacopt) ->
U.format2 "ret %s%s "
Copy link
Collaborator

Choose a reason for hiding this comment

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

returns

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed, thanks for the catch!

Env.set_expected_typ env res_t, res_t, Env.conj_guard g1 g in
| Tm_match(e1, ret_opt, eqns) -> //ret_opt is the return annotation
let e1, c1, g1 = tc_term
({(env |> Env.clear_expected_typ |> fst) with instantiate_imp = true})
Copy link
Collaborator

@nikswamy nikswamy Mar 31, 2021

Choose a reason for hiding this comment

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

May be a bit more abstract to write it as

env |> Env.clear_expected_typ |> fst |> instantiate_both

?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done, thanks!

| Some (t_or_c, None) ->
//typecheck the return annotation and unset expected type in the env if exists
//the branch typechecking (tc_eqn) will typecheck the branch with an ascription
let env, _ = Env.clear_expected_typ env in
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we check that if an expected type is set in the environment, then the type in the returns annotation is compatible with the environment's expected type?

Otherwise, I wonder what would happen if we had something like

(match x returns t with  ...) <: t'

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We do it at the end of tc_match, I have also added a comment here to that effect.

| Some asc ->
asc
|> SS.subst_ascription [NT (scrutinee, norm_pat_exp)]
|> U.ascribe branch_exp in
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than adding an ascription node and then removing it, couldn't we just set the Env.expected_typ?

Some pros/cons:

  • That would mean we don't disturb the syntax of the term to typecheck it
  • We don't re-typecheck the annotated returns clause
  • But, currently, we only support Env.expected_typ rather than Env.expected_effect. Can we add support for Env.expected_comp_type so that we can handle both cases of a returns annotation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, agreed, would be great to have Env.expected_comp. As we discussed offline, we can take it up separately.

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.

2 participants