When does the relaxed value restriction kick in in OCaml?

前端 未结 4 582
庸人自扰
庸人自扰 2020-12-19 02:51

Can someone give a concise description of when the relaxed value restriction kicks in? I\'ve had trouble finding a concise and clear description of the rules. There\'s Garr

4条回答
  •  北海茫月
    2020-12-19 03:32

    The question why the two examples given in the addendum are typed differently has puzzled me for a couple of days. Here is what I found by digging into the OCaml compiler's code (disclaimer: I'm neither an expert on OCaml nor on the ML type system).

    Recap

    # let _x = 3 in (fun () -> ref None);;        (*  (1)  *)
    - : unit -> 'a option ref = 
    

    is given a polymorphic type (think ∀ α. unit → α option ref) while

    # let _x = ref 3 in (fun () -> ref None);;    (*  (2)  *)
    - : unit -> '_a option ref = 
    

    is given a monomorphic type (think unit → α option ref, that is, the type variable α is not universally quantified).

    Intuition

    For the purposes of type checking, the OCaml compiler sees no difference between example (2) and

    # let r = ref None in (fun () -> r);;         (*  (3)  *)
    - : unit -> '_a option ref = 
    

    since it doesn't look into the body of the let to see if the bound variable is actually used (as one might expect). But (3) clearly must be given a monomorphic type, otherwise a polymorphically typed reference cell could escape, potentially leading to unsound behaviour like memory corruption.

    Expansiveness

    To understand why (1) and (2) are typed the way they are, let's have a look at how the OCaml compiler actually checks whether a let expression is a value (i.e. "nonexpansive") or not (see is_nonexpansive):

    let rec is_nonexpansive exp =
      match exp.exp_desc with
        (* ... *)
      | Texp_let(rec_flag, pat_exp_list, body) ->
          List.for_all (fun vb -> is_nonexpansive vb.vb_expr) pat_exp_list &&
          is_nonexpansive body
      | (* ... *)
    

    So a let-expression is a value if both its body and all the bound variables are values.

    In both examples given in the addendum, the body is fun () -> ref None, which is a function and hence a value. The difference between the two pieces of code is that 3 is a value while ref 3 is not. Therefore OCaml considers the first let a value but not the second.

    Typing

    Again looking at the code of the OCaml compiler, we can see that whether an expression is considered expansive determines how the type of the let-expressions is generalised (see type_expression):

    (* Typing of toplevel expressions *)
    
    let type_expression env sexp =
      (* ... *)
      let exp = type_exp env sexp in
      (* ... *)
      if is_nonexpansive exp then generalize exp.exp_type
      else generalize_expansive env exp.exp_type;
      (* ... *)
    

    Since let _x = 3 in (fun () -> ref None) is nonexpansive, it is typed using generalize which gives it a polymorphic type. let _x = ref 3 in (fun () -> ref None), on the other hand, is typed via generalize_expansive, giving it a monomorphic type.

    That's as far as I got. If you want to dig even deeper, reading Oleg Kiselyov's Efficient and Insightful Generalization alongside generalize and generalize_expansive may be a good start.

    Many thanks to Leo White from OCaml Labs Cambridge for encouraging me to start digging!

提交回复
热议问题