caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] How is this type inferred (GADTs)
@ 2019-09-20  3:42 Malcolm Matalka
  2019-09-20  6:35 ` Gabriel Scherer
  2019-09-20 14:25 ` Oleg
  0 siblings, 2 replies; 6+ messages in thread
From: Malcolm Matalka @ 2019-09-20  3:42 UTC (permalink / raw)
  To: caml-list

I have been using GADTs to make type-safe versions of APIs that are kind
of like printf.  I've been using this pattern by rote and now I'm
getting to trying to understand how it works.

I have the following code:

module F : sig
  type ('f, 'r) t

  val z : ('r, 'r) t
  val (//) : ('f, 'a -> 'r) t -> (unit -> 'a) -> ('f, 'r) t
end = struct
  type ('f, 'r) t =
    | Z : ('r, 'r) t
    | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t

  let z = Z
  let (//) t f = S (t, f)
end

And the following usage:

utop # F.(z // (fun () -> Int32.zero) // (fun () -> ""));;
- : (int32 -> string -> '_weak1, '_weak1) F.t = <abstr>

I understand how 'r is '_weak1 (I think), but I'm still trying to figure
out how 'f gets its type by applying (//).  I think I have an
explanation below, and I'm hoping someone can say if it's correct and/or
simplify it.

Explanation:

The constructor Z says that 'f and 'r, are the same (Z : ('r, 'r) t).
The constructor S says that the (_, _) t that it takes has some type 'f,
but that the second type variable must be of the form 'a -> 'r, sort of
deconstructing pattern match on the type (if that makes sense).  And if
that (_, _) t is a Z, where we have stated the two type variables have
the same value, that means 'f must also be ('a -> 'r). So for the
first application of (//) it makes sense.  If we apply (//) again, the
first parameter has the type (int32 -> '_weak1, '_weak1) t, but that
must actually mean '_weak1 in this case is string -> '_weak, and then
this is where things become jumbled in my head.  If the passed in (_, _)
t must be (string -> '_weak), and the inner type actually must be (int32
-> '_weak), then, the inner inner type must be ('_weak), the type of 'r
at Z must be (string -> int32 -> '_weak), and since 'r, and 'f are the
same type for Z, 'f must also be (string -> int32 -> '_weak), and that
knowledge propagates back up?  But that doesn't feel quite right to me,
either.

With the help of reading other code, I've figured out how to make a
function that uses a type like this that works like kprintf, however
where I'm going in this case is that I want to take a function that
matches 'f and apply it to the result of my functions.

Something like:

  let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
    | Z ->
      f
    | S (t, v) ->
      bind (f (v ())) t

However, this does not compile given:

Error: This expression has type f
       This is not a function; it cannot be applied.

My understanding was if I have Z, and an input that has the type f, then
that is just the return type since at Z, f and r are the same type.  And
than at S, I can peel away the type of f by applying the function f and
then recursively calling.  But my understanding is missing something.

Does this make sense?

What am I not understanding?

Thank you,
/Malcolm

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

* Re: [Caml-list] How is this type inferred (GADTs)
  2019-09-20  3:42 [Caml-list] How is this type inferred (GADTs) Malcolm Matalka
@ 2019-09-20  6:35 ` Gabriel Scherer
  2019-09-20  8:11   ` Malcolm Matalka
  2019-09-20 14:25 ` Oleg
  1 sibling, 1 reply; 6+ messages in thread
From: Gabriel Scherer @ 2019-09-20  6:35 UTC (permalink / raw)
  To: Malcolm Matalka; +Cc: caml-list

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

Note: the reason why you get _weak type variables (which are not
polymorphic, just not-inferred-yet)  is that the type-checker cannot detect
that (z // (fun () -> ...)) is a value: it would have to unfold the call to
(//) for this, which it doesn't do in general, and here certainly could not
do given that its definition is hidden behind an abstraction boundary.
I would recommend experimenting *without* the module interface and the
auxiliary function, with the constructors directly, you would get slightly
better types.

# S(S(Z, (fun () -> Int32.zero)), (fun () -> ""));;
- : (int32 -> string -> 'a, 'a) t = S (S (Z, <fun>), <fun>)

Historically we have used module interfaces to implement "phantom types" --
because the type information there is only present in the interface, not in
the definition. With GADTs, the type constraints are built into the
definition itself, which is precisely what makes them more powerful; no
need for an abstract interface on top.

The first part of your question is about understanding how the
type-inference work (how variables are manipulated by the type-checker and
then "propagated back up"). This sounds like a difficult way to understand
GADTs: you want to learn the typing rules *and* the type-inference
algorithm at once. But only the first part is necessary to use the feature
productively (with a few tips on when to use annotations, which are easy to
explain and in fact explained in the manual:
http://caml.inria.fr/pub/docs/manual-ocaml/manual033.html ). So instead of
asking: "how did the compiler get this type?", I would ask: "why is this
the right type"? I think you could convince yourself that (1) it is a
correct type and (2) any other valid type would be a specialization of this
type, there is no simpler solution.

The second part: you wrote:

  let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
    | Z ->
      f
    | S (t, v) ->
      bind (f (v ())) t

Let's focus on the second clause:

    | S (t, v) ->
      bind (f (v ())) t

we know that (f : f) holds, and that the pattern-matching is on a value of
type (f, r) t, and we must return r.
When pattern-matching on S (t, v), we learn extra type information from the
typing rule of S:

    | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t

if r has type (f, r) t, then (t, v) has type ((f, a -> r) t * (unit -> a))
for *some* unknown/existential type a. So within the branch we have

  bind : type f r. f -> (f, r) t -> r
  f : (f, a -> r) t
  v : unit -> a
  expected return type: r

f does *not* have a function type here, so your idea of applying (f (v ()))
cannot work (v does have a function type, so (v ()) is valid).

The only thing you can do on f is call (bind) recursively (with what
arguments?), and then you will get an (a -> r) as result.

Do you see how to write a correct program from there?



On Fri, Sep 20, 2019 at 5:42 AM Malcolm Matalka <mmatalka@gmail.com> wrote:

> I have been using GADTs to make type-safe versions of APIs that are kind
> of like printf.  I've been using this pattern by rote and now I'm
> getting to trying to understand how it works.
>
> I have the following code:
>
> module F : sig
>   type ('f, 'r) t
>
>   val z : ('r, 'r) t
>   val (//) : ('f, 'a -> 'r) t -> (unit -> 'a) -> ('f, 'r) t
> end = struct
>   type ('f, 'r) t =
>     | Z : ('r, 'r) t
>     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
>
>   let z = Z
>   let (//) t f = S (t, f)
> end
>
> And the following usage:
>
> utop # F.(z // (fun () -> Int32.zero) // (fun () -> ""));;
> - : (int32 -> string -> '_weak1, '_weak1) F.t = <abstr>
>
> I understand how 'r is '_weak1 (I think), but I'm still trying to figure
> out how 'f gets its type by applying (//).  I think I have an
> explanation below, and I'm hoping someone can say if it's correct and/or
> simplify it.
>
> Explanation:
>
> The constructor Z says that 'f and 'r, are the same (Z : ('r, 'r) t).
> The constructor S says that the (_, _) t that it takes has some type 'f,
> but that the second type variable must be of the form 'a -> 'r, sort of
> deconstructing pattern match on the type (if that makes sense).  And if
> that (_, _) t is a Z, where we have stated the two type variables have
> the same value, that means 'f must also be ('a -> 'r). So for the
> first application of (//) it makes sense.  If we apply (//) again, the
> first parameter has the type (int32 -> '_weak1, '_weak1) t, but that
> must actually mean '_weak1 in this case is string -> '_weak, and then
> this is where things become jumbled in my head.  If the passed in (_, _)
> t must be (string -> '_weak), and the inner type actually must be (int32
> -> '_weak), then, the inner inner type must be ('_weak), the type of 'r
> at Z must be (string -> int32 -> '_weak), and since 'r, and 'f are the
> same type for Z, 'f must also be (string -> int32 -> '_weak), and that
> knowledge propagates back up?  But that doesn't feel quite right to me,
> either.
>
> With the help of reading other code, I've figured out how to make a
> function that uses a type like this that works like kprintf, however
> where I'm going in this case is that I want to take a function that
> matches 'f and apply it to the result of my functions.
>
> Something like:
>
>   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
>     | Z ->
>       f
>     | S (t, v) ->
>       bind (f (v ())) t
>
> However, this does not compile given:
>
> Error: This expression has type f
>        This is not a function; it cannot be applied.
>
> My understanding was if I have Z, and an input that has the type f, then
> that is just the return type since at Z, f and r are the same type.  And
> than at S, I can peel away the type of f by applying the function f and
> then recursively calling.  But my understanding is missing something.
>
> Does this make sense?
>
> What am I not understanding?
>
> Thank you,
> /Malcolm
>

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

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

* Re: [Caml-list] How is this type inferred (GADTs)
  2019-09-20  6:35 ` Gabriel Scherer
@ 2019-09-20  8:11   ` Malcolm Matalka
  2019-09-20  8:35     ` Gabriel Scherer
  0 siblings, 1 reply; 6+ messages in thread
From: Malcolm Matalka @ 2019-09-20  8:11 UTC (permalink / raw)
  To: Gabriel Scherer; +Cc: caml-list


Gabriel Scherer <gabriel.scherer@gmail.com> writes:

> Note: the reason why you get _weak type variables (which are not
> polymorphic, just not-inferred-yet)  is that the type-checker cannot detect
> that (z // (fun () -> ...)) is a value: it would have to unfold the call to
> (//) for this, which it doesn't do in general, and here certainly could not
> do given that its definition is hidden behind an abstraction boundary.
> I would recommend experimenting *without* the module interface and the
> auxiliary function, with the constructors directly, you would get slightly
> better types.
>
> # S(S(Z, (fun () -> Int32.zero)), (fun () -> ""));;
> - : (int32 -> string -> 'a, 'a) t = S (S (Z, <fun>), <fun>)
>
> Historically we have used module interfaces to implement "phantom types" --
> because the type information there is only present in the interface, not in
> the definition. With GADTs, the type constraints are built into the
> definition itself, which is precisely what makes them more powerful; no
> need for an abstract interface on top.
>
> The first part of your question is about understanding how the
> type-inference work (how variables are manipulated by the type-checker and
> then "propagated back up"). This sounds like a difficult way to understand
> GADTs: you want to learn the typing rules *and* the type-inference
> algorithm at once. But only the first part is necessary to use the feature
> productively (with a few tips on when to use annotations, which are easy to
> explain and in fact explained in the manual:
> http://caml.inria.fr/pub/docs/manual-ocaml/manual033.html ). So instead of
> asking: "how did the compiler get this type?", I would ask: "why is this
> the right type"? I think you could convince yourself that (1) it is a
> correct type and (2) any other valid type would be a specialization of this
> type, there is no simpler solution.
>
> The second part: you wrote:
>
>   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
>     | Z ->
>       f
>     | S (t, v) ->
>       bind (f (v ())) t
>
> Let's focus on the second clause:
>
>     | S (t, v) ->
>       bind (f (v ())) t
>
> we know that (f : f) holds, and that the pattern-matching is on a value of
> type (f, r) t, and we must return r.
> When pattern-matching on S (t, v), we learn extra type information from the
> typing rule of S:
>
>     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
>
> if r has type (f, r) t, then (t, v) has type ((f, a -> r) t * (unit -> a))
> for *some* unknown/existential type a. So within the branch we have
>
>   bind : type f r. f -> (f, r) t -> r
>   f : (f, a -> r) t
>   v : unit -> a
>   expected return type: r
>
> f does *not* have a function type here, so your idea of applying (f (v ()))
> cannot work (v does have a function type, so (v ()) is valid).
>
> The only thing you can do on f is call (bind) recursively (with what
> arguments?), and then you will get an (a -> r) as result.
>
> Do you see how to write a correct program from there?

Ah-ha!  I was close but my thinking was at the "wrong level" (I'm not
sure how to put it).  The working implementation of bind is:

  let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
    | Z ->
      f
    | S (t, v) ->
      let f' = bind f t in
      f' (v ())

And now I see why you wanted me to look at it not through the module
interface.  Looking at how the recursion would work, of course the
most-nested S will have the function corresponding to the first
parameter of the function.  I was thinking that I would consume the
parameters on the way down, but it's actually on the way up.

I am still working out in my head the answer to "why is this the right
type", I think it has to slosh around in there a bit before it truly
makes sense to me.

Thank you!

>
>
>
> On Fri, Sep 20, 2019 at 5:42 AM Malcolm Matalka <mmatalka@gmail.com> wrote:
>
>> I have been using GADTs to make type-safe versions of APIs that are kind
>> of like printf.  I've been using this pattern by rote and now I'm
>> getting to trying to understand how it works.
>>
>> I have the following code:
>>
>> module F : sig
>>   type ('f, 'r) t
>>
>>   val z : ('r, 'r) t
>>   val (//) : ('f, 'a -> 'r) t -> (unit -> 'a) -> ('f, 'r) t
>> end = struct
>>   type ('f, 'r) t =
>>     | Z : ('r, 'r) t
>>     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
>>
>>   let z = Z
>>   let (//) t f = S (t, f)
>> end
>>
>> And the following usage:
>>
>> utop # F.(z // (fun () -> Int32.zero) // (fun () -> ""));;
>> - : (int32 -> string -> '_weak1, '_weak1) F.t = <abstr>
>>
>> I understand how 'r is '_weak1 (I think), but I'm still trying to figure
>> out how 'f gets its type by applying (//).  I think I have an
>> explanation below, and I'm hoping someone can say if it's correct and/or
>> simplify it.
>>
>> Explanation:
>>
>> The constructor Z says that 'f and 'r, are the same (Z : ('r, 'r) t).
>> The constructor S says that the (_, _) t that it takes has some type 'f,
>> but that the second type variable must be of the form 'a -> 'r, sort of
>> deconstructing pattern match on the type (if that makes sense).  And if
>> that (_, _) t is a Z, where we have stated the two type variables have
>> the same value, that means 'f must also be ('a -> 'r). So for the
>> first application of (//) it makes sense.  If we apply (//) again, the
>> first parameter has the type (int32 -> '_weak1, '_weak1) t, but that
>> must actually mean '_weak1 in this case is string -> '_weak, and then
>> this is where things become jumbled in my head.  If the passed in (_, _)
>> t must be (string -> '_weak), and the inner type actually must be (int32
>> -> '_weak), then, the inner inner type must be ('_weak), the type of 'r
>> at Z must be (string -> int32 -> '_weak), and since 'r, and 'f are the
>> same type for Z, 'f must also be (string -> int32 -> '_weak), and that
>> knowledge propagates back up?  But that doesn't feel quite right to me,
>> either.
>>
>> With the help of reading other code, I've figured out how to make a
>> function that uses a type like this that works like kprintf, however
>> where I'm going in this case is that I want to take a function that
>> matches 'f and apply it to the result of my functions.
>>
>> Something like:
>>
>>   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
>>     | Z ->
>>       f
>>     | S (t, v) ->
>>       bind (f (v ())) t
>>
>> However, this does not compile given:
>>
>> Error: This expression has type f
>>        This is not a function; it cannot be applied.
>>
>> My understanding was if I have Z, and an input that has the type f, then
>> that is just the return type since at Z, f and r are the same type.  And
>> than at S, I can peel away the type of f by applying the function f and
>> then recursively calling.  But my understanding is missing something.
>>
>> Does this make sense?
>>
>> What am I not understanding?
>>
>> Thank you,
>> /Malcolm
>>


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

* Re: [Caml-list] How is this type inferred (GADTs)
  2019-09-20  8:11   ` Malcolm Matalka
@ 2019-09-20  8:35     ` Gabriel Scherer
  2019-09-20  8:56       ` Gabriel Scherer
  0 siblings, 1 reply; 6+ messages in thread
From: Gabriel Scherer @ 2019-09-20  8:35 UTC (permalink / raw)
  To: Malcolm Matalka; +Cc: caml-list

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

There is a direct relation between how type evolve during GADT typing and
how queries evolve during logic/relational programming. It can help to
think of a GADT declaration as the set of rules of a Prolog program. (I you
have never looked at Prolog, here is an occasion to learn something new).
That does *not* mean that GADTs let you do prolog-style programming in
types (the compiler will not automatically search for a proof, the user has
to write it down explicitly as a GADT term, so this work is not done
implicitly, as with type-class search for example), but it does give a way
to think about GADT declarations.

Your declaration (notice that I renamed the Z variable, for a good reason
that will appear clear below):

  type (_, _) t =
    | Z : ('f, 'f) t
    | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t

becomes

  t F F.
  t F R :- t F (A -> R), (unit -> A).

In any case, your declaration has the following effect:
- by the Z rule, all types of the form
  ('a1 -> 'a2 -> ... -> 'an -> 'r, 'a -> 'a2 -> ... -> 'an -> 'r) t
  are inhabited
- by the S rule, you can "hide" an element on the right
  (and you have to provide a thunk for it)
, moving from
  ('a1 -> ... -> 'an -> r, 'ak -> 'a{k+1} -> .. -> 'an -> r) t
  to
  ('a1 -> ... -> 'an -> r, 'a{k+1} -> .. -> 'an -> r) t

If you do this repeatedly you end up on what you want: ('a1 -> .. -> 'an ->
r, r) t.

The way you write about it, it sounds like you had the following
*different* definition in mind

type (_, _) t =
| Z : ('r, 'r) t
| S : ('f, 'r) t * (unit -> 'a) -> ('a -> 'f, 'r) t

let test = S (S (Z, (fun () -> 3l)), (fun () -> "foo"))

corresponding to the following Prolog program that I do find easier to
think about:

  t R R.
  t (A -> F) R :- t F R, (unit -> A).

It turns out that with this other definition, the first implementation that
you had written actually type-check fines.

(You could try to write conversion functions between those two definitions
to convince yourself that they are morally equivalent, but this is actually
a difficult exercise.)

On Fri, Sep 20, 2019 at 10:11 AM Malcolm Matalka <mmatalka@gmail.com> wrote:

>
> Gabriel Scherer <gabriel.scherer@gmail.com> writes:
>
> > Note: the reason why you get _weak type variables (which are not
> > polymorphic, just not-inferred-yet)  is that the type-checker cannot
> detect
> > that (z // (fun () -> ...)) is a value: it would have to unfold the call
> to
> > (//) for this, which it doesn't do in general, and here certainly could
> not
> > do given that its definition is hidden behind an abstraction boundary.
> > I would recommend experimenting *without* the module interface and the
> > auxiliary function, with the constructors directly, you would get
> slightly
> > better types.
> >
> > # S(S(Z, (fun () -> Int32.zero)), (fun () -> ""));;
> > - : (int32 -> string -> 'a, 'a) t = S (S (Z, <fun>), <fun>)
> >
> > Historically we have used module interfaces to implement "phantom types"
> --
> > because the type information there is only present in the interface, not
> in
> > the definition. With GADTs, the type constraints are built into the
> > definition itself, which is precisely what makes them more powerful; no
> > need for an abstract interface on top.
> >
> > The first part of your question is about understanding how the
> > type-inference work (how variables are manipulated by the type-checker
> and
> > then "propagated back up"). This sounds like a difficult way to
> understand
> > GADTs: you want to learn the typing rules *and* the type-inference
> > algorithm at once. But only the first part is necessary to use the
> feature
> > productively (with a few tips on when to use annotations, which are easy
> to
> > explain and in fact explained in the manual:
> > http://caml.inria.fr/pub/docs/manual-ocaml/manual033.html ). So instead
> of
> > asking: "how did the compiler get this type?", I would ask: "why is this
> > the right type"? I think you could convince yourself that (1) it is a
> > correct type and (2) any other valid type would be a specialization of
> this
> > type, there is no simpler solution.
> >
> > The second part: you wrote:
> >
> >   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
> >     | Z ->
> >       f
> >     | S (t, v) ->
> >       bind (f (v ())) t
> >
> > Let's focus on the second clause:
> >
> >     | S (t, v) ->
> >       bind (f (v ())) t
> >
> > we know that (f : f) holds, and that the pattern-matching is on a value
> of
> > type (f, r) t, and we must return r.
> > When pattern-matching on S (t, v), we learn extra type information from
> the
> > typing rule of S:
> >
> >     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
> >
> > if r has type (f, r) t, then (t, v) has type ((f, a -> r) t * (unit ->
> a))
> > for *some* unknown/existential type a. So within the branch we have
> >
> >   bind : type f r. f -> (f, r) t -> r
> >   f : (f, a -> r) t
> >   v : unit -> a
> >   expected return type: r
> >
> > f does *not* have a function type here, so your idea of applying (f (v
> ()))
> > cannot work (v does have a function type, so (v ()) is valid).
> >
> > The only thing you can do on f is call (bind) recursively (with what
> > arguments?), and then you will get an (a -> r) as result.
> >
> > Do you see how to write a correct program from there?
>
> Ah-ha!  I was close but my thinking was at the "wrong level" (I'm not
> sure how to put it).  The working implementation of bind is:
>
>   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
>     | Z ->
>       f
>     | S (t, v) ->
>       let f' = bind f t in
>       f' (v ())
>
> And now I see why you wanted me to look at it not through the module
> interface.  Looking at how the recursion would work, of course the
> most-nested S will have the function corresponding to the first
> parameter of the function.  I was thinking that I would consume the
> parameters on the way down, but it's actually on the way up.
>
> I am still working out in my head the answer to "why is this the right
> type", I think it has to slosh around in there a bit before it truly
> makes sense to me.
>
> Thank you!
>
> >
> >
> >
> > On Fri, Sep 20, 2019 at 5:42 AM Malcolm Matalka <mmatalka@gmail.com>
> wrote:
> >
> >> I have been using GADTs to make type-safe versions of APIs that are kind
> >> of like printf.  I've been using this pattern by rote and now I'm
> >> getting to trying to understand how it works.
> >>
> >> I have the following code:
> >>
> >> module F : sig
> >>   type ('f, 'r) t
> >>
> >>   val z : ('r, 'r) t
> >>   val (//) : ('f, 'a -> 'r) t -> (unit -> 'a) -> ('f, 'r) t
> >> end = struct
> >>   type ('f, 'r) t =
> >>     | Z : ('r, 'r) t
> >>     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
> >>
> >>   let z = Z
> >>   let (//) t f = S (t, f)
> >> end
> >>
> >> And the following usage:
> >>
> >> utop # F.(z // (fun () -> Int32.zero) // (fun () -> ""));;
> >> - : (int32 -> string -> '_weak1, '_weak1) F.t = <abstr>
> >>
> >> I understand how 'r is '_weak1 (I think), but I'm still trying to figure
> >> out how 'f gets its type by applying (//).  I think I have an
> >> explanation below, and I'm hoping someone can say if it's correct and/or
> >> simplify it.
> >>
> >> Explanation:
> >>
> >> The constructor Z says that 'f and 'r, are the same (Z : ('r, 'r) t).
> >> The constructor S says that the (_, _) t that it takes has some type 'f,
> >> but that the second type variable must be of the form 'a -> 'r, sort of
> >> deconstructing pattern match on the type (if that makes sense).  And if
> >> that (_, _) t is a Z, where we have stated the two type variables have
> >> the same value, that means 'f must also be ('a -> 'r). So for the
> >> first application of (//) it makes sense.  If we apply (//) again, the
> >> first parameter has the type (int32 -> '_weak1, '_weak1) t, but that
> >> must actually mean '_weak1 in this case is string -> '_weak, and then
> >> this is where things become jumbled in my head.  If the passed in (_, _)
> >> t must be (string -> '_weak), and the inner type actually must be (int32
> >> -> '_weak), then, the inner inner type must be ('_weak), the type of 'r
> >> at Z must be (string -> int32 -> '_weak), and since 'r, and 'f are the
> >> same type for Z, 'f must also be (string -> int32 -> '_weak), and that
> >> knowledge propagates back up?  But that doesn't feel quite right to me,
> >> either.
> >>
> >> With the help of reading other code, I've figured out how to make a
> >> function that uses a type like this that works like kprintf, however
> >> where I'm going in this case is that I want to take a function that
> >> matches 'f and apply it to the result of my functions.
> >>
> >> Something like:
> >>
> >>   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
> >>     | Z ->
> >>       f
> >>     | S (t, v) ->
> >>       bind (f (v ())) t
> >>
> >> However, this does not compile given:
> >>
> >> Error: This expression has type f
> >>        This is not a function; it cannot be applied.
> >>
> >> My understanding was if I have Z, and an input that has the type f, then
> >> that is just the return type since at Z, f and r are the same type.  And
> >> than at S, I can peel away the type of f by applying the function f and
> >> then recursively calling.  But my understanding is missing something.
> >>
> >> Does this make sense?
> >>
> >> What am I not understanding?
> >>
> >> Thank you,
> >> /Malcolm
> >>
>
>

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

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

* Re: [Caml-list] How is this type inferred (GADTs)
  2019-09-20  8:35     ` Gabriel Scherer
@ 2019-09-20  8:56       ` Gabriel Scherer
  0 siblings, 0 replies; 6+ messages in thread
From: Gabriel Scherer @ 2019-09-20  8:56 UTC (permalink / raw)
  To: Malcolm Matalka; +Cc: caml-list

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

> (You could try to write conversion functions between those two definitions
> to convince yourself that they are morally equivalent,
> but this is actually a difficult exercise.)

type (_, _) t1 =
| Z : ('f, 'f) t1
| S : (('f, 'a -> 'r) t1 * (unit -> 'a)) -> ('f, 'r) t1

type (_, _) t2 =
| Z : ('r, 'r) t2
| S : ('f, 'r) t2 * (unit -> 'a) -> ('a -> 'f, 'r) t2

t2 is t1 "upside down" (and conversely). To go from t1 to t2 is thus a
"reverse" operation.
Just like for lists, writing "reverse" directly is difficult, but one can
use an auxiliary function

let rec rev_append
  : type a b c. (b, c) t2 -> (a, b) t1 -> (a, c) t2


On Fri, Sep 20, 2019 at 10:35 AM Gabriel Scherer <gabriel.scherer@gmail.com>
wrote:

> There is a direct relation between how type evolve during GADT typing and
> how queries evolve during logic/relational programming. It can help to
> think of a GADT declaration as the set of rules of a Prolog program. (I you
> have never looked at Prolog, here is an occasion to learn something new).
> That does *not* mean that GADTs let you do prolog-style programming in
> types (the compiler will not automatically search for a proof, the user has
> to write it down explicitly as a GADT term, so this work is not done
> implicitly, as with type-class search for example), but it does give a way
> to think about GADT declarations.
>
> Your declaration (notice that I renamed the Z variable, for a good reason
> that will appear clear below):
>
>   type (_, _) t =
>     | Z : ('f, 'f) t
>     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
>
> becomes
>
>   t F F.
>   t F R :- t F (A -> R), (unit -> A).
>
> In any case, your declaration has the following effect:
> - by the Z rule, all types of the form
>   ('a1 -> 'a2 -> ... -> 'an -> 'r, 'a -> 'a2 -> ... -> 'an -> 'r) t
>   are inhabited
> - by the S rule, you can "hide" an element on the right
>   (and you have to provide a thunk for it)
> , moving from
>   ('a1 -> ... -> 'an -> r, 'ak -> 'a{k+1} -> .. -> 'an -> r) t
>   to
>   ('a1 -> ... -> 'an -> r, 'a{k+1} -> .. -> 'an -> r) t
>
> If you do this repeatedly you end up on what you want: ('a1 -> .. -> 'an
> -> r, r) t.
>
> The way you write about it, it sounds like you had the following
> *different* definition in mind
>
> type (_, _) t =
> | Z : ('r, 'r) t
> | S : ('f, 'r) t * (unit -> 'a) -> ('a -> 'f, 'r) t
>
> let test = S (S (Z, (fun () -> 3l)), (fun () -> "foo"))
>
> corresponding to the following Prolog program that I do find easier to
> think about:
>
>   t R R.
>   t (A -> F) R :- t F R, (unit -> A).
>
> It turns out that with this other definition, the first implementation
> that you had written actually type-check fines.
>
> (You could try to write conversion functions between those two definitions
> to convince yourself that they are morally equivalent, but this is actually
> a difficult exercise.)
>
> On Fri, Sep 20, 2019 at 10:11 AM Malcolm Matalka <mmatalka@gmail.com>
> wrote:
>
>>
>> Gabriel Scherer <gabriel.scherer@gmail.com> writes:
>>
>> > Note: the reason why you get _weak type variables (which are not
>> > polymorphic, just not-inferred-yet)  is that the type-checker cannot
>> detect
>> > that (z // (fun () -> ...)) is a value: it would have to unfold the
>> call to
>> > (//) for this, which it doesn't do in general, and here certainly could
>> not
>> > do given that its definition is hidden behind an abstraction boundary.
>> > I would recommend experimenting *without* the module interface and the
>> > auxiliary function, with the constructors directly, you would get
>> slightly
>> > better types.
>> >
>> > # S(S(Z, (fun () -> Int32.zero)), (fun () -> ""));;
>> > - : (int32 -> string -> 'a, 'a) t = S (S (Z, <fun>), <fun>)
>> >
>> > Historically we have used module interfaces to implement "phantom
>> types" --
>> > because the type information there is only present in the interface,
>> not in
>> > the definition. With GADTs, the type constraints are built into the
>> > definition itself, which is precisely what makes them more powerful; no
>> > need for an abstract interface on top.
>> >
>> > The first part of your question is about understanding how the
>> > type-inference work (how variables are manipulated by the type-checker
>> and
>> > then "propagated back up"). This sounds like a difficult way to
>> understand
>> > GADTs: you want to learn the typing rules *and* the type-inference
>> > algorithm at once. But only the first part is necessary to use the
>> feature
>> > productively (with a few tips on when to use annotations, which are
>> easy to
>> > explain and in fact explained in the manual:
>> > http://caml.inria.fr/pub/docs/manual-ocaml/manual033.html ). So
>> instead of
>> > asking: "how did the compiler get this type?", I would ask: "why is this
>> > the right type"? I think you could convince yourself that (1) it is a
>> > correct type and (2) any other valid type would be a specialization of
>> this
>> > type, there is no simpler solution.
>> >
>> > The second part: you wrote:
>> >
>> >   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
>> >     | Z ->
>> >       f
>> >     | S (t, v) ->
>> >       bind (f (v ())) t
>> >
>> > Let's focus on the second clause:
>> >
>> >     | S (t, v) ->
>> >       bind (f (v ())) t
>> >
>> > we know that (f : f) holds, and that the pattern-matching is on a value
>> of
>> > type (f, r) t, and we must return r.
>> > When pattern-matching on S (t, v), we learn extra type information from
>> the
>> > typing rule of S:
>> >
>> >     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
>> >
>> > if r has type (f, r) t, then (t, v) has type ((f, a -> r) t * (unit ->
>> a))
>> > for *some* unknown/existential type a. So within the branch we have
>> >
>> >   bind : type f r. f -> (f, r) t -> r
>> >   f : (f, a -> r) t
>> >   v : unit -> a
>> >   expected return type: r
>> >
>> > f does *not* have a function type here, so your idea of applying (f (v
>> ()))
>> > cannot work (v does have a function type, so (v ()) is valid).
>> >
>> > The only thing you can do on f is call (bind) recursively (with what
>> > arguments?), and then you will get an (a -> r) as result.
>> >
>> > Do you see how to write a correct program from there?
>>
>> Ah-ha!  I was close but my thinking was at the "wrong level" (I'm not
>> sure how to put it).  The working implementation of bind is:
>>
>>   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
>>     | Z ->
>>       f
>>     | S (t, v) ->
>>       let f' = bind f t in
>>       f' (v ())
>>
>> And now I see why you wanted me to look at it not through the module
>> interface.  Looking at how the recursion would work, of course the
>> most-nested S will have the function corresponding to the first
>> parameter of the function.  I was thinking that I would consume the
>> parameters on the way down, but it's actually on the way up.
>>
>> I am still working out in my head the answer to "why is this the right
>> type", I think it has to slosh around in there a bit before it truly
>> makes sense to me.
>>
>> Thank you!
>>
>> >
>> >
>> >
>> > On Fri, Sep 20, 2019 at 5:42 AM Malcolm Matalka <mmatalka@gmail.com>
>> wrote:
>> >
>> >> I have been using GADTs to make type-safe versions of APIs that are
>> kind
>> >> of like printf.  I've been using this pattern by rote and now I'm
>> >> getting to trying to understand how it works.
>> >>
>> >> I have the following code:
>> >>
>> >> module F : sig
>> >>   type ('f, 'r) t
>> >>
>> >>   val z : ('r, 'r) t
>> >>   val (//) : ('f, 'a -> 'r) t -> (unit -> 'a) -> ('f, 'r) t
>> >> end = struct
>> >>   type ('f, 'r) t =
>> >>     | Z : ('r, 'r) t
>> >>     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
>> >>
>> >>   let z = Z
>> >>   let (//) t f = S (t, f)
>> >> end
>> >>
>> >> And the following usage:
>> >>
>> >> utop # F.(z // (fun () -> Int32.zero) // (fun () -> ""));;
>> >> - : (int32 -> string -> '_weak1, '_weak1) F.t = <abstr>
>> >>
>> >> I understand how 'r is '_weak1 (I think), but I'm still trying to
>> figure
>> >> out how 'f gets its type by applying (//).  I think I have an
>> >> explanation below, and I'm hoping someone can say if it's correct
>> and/or
>> >> simplify it.
>> >>
>> >> Explanation:
>> >>
>> >> The constructor Z says that 'f and 'r, are the same (Z : ('r, 'r) t).
>> >> The constructor S says that the (_, _) t that it takes has some type
>> 'f,
>> >> but that the second type variable must be of the form 'a -> 'r, sort of
>> >> deconstructing pattern match on the type (if that makes sense).  And if
>> >> that (_, _) t is a Z, where we have stated the two type variables have
>> >> the same value, that means 'f must also be ('a -> 'r). So for the
>> >> first application of (//) it makes sense.  If we apply (//) again, the
>> >> first parameter has the type (int32 -> '_weak1, '_weak1) t, but that
>> >> must actually mean '_weak1 in this case is string -> '_weak, and then
>> >> this is where things become jumbled in my head.  If the passed in (_,
>> _)
>> >> t must be (string -> '_weak), and the inner type actually must be
>> (int32
>> >> -> '_weak), then, the inner inner type must be ('_weak), the type of 'r
>> >> at Z must be (string -> int32 -> '_weak), and since 'r, and 'f are the
>> >> same type for Z, 'f must also be (string -> int32 -> '_weak), and that
>> >> knowledge propagates back up?  But that doesn't feel quite right to me,
>> >> either.
>> >>
>> >> With the help of reading other code, I've figured out how to make a
>> >> function that uses a type like this that works like kprintf, however
>> >> where I'm going in this case is that I want to take a function that
>> >> matches 'f and apply it to the result of my functions.
>> >>
>> >> Something like:
>> >>
>> >>   let rec bind : type f r. f -> (f, r) t -> r = fun f -> function
>> >>     | Z ->
>> >>       f
>> >>     | S (t, v) ->
>> >>       bind (f (v ())) t
>> >>
>> >> However, this does not compile given:
>> >>
>> >> Error: This expression has type f
>> >>        This is not a function; it cannot be applied.
>> >>
>> >> My understanding was if I have Z, and an input that has the type f,
>> then
>> >> that is just the return type since at Z, f and r are the same type.
>> And
>> >> than at S, I can peel away the type of f by applying the function f and
>> >> then recursively calling.  But my understanding is missing something.
>> >>
>> >> Does this make sense?
>> >>
>> >> What am I not understanding?
>> >>
>> >> Thank you,
>> >> /Malcolm
>> >>
>>
>>

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

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

* Re: [Caml-list] How is this type inferred (GADTs)
  2019-09-20  3:42 [Caml-list] How is this type inferred (GADTs) Malcolm Matalka
  2019-09-20  6:35 ` Gabriel Scherer
@ 2019-09-20 14:25 ` Oleg
  1 sibling, 0 replies; 6+ messages in thread
From: Oleg @ 2019-09-20 14:25 UTC (permalink / raw)
  To: mmatalka; +Cc: caml-list


Gabriel has given an excellent explanation, it is hard to add
anything. Yet I will still try.

> module F : sig
>   type ('f, 'r) t
>   val z : ('r, 'r) t
>   val (//) : ('f, 'a -> 'r) t -> (unit -> 'a) -> ('f, 'r) t
> end = struct
>   type ('f, 'r) t =
>     | Z : ('r, 'r) t
>     | S : (('f, 'a -> 'r) t * (unit -> 'a)) -> ('f, 'r) t
>   let z = Z
>   let (//) t f = S (t, f)
> end

Perhaps it will be easier to work out the types if we don't use
GADTs. They are not necessary in your example (and not needed for
the typed printf either).

Here is the adjusted code

module F : sig
  type ('f, 'r) t

  val z : ('r, 'r) t
  val (//) : ('f, 'a -> 'r) t -> (unit -> 'a) -> ('f, 'r) t

  val bind : 'f -> ('f, 'r) t -> 'r
 end = struct
  type ('f, 'r) t = 'f -> 'r

  let z = fun x -> x
  (* the type annotations are all optional. They are given to show
     which subexpressions have which types, and let the compiler confirm
     that.
     Alas, attaching type annotations requires too many parentheses
  *)  
  let (//) : type a r f. (f, a -> r) t -> (unit -> a) -> (f, r) t =
    fun t th ->
      fun (f:f) -> ((((t f):(a->r)) (th () : a)) : r)

  let bind f t = t f
end


let exp1 = F.(z // (fun () -> Int32.zero) // (fun () -> ""));;
(*
  val exp1 : (int32 -> string -> '_weak1, '_weak1) F.t = <abstr>
*)

Just for reference, here is the implementation for the opposite order,
suggested by Gabriel.

module G : sig
  type ('f, 'r) t

  val z : ('r, 'r) t
  val (//) : ('f, 'r) t -> (unit -> 'a) -> ('a -> 'f, 'r) t

  val bind : 'f -> ('f, 'r) t -> 'r
 end = struct
  type ('f, 'r) t = {e: 'w. ('r -> 'w) -> ('f -> 'w)}

  let z = {e = fun x -> x}
  (* the type annotations are all optional. They are given to show
     which subexpressions have which types, and let the compiler confirm
     that
     Alas, attaching type annotations requires too many parentheses
  *)  
  let (//) : type a r f. (f, r) t -> (unit -> a) -> (a->f, r) t =
    fun t th ->
      {e = fun (type w) (k:(r->w)) -> fun (af:a->f) ->
        ((((t.e k):(f -> w)) ((af (th ())):f)):w)}

  let bind f t = t.e (fun x -> x) f
end

let test = G.(z // (fun () -> 3l) // (fun () -> "foo"));;
(* val test : (string -> int32 -> '_weak2, '_weak2) G.t = <abstr> *)

;;


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

end of thread, other threads:[~2019-09-20 14:23 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-09-20  3:42 [Caml-list] How is this type inferred (GADTs) Malcolm Matalka
2019-09-20  6:35 ` Gabriel Scherer
2019-09-20  8:11   ` Malcolm Matalka
2019-09-20  8:35     ` Gabriel Scherer
2019-09-20  8:56       ` Gabriel Scherer
2019-09-20 14:25 ` Oleg

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).