* [Caml-list] typechecking @ 2014-03-22 20:02 Misha Aizatulin 2014-03-22 20:40 ` Daniel Bünzli 2014-03-23 4:41 ` oleg 0 siblings, 2 replies; 7+ messages in thread From: Misha Aizatulin @ 2014-03-22 20:02 UTC (permalink / raw) To: caml-list Using ocaml 4.00.1 the program below successfully compiles. I think this should be rejected because the call (f t) forces t to be of type t2 whereas the annotation on input function requires it to be of type t1. type t1 = T1 type t2 = T2 let f T2 = () let input (c : in_channel) : t1 = let t = input_value c in f t; t ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] typechecking 2014-03-22 20:02 [Caml-list] typechecking Misha Aizatulin @ 2014-03-22 20:40 ` Daniel Bünzli 2014-03-22 22:12 ` Yaron Minsky 2014-03-23 4:41 ` oleg 1 sibling, 1 reply; 7+ messages in thread From: Daniel Bünzli @ 2014-03-22 20:40 UTC (permalink / raw) To: Misha Aizatulin; +Cc: caml-list Le samedi, 22 mars 2014 à 21:02, Misha Aizatulin a écrit : > type t1 = T1 > type t2 = T2 > > let f T2 = () > > let input (c : in_channel) : t1 = > let t = input_value c in > f t; > t Unmarshaling is not type-safe, you always need to add a type annotation when you input_value. See the warning in the documentation here [1]. Your program should read: type t1 = T1 type t2 = T2 let f T2 = () let input (c : in_channel) : t1 = let t = (input_value c : t1) in f t; t Which the compiler rejects. Best, Daniel [1] http://caml.inria.fr/pub/docs/manual-ocaml-4.01/libref/Marshal.html ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] typechecking 2014-03-22 20:40 ` Daniel Bünzli @ 2014-03-22 22:12 ` Yaron Minsky 2014-03-22 22:24 ` Jonathan Protzenko 0 siblings, 1 reply; 7+ messages in thread From: Yaron Minsky @ 2014-03-22 22:12 UTC (permalink / raw) To: Daniel Bünzli; +Cc: Misha Aizatulin, caml-list Marshal is of course not type-safe, but it's still worth explaining why this happens. My type-theory is weak, but I'll do my best. A naive mental model of type-checking is that the application of [f] should require [t] to be of type [t2], and the constraint in the return value should constrain [t] to be of type [t1], and thus there should be a type-error. Here are some examples that emphasize the point. utop[2]> let z () : t1 = let t = Obj.magic () in f t; t;; val z : unit -> t1 = <fun> If [t] comes in as an argument, we get the expected error: utop[10]> let z t : t1 = f t; t;; Error: This expression has type t2 but an expression was expected of type t1 The issue here, I think, is that the result of Obj.magic (or input_value) is truly polymorphic, so it's simultaneously compatible with all types. And the fact that it's in some contexts where it's used doesn't mean its definition is constrained. A value passed in as an argument, however, is monomorphic within the body of the function. Here's a similar example, without using Obj.magic, just using an empty list, whose type is truly polymorphic. utop[11]> let z () : t1 list = let t = [] in ignore (List.map ~f t); t;; val z : unit -> t1 list = <fun> On the other hand, if we constraint t at the definition time, we get a type error, because that makes the type not polymorphic. utop[13]> let z () : t1 list = let t = ([] : t2 list) in t;; Error: This expression has type t2 list but an expression was expected of type t1 list Type t2 is not compatible with type t1 In the end, Daniel's advice of annotating the value at its creation point is sound, but it's worth understanding why. y On Sun, Mar 23, 2014 at 7:40 AM, Daniel Bünzli <daniel.buenzli@erratique.ch> wrote: > Le samedi, 22 mars 2014 à 21:02, Misha Aizatulin a écrit : >> type t1 = T1 >> type t2 = T2 >> >> let f T2 = () >> >> let input (c : in_channel) : t1 = >> let t = input_value c in >> f t; >> t > > Unmarshaling is not type-safe, you always need to add a type annotation when you input_value. See the warning in the documentation here [1]. Your program should read: > > type t1 = T1 > type t2 = T2 > > let f T2 = () > > let input (c : in_channel) : t1 = > let t = (input_value c : t1) in > f t; > t > > > > Which the compiler rejects. > > Best, > > Daniel > > [1] http://caml.inria.fr/pub/docs/manual-ocaml-4.01/libref/Marshal.html > > -- > Caml-list mailing list. Subscription management and archives: > https://sympa.inria.fr/sympa/arc/caml-list > Beginner's list: http://groups.yahoo.com/group/ocaml_beginners > Bug reports: http://caml.inria.fr/bin/caml-bugs ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] typechecking 2014-03-22 22:12 ` Yaron Minsky @ 2014-03-22 22:24 ` Jonathan Protzenko 0 siblings, 0 replies; 7+ messages in thread From: Jonathan Protzenko @ 2014-03-22 22:24 UTC (permalink / raw) To: caml-list I was also surprised by this example, and I kind of expected input_value to have type in_channel -> '_a. However, input_value is defined as follows: stdlib/pervasives.ml 365:external input_value : in_channel -> 'a = "caml_input_value" Since writing '_a is not allowed, I guess there's not much we can do here. Cheers, ~ jonathan On Sat 22 Mar 2014 11:12:10 PM CET, Yaron Minsky wrote: > Marshal is of course not type-safe, but it's still worth explaining > why this happens. My type-theory is weak, but I'll do my best. > > A naive mental model of type-checking is that the application of [f] > should require [t] to be of type [t2], and the constraint in the > return value should constrain [t] to be of type [t1], and thus there > should be a type-error. Here are some examples that emphasize the > point. > > utop[2]> let z () : t1 = let t = Obj.magic () in f t; t;; > val z : unit -> t1 = <fun> > > If [t] comes in as an argument, we get the expected error: > > utop[10]> let z t : t1 = f t; t;; > Error: This expression has type t2 but an expression was expected of type t1 > > The issue here, I think, is that the result of Obj.magic (or > input_value) is truly polymorphic, so it's simultaneously compatible > with all types. And the fact that it's in some contexts where it's > used doesn't mean its definition is constrained. A value passed in as > an argument, however, is monomorphic within the body of the function. > > Here's a similar example, without using Obj.magic, just using an empty > list, whose type is truly polymorphic. > > utop[11]> let z () : t1 list = let t = [] in ignore (List.map ~f t); t;; > val z : unit -> t1 list = <fun> > > On the other hand, if we constraint t at the definition time, we get a > type error, because that makes the type not polymorphic. > > utop[13]> let z () : t1 list = let t = ([] : t2 list) in t;; > Error: This expression has type t2 list but an expression was expected of type > t1 list > Type t2 is not compatible with type t1 > > In the end, Daniel's advice of annotating the value at its creation > point is sound, but it's worth understanding why. > > y > > On Sun, Mar 23, 2014 at 7:40 AM, Daniel Bünzli > <daniel.buenzli@erratique.ch> wrote: >> Le samedi, 22 mars 2014 à 21:02, Misha Aizatulin a écrit : >>> type t1 = T1 >>> type t2 = T2 >>> >>> let f T2 = () >>> >>> let input (c : in_channel) : t1 = >>> let t = input_value c in >>> f t; >>> t >> >> Unmarshaling is not type-safe, you always need to add a type annotation when you input_value. See the warning in the documentation here [1]. Your program should read: >> >> type t1 = T1 >> type t2 = T2 >> >> let f T2 = () >> >> let input (c : in_channel) : t1 = >> let t = (input_value c : t1) in >> f t; >> t >> >> >> >> Which the compiler rejects. >> >> Best, >> >> Daniel >> >> [1] http://caml.inria.fr/pub/docs/manual-ocaml-4.01/libref/Marshal.html >> >> -- >> Caml-list mailing list. Subscription management and archives: >> https://sympa.inria.fr/sympa/arc/caml-list >> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners >> Bug reports: http://caml.inria.fr/bin/caml-bugs > ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] typechecking 2014-03-22 20:02 [Caml-list] typechecking Misha Aizatulin 2014-03-22 20:40 ` Daniel Bünzli @ 2014-03-23 4:41 ` oleg 2014-03-23 12:59 ` Jacques Garrigue 1 sibling, 1 reply; 7+ messages in thread From: oleg @ 2014-03-23 4:41 UTC (permalink / raw) To: avatar, jonathan.protzenko; +Cc: caml-list Misha Aizatulin wrote: > type t1 = T1 > type t2 = T2 > > let f T2 = () > > let input (c : in_channel) : t1 = > let t = input_value c in > f t; > t The expression input_value c has the type 'a. The variable t is let-bound, so it receives the generalized, polymorphic type forall 'a. 'a In the expression [f t] this polymorphic type is instantiated to t1. In [t], the last expression of [input], the polymorphic type is instantiated to t2. There are no problems. One may shout: wait a minute! The expression [input_value c] is not syntactically a value. Therefore, the value restriction should prevent generalization. The classical value restriction indeed does prevent. But OCaml has a relaxed value restriction. For example, the following legitimate code let foo () = [] val foo : unit -> 'a list = <fun> let f [1] = () val f : int list -> unit = <fun> let bar () : char list = let t = foo () in f t; t val bar : unit -> char list = <fun> typechecks in OCaml -- but it wouldn't under the classical value restriction. The paper Jacques Garrigue: Relaxing the Value Restriction. Proc. Int. Symposium on Functional and Logic Programming, Nara, April 2004. LNCS 2998, pp. 196--213. (extended version: RIMS Preprint 1444) < http://www.math.nagoya-u.ac.jp/~garrigue/papers/morepoly-long.pdf > explains very well why the original value restriction is too restrictive. So, what to do about the original problem? Jonathan Protzenko recommended: > I was also surprised by this example, and I kind of expected > input_value to have type in_channel -> '_a. > Since writing '_a is not allowed, I guess there's not much we can do here. Although writing the type '_a in a type annotation is not allowed, internally such a type could be ascribed to a value (I side-step murky separate compilation issues that creates). So, one could have input_value of the type Jonathan wants. Alas, that is too restrictive. Consider let ll = ref [];; val ll : '_a list ref = {contents = []} let input_list (c:in_channel) = !ll;; val input_list : in_channel -> '_a list = <fun> let c = open_in "/dev/null" in (1::input_list c, true::input_list c) Error: This expression has type int list but an expression was expected of type bool list Type int is not compatible with type bool Some if input_value has the type Jonathan suggest, we can only use input_value to read the values of the same type. One solution is to give input_value a sound type such as the following: val input_value : 'a -> in_channel -> 'a That is, input_value should receive _some_ value of the type it is supposed to read. The user must provide the evidence that the type to read is populated. One problem: it is too cumbersome in practice. The second problem is that function types are all populated -- by, for example, [fun _ -> failwith "black hole"]. The complete solution is to update the function generalize_expansive in typing/ctype.ml which is responsible for implementing the relaxed value restriction. The type 'a should not be considered co-variant in 'a. Alas, such a modification is a bit cumbersome since generalize_expansive is called recursively. One have to split cases. It is not clear how much benefit can be gained -- complicating type checker for the sake of a rare error. ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] typechecking 2014-03-23 4:41 ` oleg @ 2014-03-23 12:59 ` Jacques Garrigue 2014-03-24 8:46 ` Alain Frisch 0 siblings, 1 reply; 7+ messages in thread From: Jacques Garrigue @ 2014-03-23 12:59 UTC (permalink / raw) To: Kiselyov Oleg; +Cc: avatar, jonathan.protzenko, OCaml Mailing List On 2014/03/23 13:41, oleg@okmij.org wrote: > The complete solution is to update the function > generalize_expansive in typing/ctype.ml > which is responsible for implementing the relaxed value restriction. > The type 'a should not be considered co-variant in 'a. Alas, such a > modification is a bit cumbersome since generalize_expansive is called > recursively. One have to split cases. It is not clear how much benefit > can be gained -- complicating type checker for the sake of a rare > error. I don't think it would make sense anyway. I suppose your rationale is that since 'a is empty in the sound part of the language, if you actually get a value of this type it must be unsound, and should not be generalized. However, this would just be a hack, and would not catch all such errors. For instance 'a list contains the empty list, so we have no reason not to generalize it, but an unsound function might still return a non empty list with this type. Moreover, there are also useful applications of the generalization of 'a. For instance, the following idiom allows you to give an arbitrary polymorphic type to a non-value, which you cannot under the strict value restriction. let y = Obj.magic (f x) let z : ('a -> 'a) t = y So well, unsound functions are unsound, and you have to live with that. In particular _always_ annotate with ground types. Jacques Garrigue ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [Caml-list] typechecking 2014-03-23 12:59 ` Jacques Garrigue @ 2014-03-24 8:46 ` Alain Frisch 0 siblings, 0 replies; 7+ messages in thread From: Alain Frisch @ 2014-03-24 8:46 UTC (permalink / raw) To: Jacques Garrigue, Kiselyov Oleg Cc: avatar, jonathan.protzenko, OCaml Mailing List On 03/23/2014 01:59 PM, Jacques Garrigue wrote: > So well, unsound functions are unsound, and you have to live with that. > In particular _always_ annotate with ground types. It might be useful to check that the user provides an explicit type for the result of input_value and similar functions. This could be implemented in the same way as the new warning on deprecated declarations, i.e. with a compiler-recognized attribute on those special functions: val input_value: in_channel -> 'a [@@requires_explicit_type] The compiler would raise a warning if the call site does not provide explicit type information such as: let x : ... = input_value ic in ... or: let x = (input_value ic : x) in ... The notion of "explicit type information" could probably be the same as the one used for type propagation, or even a more syntactic criterion. -- Alain ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2014-03-24 8:46 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2014-03-22 20:02 [Caml-list] typechecking Misha Aizatulin 2014-03-22 20:40 ` Daniel Bünzli 2014-03-22 22:12 ` Yaron Minsky 2014-03-22 22:24 ` Jonathan Protzenko 2014-03-23 4:41 ` oleg 2014-03-23 12:59 ` Jacques Garrigue 2014-03-24 8:46 ` Alain Frisch
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox