Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module subtyping in higher-rank-polymorphic functions #62

Open
pxeger opened this issue Sep 4, 2023 · 3 comments
Open

Module subtyping in higher-rank-polymorphic functions #62

pxeger opened this issue Sep 4, 2023 · 3 comments

Comments

@pxeger
Copy link

pxeger commented Sep 4, 2023

(Functor and Applicative are defined here)

The following code fails to compile, because the compiler doesn't consider Applicative a subtype of Functor, and possibly also because the compiler doesn't know that {M} -> t is contravariant in M.

type accepts_functor = {F: Functor} -> int F.t -> int F.t
let accepts_functor {F: Functor} x = F.fmap ((+) 1) x

type accepts_applicative = {F: Applicative} -> int F.t -> int F.t
let accepts_applicative {F: Applicative} x = F.fmap ((+) 1) x

let use (f: accepts_applicative) = f [1; 2; 3]

let () =
  ignore (use accepts_applicative);
  ignore (use accepts_functor)   (* errors here *)
Error: This expression has type
         {F : Functor} -> int F.t -> int F.t
       but an expression was expected of type
         accepts_applicative =
           {F : Applicative} -> int F.t -> int F.t
       Type (module Functor) is not compatible with type
         (module Applicative)

This came up while trying to implement composition of lenses in lens.

The corresponding code in Haskell works fine.

Unlike in Haskell, Functor may not necessarily be a subtype of Applicative in OCaml, I think because of something to do with the scoping flexibility OCaml gives you in modules, compared to Haskell's typeclasses? (TODO: find out exactly why, ideally with a counterexample)

@yallop suggested that it might at least be allowed when Functor's signature is a strict prefix of Applicative's (which it is, in this case).

@yallop
Copy link
Contributor

yallop commented Sep 5, 2023

possibly also because the compiler doesn't know that {M} -> t is contravariant in M.

I think the variance of {...} -> is handled correctly, since this is accepted:

module type S = sig type t end
module type T = sig include S type u end
let f (t : {X:S} -> int) = (t :> {X:T} -> int)

but there's a very restrictive notion of subtyping allowed for implicit arguments

@clementblaudeau
Copy link

Do you reuse the subtyping check of first-class modules ?
Type (module Functor) is not compatible with type (module Applicative)
This looks like the type of the first-class module (module Functor) is checked against (module Applicative), but I might be wrong.

The subtyping check for first-class modules is a subset of the normal subtyping check between signatures, with a restriction to code-free subtyping (same value fields in the same order).

@samsa1
Copy link

samsa1 commented May 13, 2024

I just checked the code and I confirm what clement stated.
Subtyping is done by using the same comparison than the one used with first-class modules : ie same runtime representation.
Modifying the way it currently works could be done by :

  • either an eta expansion of the function (but it would have an impact on performances)
  • or allowing the runtime to handle modules containing more fields than expected (but would allows only a few more cases)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants