caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] Type generalization confusion
@ 2017-05-08 18:32 Reed Wilson
  2017-05-09  3:49 ` Gabriel Scherer
  0 siblings, 1 reply; 8+ messages in thread
From: Reed Wilson @ 2017-05-08 18:32 UTC (permalink / raw)
  To: caml-list

[-- Attachment #1: Type: text/plain, Size: 1071 bytes --]

I've been working with some of the new features introduced with GADTs, and
ran into a confusing instance of the "type variables that cannot be
generalized" error.

The simplified program is as follows:
type _ field = Int : int -> 'a field
let a = Int 3
let b = Int (1 + 2)
let inside = 1 + 2
let c = Int inside

ocamlc -i returns:
type _ field = Int : int -> 'a field
val a : 'a field
val b : '_a field
val inside : int
val c : 'a field

with b remaining non-generalized. The problem I'm having with it is that
the type variable doesn't depend on the value at all, so I don't see how
that can prevent generalization.

Also, the manual says the reason some types aren't generalized is due to
"polymorphic mutable data structures". Nothing I created is mutable, so why
was generalization turned off in the first place?

Finally, I'm confused why separating the function from the definition is
enough to fix this; c is generalized simply by defining 1+2 in a separate
value (which must be global, apparently).

Thanks,
Reed Wilson

-- 
ç

[-- Attachment #2: Type: text/html, Size: 1483 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] Type generalization confusion
  2017-05-08 18:32 [Caml-list] Type generalization confusion Reed Wilson
@ 2017-05-09  3:49 ` Gabriel Scherer
  2017-05-09  5:43   ` Reed Wilson
  0 siblings, 1 reply; 8+ messages in thread
From: Gabriel Scherer @ 2017-05-09  3:49 UTC (permalink / raw)
  To: Reed Wilson; +Cc: caml users

This is the value restriction at work: only values are generalized
(often of the form "fun x -> ..."), and while (Int 3) is a value, (Int
(1 + 2)) is not.

On Mon, May 8, 2017 at 11:32 AM, Reed Wilson <cedilla@gmail.com> wrote:
> I've been working with some of the new features introduced with GADTs, and
> ran into a confusing instance of the "type variables that cannot be
> generalized" error.
>
> The simplified program is as follows:
> type _ field = Int : int -> 'a field
> let a = Int 3
> let b = Int (1 + 2)
> let inside = 1 + 2
> let c = Int inside
>
> ocamlc -i returns:
> type _ field = Int : int -> 'a field
> val a : 'a field
> val b : '_a field
> val inside : int
> val c : 'a field
>
> with b remaining non-generalized. The problem I'm having with it is that the
> type variable doesn't depend on the value at all, so I don't see how that
> can prevent generalization.
>
> Also, the manual says the reason some types aren't generalized is due to
> "polymorphic mutable data structures". Nothing I created is mutable, so why
> was generalization turned off in the first place?
>
> Finally, I'm confused why separating the function from the definition is
> enough to fix this; c is generalized simply by defining 1+2 in a separate
> value (which must be global, apparently).
>
> Thanks,
> Reed Wilson
>
> --
> ç

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] Type generalization confusion
  2017-05-09  3:49 ` Gabriel Scherer
@ 2017-05-09  5:43   ` Reed Wilson
  2017-05-09  8:54     ` Leo White
  0 siblings, 1 reply; 8+ messages in thread
From: Reed Wilson @ 2017-05-09  5:43 UTC (permalink / raw)
  To: Gabriel Scherer; +Cc: caml users

