caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] Confusing behaviour of type inference for polymorphic classes.
@ 2013-12-01 21:33 Dmitri Boulytchev
  2013-12-02 14:41 ` Goswin von Brederlow
  2013-12-02 15:24 ` Jeremy Yallop
  0 siblings, 2 replies; 12+ messages in thread
From: Dmitri Boulytchev @ 2013-12-01 21:33 UTC (permalink / raw)
  To: caml-list

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

     Hello everyone,

     I stumbled on the following confusing behaviour of the type 
checker: given the definitions

     type ('a, 'b) t =
        A of 'a * ('b, 'a) t
      | B of 'a

    class ['a, 'b, 'ta, 'tb] m =
      object
        method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 
'tb) t =
          fun fa fb s ->
            match s with
            | A (a, bat) -> A (fa a, (new m)#t fb fa bat)
            | B  a       -> B (fa a)
      end

     the following type is inferred for the class m:

     class ['a, 'b, 'ta, 'c] m :
       object
         constraint 'b = 'a  <--- why?
         constraint 'c = 'ta <--- why?
         method t : ('a -> 'ta) -> ('a -> 'ta) -> ('a, 'a) t -> ('ta, 'ta) t
       end

    Perhaps some explicit annotation is needed here (like that for the 
polymorphic recursion
for functions).
    I found the following workaround: first we abstract the instance 
creation ("new m") away:

    class ['a, 'b, 'ta, 'tb] m' f =
      object
        method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 
'tb) t =
          fun fa fb s ->
            match s with
            | A (a, bat) -> A (fa a, (f ())#t fb fa bat)
            | B  a       -> B (fa a)
      end

   which gives us the unconstrained type

    class ['a, 'b, 'ta, 'tb] m' :
         (unit ->
          < t : ('b -> 'tb) -> ('a -> 'ta) -> ('b, 'a) t -> ('tb, 'ta) 
t; .. >) ->
          object
            method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 
'tb) t
          end

   Then we construct the instance creation explicitly polymorphic function:

    let rec f : 'a 'b 'ta 'tb . unit -> <t : ('a -> 'ta) -> ('b -> 'tb) 
-> ('a, 'b) t -> ('ta, 'tb) t> =
      fun () -> new m' f

  and finally the class we're looking for:

    class ['a, 'b, 'ta, 'tb] m = ['a, 'b, 'ta, 'tb] m' f

    The complete annotated source file is attached.
    This workaround however does not solve everything: we cannot 
actually inherit
from the m since it calls hardcoded "new m"; we should inherit from m' 
(with additional parameter)
instead and "tie the knot" on the toplevel.
     Are there better solutions? Please help :)

     Best regards,
     Dmitry Boulytchev,
     St.Petersburg State University,
     Russia.



[-- Attachment #2: sample.ml --]
[-- Type: text/x-ocaml, Size: 1471 bytes --]

type ('a, 'b) t = 
    A of 'a * ('b, 'a) t 
  | B of 'a

(*
   The initial definition

   class ['a, 'b, 'ta, 'tb] m =
     object
       method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t = 
         fun fa fb s ->
           match s with
           | A (a, bat) -> A (fa a, (new m)#t fb fa bat)
           | B  a       -> B (fa a)
     end

   gives us the following type:

   class ['a, 'b, 'ta, 'c] m :
     object
       constraint 'b = 'a  <--- why?
       constraint 'c = 'ta <--- why?
       method t : ('a -> 'ta) -> ('a -> 'ta) -> ('a, 'a) t -> ('ta, 'ta) t
     end
*)

(* The modified version with the "new" abstracted away: *)

class ['a, 'b, 'ta, 'tb] m' f =
  object
    method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t = 
      fun fa fb s ->
        match s with
        | A (a, bat) -> A (fa a, (f ())#t fb fa bat)
        | B  a       -> B (fa a)
  end

(* Inferred type with no artificial constraints:

   class ['a, 'b, 'ta, 'tb] m' :
     (unit ->
      < t : ('b -> 'tb) -> ('a -> 'ta) -> ('b, 'a) t -> ('tb, 'ta) t; .. >) ->
      object
        method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t
      end
*)

(* Instance creation function: *)

let rec f : 'a 'b 'ta 'tb . unit -> <t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t> = 
  fun () -> new m' f

class ['a, 'b, 'ta, 'tb] m = ['a, 'b, 'ta, 'tb] m' f

(* Ok now:
   class ['a, 'b, 'ta, 'tb] m' : ['a, 'b, 'ta, 'tb] m
*)


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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-01 21:33 [Caml-list] Confusing behaviour of type inference for polymorphic classes Dmitri Boulytchev
@ 2013-12-02 14:41 ` Goswin von Brederlow
  2013-12-02 15:05   ` Dmitri Boulytchev
  2013-12-02 15:24 ` Jeremy Yallop
  1 sibling, 1 reply; 12+ messages in thread
From: Goswin von Brederlow @ 2013-12-02 14:41 UTC (permalink / raw)
  To: Dmitri Boulytchev; +Cc: caml-list

On Mon, Dec 02, 2013 at 01:33:55AM +0400, Dmitri Boulytchev wrote:
>     Hello everyone,
> 
>     I stumbled on the following confusing behaviour of the type
> checker: given the definitions
> 
>     type ('a, 'b) t =
>        A of 'a * ('b, 'a) t
>      | B of 'a
> 
>    class ['a, 'b, 'ta, 'tb] m =
>      object
>        method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta,
> 'tb) t =
>          fun fa fb s ->
>            match s with
>            | A (a, bat) -> A (fa a, (new m)#t fb fa bat)
>            | B  a       -> B (fa a)
>      end
> 
>     the following type is inferred for the class m:
> 
>     class ['a, 'b, 'ta, 'c] m :
>       object
>         constraint 'b = 'a  <--- why?
>         constraint 'c = 'ta <--- why?
>         method t : ('a -> 'ta) -> ('a -> 'ta) -> ('a, 'a) t -> ('ta, 'ta) t
>       end

I think this is caused by the recursion. The type inference assumes
that the recursive call will have the same type as the parent. Since
you switch 'a and 'b in the recursive call the compiler inferes that
'a == 'b.

>    Perhaps some explicit annotation is needed here (like that for
> the polymorphic recursion
> for functions).
>    I found the following workaround: first we abstract the instance
> creation ("new m") away:
> 
>    class ['a, 'b, 'ta, 'tb] m' f =
>      object
>        method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta,
> 'tb) t =
>          fun fa fb s ->
>            match s with
>            | A (a, bat) -> A (fa a, (f ())#t fb fa bat)
>            | B  a       -> B (fa a)
>      end
> 
>   which gives us the unconstrained type
> 
>    class ['a, 'b, 'ta, 'tb] m' :
>         (unit ->
>          < t : ('b -> 'tb) -> ('a -> 'ta) -> ('b, 'a) t -> ('tb,
> 'ta) t; .. >) ->
>          object
>            method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t ->
> ('ta, 'tb) t
>          end
> 
>   Then we construct the instance creation explicitly polymorphic function:
> 
>    let rec f : 'a 'b 'ta 'tb . unit -> <t : ('a -> 'ta) -> ('b ->
> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t> =
>      fun () -> new m' f
> 
>  and finally the class we're looking for:
> 
>    class ['a, 'b, 'ta, 'tb] m = ['a, 'b, 'ta, 'tb] m' f
> 
>    The complete annotated source file is attached.
>    This workaround however does not solve everything: we cannot
> actually inherit
> from the m since it calls hardcoded "new m"; we should inherit from
> m' (with additional parameter)
> instead and "tie the knot" on the toplevel.
>     Are there better solutions? Please help :)

Try using a self type or #m but this might not be solvable.
 
>     Best regards,
>     Dmitry Boulytchev,
>     St.Petersburg State University,
>     Russia.

MfG
	Goswin

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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-02 14:41 ` Goswin von Brederlow
@ 2013-12-02 15:05   ` Dmitri Boulytchev
  2013-12-05 15:13     ` Goswin von Brederlow
  0 siblings, 1 reply; 12+ messages in thread
From: Dmitri Boulytchev @ 2013-12-02 15:05 UTC (permalink / raw)
  To: Goswin von Brederlow; +Cc: caml-list

> I think this is caused by the recursion. The type inference assumes
> that the recursive call will have the same type as the parent. Since
> you switch 'a and 'b in the recursive call the compiler inferes that
> 'a == 'b.
>
     I don't think we have a recursive call here since we call
a method from *another* object.

     BR,
     DB

> On Mon, Dec 02, 2013 at 01:33:55AM +0400, Dmitri Boulytchev wrote:
>>      Hello everyone,
>>
>>      I stumbled on the following confusing behaviour of the type
>> checker: given the definitions
>>
>>      type ('a, 'b) t =
>>         A of 'a * ('b, 'a) t
>>       | B of 'a
>>
>>     class ['a, 'b, 'ta, 'tb] m =
>>       object
>>         method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta,
>> 'tb) t =
>>           fun fa fb s ->
>>             match s with
>>             | A (a, bat) -> A (fa a, (new m)#t fb fa bat)
>>             | B  a       -> B (fa a)
>>       end
>>
>>      the following type is inferred for the class m:
>>
>>      class ['a, 'b, 'ta, 'c] m :
>>        object
>>          constraint 'b = 'a  <--- why?
>>          constraint 'c = 'ta <--- why?
>>          method t : ('a -> 'ta) -> ('a -> 'ta) -> ('a, 'a) t -> ('ta, 'ta) t
>>        end
> I think this is caused by the recursion. The type inference assumes
> that the recursive call will have the same type as the parent. Since
> you switch 'a and 'b in the recursive call the compiler inferes that
> 'a == 'b.
>
>>     Perhaps some explicit annotation is needed here (like that for
>> the polymorphic recursion
>> for functions).
>>     I found the following workaround: first we abstract the instance
>> creation ("new m") away:
>>
>>     class ['a, 'b, 'ta, 'tb] m' f =
>>       object
>>         method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta,
>> 'tb) t =
>>           fun fa fb s ->
>>             match s with
>>             | A (a, bat) -> A (fa a, (f ())#t fb fa bat)
>>             | B  a       -> B (fa a)
>>       end
>>
>>    which gives us the unconstrained type
>>
>>     class ['a, 'b, 'ta, 'tb] m' :
>>          (unit ->
>>           < t : ('b -> 'tb) -> ('a -> 'ta) -> ('b, 'a) t -> ('tb,
>> 'ta) t; .. >) ->
>>           object
>>             method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t ->
>> ('ta, 'tb) t
>>           end
>>
>>    Then we construct the instance creation explicitly polymorphic function:
>>
>>     let rec f : 'a 'b 'ta 'tb . unit -> <t : ('a -> 'ta) -> ('b ->
>> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t> =
>>       fun () -> new m' f
>>
>>   and finally the class we're looking for:
>>
>>     class ['a, 'b, 'ta, 'tb] m = ['a, 'b, 'ta, 'tb] m' f
>>
>>     The complete annotated source file is attached.
>>     This workaround however does not solve everything: we cannot
>> actually inherit
>> from the m since it calls hardcoded "new m"; we should inherit from
>> m' (with additional parameter)
>> instead and "tie the knot" on the toplevel.
>>      Are there better solutions? Please help :)
> Try using a self type or #m but this might not be solvable.
>   
>>      Best regards,
>>      Dmitry Boulytchev,
>>      St.Petersburg State University,
>>      Russia.
> MfG
> 	Goswin
>


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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-01 21:33 [Caml-list] Confusing behaviour of type inference for polymorphic classes Dmitri Boulytchev
  2013-12-02 14:41 ` Goswin von Brederlow
@ 2013-12-02 15:24 ` Jeremy Yallop
  2013-12-03  8:35   ` Alain Frisch
  1 sibling, 1 reply; 12+ messages in thread
From: Jeremy Yallop @ 2013-12-02 15:24 UTC (permalink / raw)
  To: Dmitri Boulytchev; +Cc: caml-list

Hi Dmitri,

On 1 December 2013 21:33, Dmitri Boulytchev <dboulytchev@gmail.com> wrote:
>     I stumbled on the following confusing behaviour of the type checker:
> given the definitions
>
>     type ('a, 'b) t =
>        A of 'a * ('b, 'a) t
>      | B of 'a
>
>    class ['a, 'b, 'ta, 'tb] m =
>      object
>        method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t =
>          fun fa fb s ->
>            match s with
>            | A (a, bat) -> A (fa a, (new m)#t fb fa bat)
>            | B  a       -> B (fa a)
>      end
>
>     the following type is inferred for the class m:
>
>     class ['a, 'b, 'ta, 'c] m :
>       object
>         constraint 'b = 'a  <--- why?
>         constraint 'c = 'ta <--- why?
>         method t : ('a -> 'ta) -> ('a -> 'ta) -> ('a, 'a) t -> ('ta, 'ta) t
>       end

There's good news and bad news.  The good news is that the problem can
be solved for your particular example.  The bad news is that the
approach doesn't work in the general case.  On to the details...

First, let's get some definitions out of the way.  A parameterised
type definition "('a1, 'a2, ... 'an) t" is *regular* if all
occurrences of "t" within its definition are instantiated with the
parameters from the lhs of the '=' (i.e. it occurs exactly as "('a1,
'a2, ... 'an) t").  Here are some regular data types definitions:

   type 'a list = Nil | Cons of 'a * 'a listapproach

   type ('a, 'b) altlist = Nil | Cons of 'a * 'b * ('a, 'b) altlist

In each of these the type constructor being defined is only
instantiated with the exact parameter list from the lhs of the '='.

In contrast, here are some non-regular data type definitions:

   type 'a nested = Empty | Branch of 'a * ('a * 'a) nested

   type ('a, 'len) vec =
     Nil : ('a, zero) vec
   | Cons : 'a * ('a, 'n) vec -> ('a, 'n succ) vec

In each of these the type constructor being defined is instantiated
with parameter lists that are different from the lhs of the
definition: "('a * 'a) nested" is different from "'a nested" in the
first case, and "('a, zero) vec" and "('a, 'n succ)" vec are different
from "('a, 'len) vec" in the second case.

The analogue of non-regular types for functions is polymorphic
recursion.  A function is *polymorphic-recursive* if some calls to the
function in its own definition instantiate the type variables
differently from the definition itself.  Here's a polymorphic
recursive function

   let rec depth : 'a. 'a nested -> int = function
     | Empty -> 0
     | Branch (_, n) -> 1 + depth n

The call to "depth" on the last line uses it at type "('a * 'a) nested
-> int", which is different from the defined type "'a nested -> int".
If you call "depth" on a value of type "int nested" of depth 3 then
the recursive calls will have the following types:

   int nested -> int
   (int * int) nested -> int
   ((int * int) * (int * int)) nested -> int
   (((int * int) * (int * int)) * ((int * int) * (int * int))) nested -> int

It's also worth noting that the type annotation can't be omitted,
since polymorphic-recursive types can't be inferred, although they can
be checked.

In contrast, here's a function that's polymorphic and recursive but
not polymorphic-recursive:

  let rec length : 'a. 'a list -> int = function
    | Nil -> 0
    | Cons (_, l) -> 1 + length l

In this case the call to "length" uses it at type "'a list -> int" --
just the same type as the definition.  If you call "length" on a value
of type "int list" of length 3 then the recursive calls will have the
following types:

  int list -> int
  int list -> int
  int list -> int
  int list -> int

In this case the type annotation is unnecessary, and if you omit it
then OCaml will infer the same type, "'a list -> int".

It'll probably come as no surprise that non-regular types and
polymorphic recursion are closely related: traversals over non-regular
types generally involve polymorphic recursion.

In your example the type 't' is non-regular: it's defined to take
parameters "('a, 'b)", but used in its definition as "('b, 'a) t":

>   type ('a, 'b) t =
>      A of 'a * ('b, 'a) t
>    | B of 'a

Traversals over values of type "t" therefore need to be
polymorphic-recursive.  Since you've exposed the type parameters as
class parameters you need the class type to be non-regular as well:
you've defining "m" with parameters "['a, 'b, 'ta, 'tb]", but trying
to instantiate it with parameters "['b, 'a, 'tb, 'ta]".  This isn't
possible in OCaml: class and object types (like other structural types
such as polymorphic variants) can only be regular.  The approach
you're trying therefore won't work in general for defining traversals
over non-regular types.  As you've noted, you can work around the
problem using open recursion and typing the knot yourself, but you
lose the ability to subtype in the process.

On to the good news.  Unlike "nested" and "vec", your type "t" is only
a little bit non-regular.  The non-regularity is a simple flip of the
type parameters, so a recursive traversal of a value of "t" involves
calling the traversal function at the following types

   ('a, 'b) t -> 'result
   ('b, 'a) t -> 'result
   ('a, 'b) t -> 'result
   ('b, 'a) t -> 'result
   ...

This gives us a clue as to how we might attack the typing problem.
Unfolding the traversal methods one level gives us a pair of
mutually-recursive methods, neither of which is polymorphic-recursive,
and neither of which instantiates the class with different parameters:

   class ['a, 'b, 'ta, 'tb] m =
     object
       method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t =
         fun fa fb s ->
           match s with
           | A (a, bat) ->
             A (fa a, (new m)#t' fb fa bat)
           | B  a       -> B (fa a)
       method t' :  ('b -> 'tb) -> ('a -> 'ta) -> ('b, 'a) t -> ('tb, 'ta) t =
         fun fa fb s ->
           match s with
           | A (a, bat) ->
             A (fa a, (new m)#t fb fa bat)
           | B  a       -> B (fa a)
     end

In fact, instead of repeatedly creating new instances you can now use
a "self" binding, which will give you more scope for overriding
behaviour with subtyping:

   class ['a, 'b, 'ta, 'tb] m =
     object (self)
       method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t =
         fun fa fb s ->
           match s with
           | A (a, bat) ->
             A (fa a, self#t' fb fa bat)
           | B  a       -> B (fa a)
       method t' :  ('b -> 'tb) -> ('a -> 'ta) -> ('b, 'a) t -> ('tb, 'ta) t =
         fun fa fb s ->
           match s with
           | A (a, bat) ->
             A (fa a, self#t fb fa bat)
           | B  a       -> B (fa a)
     end

Of course, unfolding in this way doesn't work for general non-regular
types, since unfolding "nested" or "vec" would go on forever.

You might also consider unfolding the type "t" itself, at which point
traversals become more straightforward.  If you can expand the
definition of "t" to a pair of mutually recursive (and regular!)
types:

    type ('a, 'b) t =
       A of 'a * ('a, 'b) s
     | B of 'a
    and ('a, 'b) s =
       C of 'b * ('a, 'b) t
     | D of 'b

then you can use your current approach without any changes.

Hope this helps,

Jeremy.

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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-02 15:24 ` Jeremy Yallop
@ 2013-12-03  8:35   ` Alain Frisch
  2013-12-03 10:17     ` Jeremy Yallop
  0 siblings, 1 reply; 12+ messages in thread
From: Alain Frisch @ 2013-12-03  8:35 UTC (permalink / raw)
  To: Jeremy Yallop, Dmitri Boulytchev; +Cc: caml-list

On 12/02/2013 04:24 PM, Jeremy Yallop wrote:
> There's good news and bad news.  The good news is that the problem can
> be solved for your particular example.  The bad news is that the
> approach doesn't work in the general case.

A more general solution is based on recursive modules:

=======================================================================
type ('a, 'b) t =
     A of 'a * ('b, 'a) t
   | B of 'a

module rec X : sig
   class ['a, 'b, 'ta, 'tb] m : object
     method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t
   end
end = struct
   class ['a, 'b, 'ta, 'tb] m =
     object
       method t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t =
         fun fa fb s ->
           match s with
           | A (a, bat) -> A (fa a, (new X.m)#t fb fa bat)
           | B  a       -> B (fa a)
     end
end
=======================================================================


This technique also works e.g. for defining mutually recursive classes 
and nominal types.


Alain

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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-03  8:35   ` Alain Frisch
@ 2013-12-03 10:17     ` Jeremy Yallop
  2013-12-03 12:33       ` Alain Frisch
  0 siblings, 1 reply; 12+ messages in thread
From: Jeremy Yallop @ 2013-12-03 10:17 UTC (permalink / raw)
  To: Alain Frisch; +Cc: Dmitri Boulytchev, caml-list

On 3 December 2013 08:35, Alain Frisch <alain@frisch.fr> wrote:
> On 12/02/2013 04:24 PM, Jeremy Yallop wrote:
>> There's good news and bad news.  The good news is that the problem can
>> be solved for your particular example.  The bad news is that the
>> approach doesn't work in the general case.
>
> A more general solution is based on recursive modules:

Using recursive modules certainly makes it possible to write a class
with the correct type, but unless I'm mistaken you lose the ability to
override the behaviour on recursive calls -- i.e. the call to (new
X.m)#t can no longer be replaced with a call through a self variable.

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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-03 10:17     ` Jeremy Yallop
@ 2013-12-03 12:33       ` Alain Frisch
  2013-12-03 12:58         ` Jeremy Yallop
  0 siblings, 1 reply; 12+ messages in thread
From: Alain Frisch @ 2013-12-03 12:33 UTC (permalink / raw)
  To: Jeremy Yallop; +Cc: Dmitri Boulytchev, caml-list

On 12/03/2013 11:17 AM, Jeremy Yallop wrote:
> Using recursive modules certainly makes it possible to write a class
> with the correct type, but unless I'm mistaken you lose the ability to
> override the behaviour on recursive calls -- i.e. the call to (new
> X.m)#t can no longer be replaced with a call through a self variable.

The instance on which the method is called cannot be "self", since it 
corresponds to different type parameters for the class.  I don't know 
what Dmitri is exactly looking for, but maybe a polymorphic method 
(instead of a parametrized class) would do the job as well.

-- Alain


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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-03 12:33       ` Alain Frisch
@ 2013-12-03 12:58         ` Jeremy Yallop
  2013-12-03 17:49           ` Dmitri Boulytchev
  0 siblings, 1 reply; 12+ messages in thread
From: Jeremy Yallop @ 2013-12-03 12:58 UTC (permalink / raw)
  To: Alain Frisch; +Cc: Dmitri Boulytchev, caml-list

On 3 December 2013 12:33, Alain Frisch <alain@frisch.fr> wrote:
> On 12/03/2013 11:17 AM, Jeremy Yallop wrote:
>> Using recursive modules certainly makes it possible to write a class
>> with the correct type, but unless I'm mistaken you lose the ability to
>> override the behaviour on recursive calls -- i.e. the call to (new
>> X.m)#t can no longer be replaced with a call through a self variable.
>
> The instance on which the method is called cannot be "self", since it
> corresponds to different type parameters for the class.

That's true for the approach based on recursive modules, but not for
the unfolding approach outlined in my earlier message, which does
support recursive calls through self.

> I don't know what Dmitri is exactly looking for, but maybe a polymorphic method (instead of a parametrized class) would do the job as well.

Agreed.  For straightforward polymorphic recursion either your
recursive module approach or a simple recursive method or function is
sufficient.  Classes are better for open recursion, but only work
either for regular types or for types with finite irregularity (i.e.
where a finite number of unfoldings bring you back to the original
parameter list).

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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-03 12:58         ` Jeremy Yallop
@ 2013-12-03 17:49           ` Dmitri Boulytchev
  2013-12-08  1:15             ` Jeremy Yallop
  0 siblings, 1 reply; 12+ messages in thread
From: Dmitri Boulytchev @ 2013-12-03 17:49 UTC (permalink / raw)
  To: Jeremy Yallop, Alain Frisch; +Cc: caml-list

     Jeremy, Alain,

    the code snippet I presented was actually boiled down from the more 
complex example.
I don't have any particular interest to the concrete type t; I'm ok to 
give up
on "finitely regularizable" types but I definitely don't want to give up 
on GADTs
which might be essentially irregular.

 > As you've noted, you can work around the
 > problem using open recursion and typing the knot yourself, but you
 > lose the ability to subtype in the process.

   Well, having the solution with the polymorphic recursive function

     class ['a, 'b, 'ta, 'tb] proto_m f =
       object ... end

     let rec f : 'a 'b 'ta 'tb . unit -> ('a, 'b, 'ta, 'tb) #proto_m = 
fun () -> new proto_m f

     class ['a, 'b, 'ta, 'tb] m = ['a, 'b, 'ta, 'tb] proto_m f

   it is possible to inherit from proto_m:

     class ['a, 'b, 'ta, 'tb] m' f  =
       object
         inherit ['a, 'b, 'ta, 'tb] proto_m f as super
         method t fa fb s =
           match s with
           | A (a, bat) -> <some extra work>; super#t fa fb s
           | B  a       -> <some extra work>; super#t fa fb s
       end

     let rec g : 'a 'b 'ta 'tb . unit -> ('a, 'b, 'ta, 'tb) #proto_m = 
fun () -> new m' g

     class ['a, 'b, 'ta, 'tb] subm = ['a, 'b, 'ta, 'tb] m' g

   Here subm is a legitimate subclass of m with the desired behaviour 
(this property can not be
achieved with the recursive module though it is really much more elegant 
way to provide the proper
instantiation for class type parameters). So I don't see why I'm loosing 
the ability to subtype.
It seems to me that this workaround can be used to overcome the "regular 
class" restriction
completely in a syntax-conversion manner. I appreciate any comments.

   Dmitry.



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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-02 15:05   ` Dmitri Boulytchev
@ 2013-12-05 15:13     ` Goswin von Brederlow
  0 siblings, 0 replies; 12+ messages in thread
From: Goswin von Brederlow @ 2013-12-05 15:13 UTC (permalink / raw)
  To: Dmitri Boulytchev; +Cc: caml-list

On Mon, Dec 02, 2013 at 07:05:45PM +0400, Dmitri Boulytchev wrote:
> >I think this is caused by the recursion. The type inference assumes
> >that the recursive call will have the same type as the parent. Since
> >you switch 'a and 'b in the recursive call the compiler inferes that
> >'a == 'b.
> >
>     I don't think we have a recursive call here since we call
> a method from *another* object.
> 
>     BR,
>     DB

Different instance but same class and method. The type system is a bit
odd with objects sometimes.
 
MfG
	Goswin

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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-03 17:49           ` Dmitri Boulytchev
@ 2013-12-08  1:15             ` Jeremy Yallop
  2013-12-09 13:48               ` Dmitri Boulytchev
  0 siblings, 1 reply; 12+ messages in thread
From: Jeremy Yallop @ 2013-12-08  1:15 UTC (permalink / raw)
  To: Dmitri Boulytchev; +Cc: Alain Frisch, caml-list

On 3 December 2013 17:49, Dmitri Boulytchev <dboulytchev@gmail.com> wrote:
>   Here subm is a legitimate subclass of m with the desired behaviour [...]. So I don't see why I'm loosing the ability to subtype.

You're quite right, of course: you still have subtyping.  What you're
giving up are some of the benefits of inheritance, since the
"recursive" calls go through the instance you pass in rather than
through a self variable.  If you don't have other reasons for using
objects and classes it might be worth considering using records
instead, since they're semantically much simpler.  The example should
translate directly:

  type ('a, 'b, 'ta, 'tb) m' =  {
    t : ('a -> 'ta) -> ('b -> 'tb) -> ('a, 'b) t -> ('ta, 'tb) t
  }

  let proto_m f = {
    t = fun fa fb s ->
    ...

  let m' f =
    let super = proto_m f in {
      super with t = fun fa fb s ->
    ...

> It seems to me that this workaround can be used to overcome the "regular class" restriction completely in a syntax-conversion manner. I appreciate any comments.

Yes, it's a neat approach!  One small suggestion: in order to handle
the case where the type you're traversing has multiple recursive
instantations with different parameters you might consider passing the
instance creation function in a way that allows it to be called
polymorphically (e.g. by wrapping it in a record with a polymorphic
field):

  type new_proto_m = {
     new_proto_m : 'a 'b 'ta 'tb .
         unit -> <t : ('a -> 'ta) -> ('b -> 'tb) ->
                      ('a, 'b) t -> ('ta, 'tb) t>
  }

There is one last thing I'm curious about.  The difficulties that
you're avoiding with open recursion seem to arise from the
representation of the traversal as a parameterised class with a
monomorphic method.  Have you considered switching things round and
using an unparameterised class with polymorphic methods instead?  I
think everything turns out much simpler if you do: you can use
straightforward inheritance, calls through self, etc.

  class m =
  object (self)
    method t : 'a 'b 'ta 'tb. ('a -> 'ta) -> ('b -> 'tb) ->
                              ('a, 'b) t -> ('ta, 'tb) t =
      fun fa fb s ->
        match s with
        | A (a, bat) -> A (fa a, self#t fb fa bat)
        | B  a       -> B (fa a)
  end

The map and fold generators supplied with Camlp4 use a variant of this
approach, and it seems to work well in practice.

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

* Re: [Caml-list] Confusing behaviour of type inference for polymorphic classes.
  2013-12-08  1:15             ` Jeremy Yallop
@ 2013-12-09 13:48               ` Dmitri Boulytchev
  0 siblings, 0 replies; 12+ messages in thread
From: Dmitri Boulytchev @ 2013-12-09 13:48 UTC (permalink / raw)
  To: Jeremy Yallop; +Cc: caml-list


> Yes, it's a neat approach!  One small suggestion: in order to handle
> the case where the type you're traversing has multiple recursive
> instantations with different parameters you might consider passing the
> instance creation function in a way that allows it to be called
> polymorphically (e.g. by wrapping it in a record with a polymorphic
> field):
     Of course; this is yet another workaround to make it possible to use
that workaround :)

> There is one last thing I'm curious about.  The difficulties that
> you're avoiding with open recursion seem to arise from the
> representation of the traversal as a parameterised class with a
> monomorphic method.
     The reason is quite simple --- class parameters are used to represent
the *result* type of the transformation, not the parameters of the 
transforming
type. I simplified the example just to clarify the polymorphic class issues.
     In reality what I'm interested in is somewhat like this: for a type

     ('a, 'b, ...) t = A ... | B ... | C ...

     we generate a virtual class

     class virtual ['ta, 'tb, ..., 'inh, 'syn] t_t = object
         method virtual c_A : <explicitly polymorphic type on 'a, 'b etc.>
         method virtual c_B : <explicitly polymorphic type on 'a, 'b etc.>
         etc.
     end

     which represents some attribute transformation with inherited 
attribute type 'inh; the
type of synthesized attribute for type 'a is 'ta, for 'b is 'tb etc., 
for type ('a, 'b, ...) t
is 'syn. The methods of the class handle the individual constructors; 
the arguments for
these methods capture the transformation function as well.
     For example, for regular map we have the following bindings:

     class ['ta, 'tb, ...] map_t = object
         inherit ['ta, 'tb, ..., unit, ('ta, 'tb, ...) t] t_t
         ...
     end

     for show we have

     class show_t = object
         inherit [string, string, ..., unit, string] t_t
         ...
     end

     for, say, eval

     class eval_t = object
         inherit [int, int, ..., string -> int, int] t_t
         ...
     end

     etc.

     Under this representation we might need the single 
shallow-traversal generic function
to implement all these transformations.
     Per-constructor object encoding allows modification with less 
boilerplate code (via
inhertance); in addition if we have a polymorphic-variant type t = [ a | 
b | c ... ] then
the certain transformation for t can be constructed by regular class 
inheritance from the
implementations of the same transformation for it's counterparts.


     Best regards,
     Dmitry.

     P.S. I was'nt offended by your long message (but surprised a little 
bit ).

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

end of thread, other threads:[~2013-12-09 13:48 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-12-01 21:33 [Caml-list] Confusing behaviour of type inference for polymorphic classes Dmitri Boulytchev
2013-12-02 14:41 ` Goswin von Brederlow
2013-12-02 15:05   ` Dmitri Boulytchev
2013-12-05 15:13     ` Goswin von Brederlow
2013-12-02 15:24 ` Jeremy Yallop
2013-12-03  8:35   ` Alain Frisch
2013-12-03 10:17     ` Jeremy Yallop
2013-12-03 12:33       ` Alain Frisch
2013-12-03 12:58         ` Jeremy Yallop
2013-12-03 17:49           ` Dmitri Boulytchev
2013-12-08  1:15             ` Jeremy Yallop
2013-12-09 13:48               ` Dmitri Boulytchev

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