[-- Attachment #1: Type: text/plain, Size: 1770 bytes --]

But this similar type has no problem:

type 'a field = Int of int
let a = Int 3
let b = Int (1 + 2)

Both a and b receive the same generalized type. Why does the GADT style
prevent this from occuring?

On Mon, May 8, 2017 at 8:49 PM, Gabriel Scherer <gabriel.scherer@gmail.com>
wrote:

> This is the value restriction at work: only values are generalized
> (often of the form "fun x -> ..."), and while (Int 3) is a value, (Int
> (1 + 2)) is not.
>
> On Mon, May 8, 2017 at 11:32 AM, Reed Wilson <cedilla@gmail.com> wrote:
> > I've been working with some of the new features introduced with GADTs,
> and
> > ran into a confusing instance of the "type variables that cannot be
> > generalized" error.
> >
> > The simplified program is as follows:
> > type _ field = Int : int -> 'a field
> > let a = Int 3
> > let b = Int (1 + 2)
> > let inside = 1 + 2
> > let c = Int inside
> >
> > ocamlc -i returns:
> > type _ field = Int : int -> 'a field
> > val a : 'a field
> > val b : '_a field
> > val inside : int
> > val c : 'a field
> >
> > with b remaining non-generalized. The problem I'm having with it is that
> the
> > type variable doesn't depend on the value at all, so I don't see how that
> > can prevent generalization.
> >
> > Also, the manual says the reason some types aren't generalized is due to
> > "polymorphic mutable data structures". Nothing I created is mutable, so
> why
> > was generalization turned off in the first place?
> >
> > Finally, I'm confused why separating the function from the definition is
> > enough to fix this; c is generalized simply by defining 1+2 in a separate
> > value (which must be global, apparently).
> >
> > Thanks,
> > Reed Wilson
> >
> > --
> > ç
>



-- 
ç

[-- Attachment #2: Type: text/html, Size: 2575 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] Type generalization confusion
  2017-05-09  5:43   ` Reed Wilson
@ 2017-05-09  8:54     ` Leo White
  2017-05-09 19:56       ` Reed Wilson
  0 siblings, 1 reply; 8+ messages in thread
From: Leo White @ 2017-05-09  8:54 UTC (permalink / raw)
  To: caml-list

[-- Attachment #1: Type: text/plain, Size: 837 bytes --]

> But this similar type has no problem:
>  type 'a field = Int of int
>  let a = Int 3
>  let b = Int (1 + 2)

This is the relaxed value restriction: `field` is covariant in it's
parameter which means that the type variable is only used in
covariant positions and so can be generalized even though the
expression is not a value.
> Both a and b receive the same generalized type. Why does the GADT
> style prevent this from occuring?
 OCaml currently just assumes that types defined with GADT syntax are
 invariant because checking of variance with GADTs is more difficult. If
 you annotate your type as covariant then it behaves the same:
  type +_ field = Int : int -> 'a field
  let a = Int 3
  let b = Int (1 + 2)

gives:

  type +_ field = Int : int -> 'a field
  val a : 'a field = Int 3
  val b : 'a field = Int 3

Regards,

Leo



[-- Attachment #2: Type: text/html, Size: 1364 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] Type generalization confusion
  2017-05-09  8:54     ` Leo White
@ 2017-05-09 19:56       ` Reed Wilson
  2017-05-09 20:54         ` Jeremy Yallop
  2017-05-10 13:29         ` Mikhail Mandrykin
  0 siblings, 2 replies; 8+ messages in thread
From: Reed Wilson @ 2017-05-09 19:56 UTC (permalink / raw)
  To: Leo White; +Cc: caml-list

[-- Attachment #1: Type: text/plain, Size: 3338 bytes --]

Thanks for the info! I didn't think about just telling the type system
about the variance, and this kind of fix is exactly what I was (eventually)
getting at. Unfortunately, this solution doesn't seem to work in my actual
code.

I wrote a module to read and write binary data with fixed layouts, and I'm
using a GADT to enforce typing. A simplification of my actual code:

  type (_,_) field =
  | Int8 : ('a,'b) field -> ((int -> 'a),'b) field
  | String : (int * ('a,'b) field) -> ((string -> 'a),'b) field
  | End : ('b,'b) field

  let intstring = Int8 (String (3, End))

In this case, intstring would be used as a template for reading/writing an
8-bit integer, followed by a 3-byte long string. The type would be:
  val intstring : (int -> string -> 'a, 'a) field

My reading/writing functions are of the type:

  val to_chan : ('a, unit) field -> out_channel -> 'a
  val of_chan : ('a, 'b) field -> 'a -> in_channel -> 'b

This ensures that the arguments I pass are of the correct type for the
field. Passing intstring to the field parameter of the functions results in
a type:
  val intstring_to_chan : out_channel -> int -> string -> unit
  val intstring_of_chan : (int -> string -> '_a) -> in_channel -> '_a

(Note that the non-generalization of intstring_of_chan is not a problem
since I would always pass the reading function at the same time as the
field value.)

The current issue I'm actually facing is trying to create a lookup table
for these templates, which only differ by the length of some of the fields.
For example:

  let lookup_array = [| String (0, End); String (1, End) |]
  let lookup_fun i = String (i, End)

These end up being the following types:
  val lookup_array : (string -> '_a, '_a) field array
  val lookup_fun : int -> (string -> 'a, 'a) field

The lookup array is nongeneralized. The function works, but I'll be using
this in a very tight loop which is extremely resource-intensive. I wanted
to use a lookup table to see if that runs any faster, but I can't get it to
compile. (Even using Obj.magic didn't work)

No number of "+" or "-" in my type parameters fix the issue as they did
with the example in my previous e-mail. Everything returns:
  Error: In this GADT definition, the variance of some parameter cannot be
checked

Thanks again for your help! I think I'm a bit over my head with all this,
but I'd like to figure out as much as I can.


On May 9, 2017 01:55, "Leo White" <leo@lpw25.net> wrote:

> > But this similar type has no problem:
> >  type 'a field = Int of int
> >  let a = Int 3
> >  let b = Int (1 + 2)
>
> This is the relaxed value restriction: `field` is covariant in it's
> parameter which means that the type variable is only used in covariant
> positions and so can be generalized even though the expression is not a
> value.
>
> > Both a and b receive the same generalized type. Why does the GADT style
> prevent this from occuring?
>
> OCaml currently just assumes that types defined with GADT syntax are
> invariant because checking of variance with GADTs is more difficult. If you
> annotate your type as covariant then it behaves the same:
>
>   type +_ field = Int : int -> 'a field
>   let a = Int 3
>   let b = Int (1 + 2)
>
> gives:
>
>   type +_ field = Int : int -> 'a field
>   val a : 'a field = Int 3
>   val b : 'a field = Int 3
>
> Regards,
>
> Leo
>
>
>

[-- Attachment #2: Type: text/html, Size: 5404 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] Type generalization confusion
  2017-05-09 19:56       ` Reed Wilson
@ 2017-05-09 20:54         ` Jeremy Yallop
  2017-05-10 18:14           ` Reed Wilson
  2017-05-10 13:29         ` Mikhail Mandrykin
  1 sibling, 1 reply; 8+ messages in thread
From: Jeremy Yallop @ 2017-05-09 20:54 UTC (permalink / raw)
  To: Reed Wilson; +Cc: Leo White, Caml List

On 9 May 2017 at 20:56, Reed Wilson <cedilla@gmail.com> wrote:
>   type (_,_) field =
>   | Int8 : ('a,'b) field -> ((int -> 'a),'b) field
>   | String : (int * ('a,'b) field) -> ((string -> 'a),'b) field
>   | End : ('b,'b) field
[...]
> No number of "+" or "-" in my type parameters fix the issue as they did with
> the example in my previous e-mail. Everything returns:
>   Error: In this GADT definition, the variance of some parameter cannot be
> checked

Indeed, soundness requires the parameters of 'field' to be invariant,
so there's no possibility of adding variance annotations here.

For example, the type of the End constructor makes the following
definition well typed:

  let x : (< m : int >, < m : int >) field = End

If one of the parameters (the first, say) were covariant, then that
parameter could be coerced to a supertype of < m:int >, like this

  let y = (x :> ( < >, < m : int >) field)

and then matching against y would introduce an invalid equality
between < > and < m: int >:

  match y with
    End ->
      (* Here the type checker knows by the type of End that the
         two type parameters are equal, i.e. that < > is equal to < m : int > *)
    let x = (object end) in x#m + 1

A similar argument can be made against making either parameter
contravariant.  The parameters of 'field' are therefore invariant, and
so the relaxed value restriction doesn't apply.

> The current issue I'm actually facing is trying to create a lookup table for
> these templates, which only differ by the length of some of the fields. For
> example:
>
>   let lookup_array = [| String (0, End); String (1, End) |]

The value restriction prevents generalization here, since arrays are
mutable.  If bindings of array expressions were generalized then the
following program, which writes to the array at one type (int option)
and reads from the array at a different type (string option), would be
allowed.

   let array = [| None |] in                   (* val array : 'a option array *)
   let () = array.(0) <- Some 3 in
   let Some x = array.(0) in                (* val x : 'a *)
   "abc" ^ x

So it's essential for soundness that the types of array values are not
generalized.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] Type generalization confusion
  2017-05-09 19:56       ` Reed Wilson
  2017-05-09 20:54         ` Jeremy Yallop
@ 2017-05-10 13:29         ` Mikhail Mandrykin
  1 sibling, 0 replies; 8+ messages in thread
From: Mikhail Mandrykin @ 2017-05-10 13:29 UTC (permalink / raw)
  To: caml-list, Reed Wilson; +Cc: Leo White, Jeremy Yallop

On вторник, 9 мая 2017 г. 12:56:24 MSK Reed Wilson wrote:
> The current issue I'm actually facing is trying to create a lookup table
> for these templates, which only differ by the length of some of the fields.
> For example:
> 
>   let lookup_array = [| String (0, End); String (1, End) |]
>   let lookup_fun i = String (i, End)
Since what's needed here is actually not a generalized array type
( 'a. ((string -> 'a, 'a) field array) ),
but the generalized type of the element
( ('a. (string -> 'a, 'a) field) array ),
this could be work-around by introducing an intermediate universal type:

type (_, _) field =
  | Int8 : ('a, 'b) field -> ((int -> 'a),'b) field
  | String : int * ('a, 'b) field -> ((string -> 'a), 'b) field
  | End : ('b, 'b) field
type 'a lookup_param = { f : 'b. ('a -> 'b, 'b) field } [@@unboxed]
let lookup_array = [| { f = String (0, End) }; { f = String (1, End) } |]
let lookup i = match lookup_array.(i) with { f } -> f
let of_chan : ('a, 'b) field -> 'a -> in_channel -> 'b = fun _ _ _ -> assert 
false
let x = of_chan (lookup 0) (fun s -> int_of_string s) stdin
let y = of_chan (lookup 0) (fun s -> s) stdin

[@@unboxed] should have allowed this without any penalty of using an 
intermediate wrapper, but currently it fails:
Fatal error: exception File "bytecomp/typeopt.ml", line 97, characters 6-12: 
Assertion failed
(Typeopt.classify doesn't support universal types).
But even without [@@unboxed] this at least allows to avoid allocation of 
`String (i, End)` inside the loop.

--
Mikhail

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [Caml-list] Type generalization confusion
  2017-05-09 20:54         ` Jeremy Yallop
@ 2017-05-10 18:14           ` Reed Wilson
  0 siblings, 0 replies; 8+ messages in thread
From: Reed Wilson @ 2017-05-10 18:14 UTC (permalink / raw)
  To: Caml List; +Cc: Leo White, mandrykin, Jeremy Yallop

[-- Attachment #1: Type: text/plain, Size: 2856 bytes --]

On Tue, May 9, 2017 at 1:54 PM, Jeremy Yallop <yallop@gmail.com> wrote:

> On 9 May 2017 at 20:56, Reed Wilson <cedilla@gmail.com> wrote:
> > The current issue I'm actually facing is trying to create a lookup table
> for
> > these templates, which only differ by the length of some of the fields.
> For
> > example:
> >
> >   let lookup_array = [| String (0, End); String (1, End) |]
>
> The value restriction prevents generalization here, since arrays are
> mutable.  If bindings of array expressions were generalized then the
> following program, which writes to the array at one type (int option)
> and reads from the array at a different type (string option), would be
> allowed.
>
>    let array = [| None |] in                   (* val array : 'a option
> array *)
>    let () = array.(0) <- Some 3 in
>    let Some x = array.(0) in                (* val x : 'a *)
>    "abc" ^ x
>
> So it's essential for soundness that the types of array values are not
> generalized.
>

I suppose I should have known why the array didn't work, I just didn't
realize it since nothing I have defines a type that would have made that
unsoundness cause problems.


On Wed, May 10, 2017 at 6:29 AM, Mikhail Mandrykin <mandrykin@ispras.ru>
 wrote:

> type (_, _) field =
>   | Int8 : ('a, 'b) field -> ((int -> 'a),'b) field
>   | String : int * ('a, 'b) field -> ((string -> 'a), 'b) field
>   | End : ('b, 'b) field
> type 'a lookup_param = { f : 'b. ('a -> 'b, 'b) field } [@@unboxed]
> let lookup_array = [| { f = String (0, End) }; { f = String (1, End) } |]
> let lookup i = match lookup_array.(i) with { f } -> f
> let of_chan : ('a, 'b) field -> 'a -> in_channel -> 'b = fun _ _ _ ->
> assert
> false
> let x = of_chan (lookup 0) (fun s -> int_of_string s) stdin
> let y = of_chan (lookup 0) (fun s -> s) stdin
>

This is an interesting work-around. I had to tweak it a bit since it would
only work with templates that have one "field". For example:

  let lookup_array = [| {f = Int8 (String (0, End))} |]

fails with:

Error: This expression has type (int -> string -> 'a, 'a) field
       but an expression was expected of type
         (int -> string -> 'a, string -> 'a) field
       The type variable 'a occurs inside string -> 'a

Luckily, that's not a problem in my case since I only need this for one
field type, so I changed it to be a bit less general:

  type lookup_param = { f : 'b. (int -> string -> 'b, 'b) field }

And everything works fine. This will let me test the speed difference
between an extra pointer lookup vs. allocating ~15 words per loop for my
actual code. Honestly, my guess is that this is all academic since OCaml is
so good at small allocations, but I really wanted to know why things
weren't working.

Thanks again to everyone who responded!

-- 
ç

[-- Attachment #2: Type: text/html, Size: 4812 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2017-05-10 18:14 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-05-08 18:32 [Caml-list] Type generalization confusion Reed Wilson
2017-05-09  3:49 ` Gabriel Scherer
2017-05-09  5:43   ` Reed Wilson
2017-05-09  8:54     ` Leo White
2017-05-09 19:56       ` Reed Wilson
2017-05-09 20:54         ` Jeremy Yallop
2017-05-10 18:14           ` Reed Wilson
2017-05-10 13:29         ` Mikhail Mandrykin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).