* [Caml-list] Late adding of type variable constraints @ 2015-09-04 20:28 Markus Mottl 2015-09-04 22:19 ` Jacques Garrigue 0 siblings, 1 reply; 8+ messages in thread From: Markus Mottl @ 2015-09-04 20:28 UTC (permalink / raw) To: OCaml List Hi, I wonder whether there is a way to add constraints to type variables in signatures after the signature was defined. E.g.: --- module type S = sig type 'a t type ('a, 'b) mappers val map : ('a, 'b) mappers -> 'a t -> 'b t end module type T = sig type 'a t constraint 'a = unit (* whatever *) include S with type 'a t := 'a t end --- The above will fail, because 'a has additional constraints for type "t" in signature "T". If I write instead e.g. "type 'a t = 'a list", this will work and also constrain the signature to something narrower. What makes constraints on polymorphic variables special here? Regards, Markus -- Markus Mottl http://www.ocaml.info markus.mottl@gmail.com ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Caml-list] Late adding of type variable constraints 2015-09-04 20:28 [Caml-list] Late adding of type variable constraints Markus Mottl @ 2015-09-04 22:19 ` Jacques Garrigue 2015-09-04 23:58 ` Markus Mottl 0 siblings, 1 reply; 8+ messages in thread From: Jacques Garrigue @ 2015-09-04 22:19 UTC (permalink / raw) To: Markus Mottl; +Cc: OCaml List On 2015/09/04 13:28, Markus Mottl <markus.mottl@gmail.com> wrote: > > Hi, > > I wonder whether there is a way to add constraints to type variables > in signatures after the signature was defined. E.g.: > > --- > module type S = sig > type 'a t > type ('a, 'b) mappers > > val map : ('a, 'b) mappers -> 'a t -> 'b t > end > > module type T = sig > type 'a t constraint 'a = unit (* whatever *) > include S with type 'a t := 'a t > end > --- > > The above will fail, because 'a has additional constraints for type > "t" in signature "T". If I write instead e.g. "type 'a t = 'a list", > this will work and also constrain the signature to something narrower. > What makes constraints on polymorphic variables special here? Consider this signature: module type S = sig type 'a t type 'a u = U of 'a t constraint 'a = < m: int; .. > end Now the signature S with type 'a constraint 'a t = unit is ill-typed, but to see it you must type-check again all of its contents (not just the definition of t). This is the reason you cannot do that. Jacques Garrigue ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Caml-list] Late adding of type variable constraints 2015-09-04 22:19 ` Jacques Garrigue @ 2015-09-04 23:58 ` Markus Mottl 2015-09-07 18:33 ` Mikhail Mandrykin 0 siblings, 1 reply; 8+ messages in thread From: Markus Mottl @ 2015-09-04 23:58 UTC (permalink / raw) To: Jacques Garrigue; +Cc: OCaml List I see. So it's not really a matter of soundness, but the type checker changes would presumably be overly intrusive and possibly cause issues with efficiency. Ultimately, I wanted to be able to constrain type variables via a functor argument, which is also apparently not possible. Maybe there are alternative solutions to what I'm trying to do, which is actually a quite neat type system challenge: Imagine you have a datastructure that is parameterized over an unknown number of type variables. Given such a datastructure, I want to be able to map all these type variables from one type to another while preserving the "skeleton" of that structure, but without limiting the skeleton constructors to a specific number of type variables. Lets assume the "skeleton" is the option type. The first part of the (slightly simplified) solution compiles just fine: --- module type S = sig type 'a t type ('a, 'b) mapper val map : ('a, 'b) mapper -> 'a t -> 'b t end module Option (Arg : S) = struct type 'a t = 'a Arg.t option type ('a, 'b) mapper = ('a, 'b) Arg.mapper let map mapper = function | None -> None | Some el -> Some (Arg.map mapper el) end --- In order to introduce any number of type variables, I thought one could use the following neat trick (example with two variables): --- module Arg1 = struct type 'args t = 'arg1 constraint 'args = 'arg1 * _ type ('src, 'dst) mapper = { map1 : 'src1 -> 'dst1; map2 : 'src2 -> 'dst2; } constraint 'src = 'src1 * 'src2 constraint 'dst = 'dst1 * 'dst2 let map mapper arg1 = mapper.map1 arg1 end --- But "Option (Arg1)" will not type-check, because the constraints are missing in signature "S". This is unfortunate, because I think the code would be correct. The body of "Option" should preserve any constraint on the type variables. Is there any alternative that preserves extensibility, i.e. an implementation of "Option" that will work with any number of type parameters? On Fri, Sep 4, 2015 at 6:19 PM, Jacques Garrigue <garrigue@math.nagoya-u.ac.jp> wrote: > On 2015/09/04 13:28, Markus Mottl <markus.mottl@gmail.com> wrote: >> >> Hi, >> >> I wonder whether there is a way to add constraints to type variables >> in signatures after the signature was defined. E.g.: >> >> --- >> module type S = sig >> type 'a t >> type ('a, 'b) mappers >> >> val map : ('a, 'b) mappers -> 'a t -> 'b t >> end >> >> module type T = sig >> type 'a t constraint 'a = unit (* whatever *) >> include S with type 'a t := 'a t >> end >> --- >> >> The above will fail, because 'a has additional constraints for type >> "t" in signature "T". If I write instead e.g. "type 'a t = 'a list", >> this will work and also constrain the signature to something narrower. >> What makes constraints on polymorphic variables special here? > > Consider this signature: > > module type S = sig > type 'a t > type 'a u = U of 'a t constraint 'a = < m: int; .. > > end > > Now the signature > S with type 'a constraint 'a t = unit > is ill-typed, but to see it you must type-check again all of its contents > (not just the definition of t). > > This is the reason you cannot do that. > > Jacques Garrigue -- Markus Mottl http://www.ocaml.info markus.mottl@gmail.com ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Caml-list] Late adding of type variable constraints 2015-09-04 23:58 ` Markus Mottl @ 2015-09-07 18:33 ` Mikhail Mandrykin 2015-09-09 14:00 ` Markus Mottl 0 siblings, 1 reply; 8+ messages in thread From: Mikhail Mandrykin @ 2015-09-07 18:33 UTC (permalink / raw) To: Markus Mottl; +Cc: OCaml List On Friday, September 04, 2015 07:58:26 PM Markus Mottl wrote: > I see. So it's not really a matter of soundness, but the type checker > changes would presumably be overly intrusive and possibly cause issues > with efficiency. Ultimately, I wanted to be able to constrain type > variables via a functor argument, which is also apparently not > possible. > > Maybe there are alternative solutions to what I'm trying to do, which > is actually a quite neat type system challenge: > > Imagine you have a datastructure that is parameterized over an unknown > number of type variables. Given such a datastructure, I want to be > able to map all these type variables from one type to another while > preserving the "skeleton" of that structure, but without limiting the > skeleton constructors to a specific number of type variables. Not sure if it's exactly what's desired, but I'd suggest Format-like implementation in spirit of the following: (* The mapper "format" GADT, support for pairs, options, results and type variables *) type ('c, 'r, 'i, 'o) mfmt = (* 'c -- continuation, 'r -- result of the form (('a -> 'b) -> ... -> ('y -> 'z) -> 'c), 'i -- input type, o -- output type *) | T_v : ('c, ('e -> 'f) -> 'c, 'e, 'f) mfmt | Pair : ('r1, _ -> _ as 'r2, 'i1, 'o1) mfmt * ('c, _ -> _ as 'r1, 'i2, 'o2) mfmt -> ('c, 'r2, 'i1 * 'i2, 'o1 * 'o2) mfmt | Option : ('c, _ -> _ as 'r, 'i, 'o) mfmt -> ('c, 'r, 'i option, 'o option) mfmt | Result : ('r1, _ -> _ as 'r2, 'i1, 'o1) mfmt * ('c, _ -> _ as 'r1, 'i2, 'o2) mfmt -> ('c, 'r2, ('i1, 'i2) result, ('o1, 'o2) result) mfmt constraint 'r = 'x -> 'y (* The CPS mapper generator, accepts continuation after mapper "format" *) let rec map : type a y z d e x. (x -> e, (y -> z), a, d) mfmt -> ((a -> d) -> x -> e) -> (y -> z) = function | T_v -> fun c f -> c f | Pair (m1, m2) -> fun c -> map m1 @@ fun f1 -> map m2 @@ fun f2 ->c @@ fun (a, b) -> f1 a, f2 b | Option m -> fun c -> map m @@ fun f -> c (function Some a -> Some (f a) | None -> None) | Result (m1, m2) -> fun c -> map m1 @@ fun f1 -> map m2 @@ fun f2 -> c @@ function | Ok a -> Ok (f1 a) | Error b -> Error (f2 b) (* The final mapper generator *) let map f = map f (fun x -> x) (* Examples *) (* Mapper for the skeleton (('a, 'b) option, 'c option * 'd) result *) let map_1 m1 m2 m3 m4 = map (Result (Pair (T_v, Option T_v), Pair (Option T_v, T_v))) m1 m2 m3 m4;; let f = map_1 succ int_of_string string_of_int pred;; f (Ok (5, Some "6"));; (* Ok (6, Some 6) *) f (Error (None, 0));; (* Error (None, -1) *) (* Mapper for skeleton ('a * ('c * 'e), 'g option * 'i option) result *) let map_2 m1 m2 m3 m4 m5 = map (Result (Pair (T_v, Pair (T_v, T_v)), Pair (Option T_v, Option T_v))) m1 m2 m3 m4 m5;; let f = map_2 succ (fun (a, b) -> Some (int_of_string b, string_of_int a)) float_of_int pred (function Ok a -> Some (Error a) | Error a -> Some (Ok a));; f (Ok (1, ((6, "7"), 0)));; (* Ok (2, (Some (7, "6"), 0.)) *) f (Error (None, Some (Ok "5")));; (* Error (None, Some (Some (Error "5"))) *) With open extensible types it's possible to extend this approach to unlimited number of supported basic skeleton types, but the resulting usages would look somewhat messy, e.g.: let map_1 m1 m2 m3 m4 = let open T_v in let module O = Option (T_v) in let module P1 = Pair (T_v) (O) in let module P2 = Pair (O) (T_v) in let module R = Result (P1) (P2) in R.map (R.Result (P1.Pair (T_v, O.Option T_v), P2.Pair (O.Option T_v, T_v))) m1 m2 m3 m4 -- Mikhail Mandrykin Linux Verification Center, ISPRAS web: http://linuxtesting.org e-mail: mandrykin@ispras.ru ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Caml-list] Late adding of type variable constraints 2015-09-07 18:33 ` Mikhail Mandrykin @ 2015-09-09 14:00 ` Markus Mottl 2015-09-09 19:28 ` Mikhail Mandrykin 0 siblings, 1 reply; 8+ messages in thread From: Markus Mottl @ 2015-09-09 14:00 UTC (permalink / raw) To: Mikhail Mandrykin; +Cc: OCaml List On Mon, Sep 7, 2015 at 2:33 PM, Mikhail Mandrykin <mandrykin@ispras.ru> wrote: > Not sure if it's exactly what's desired, but I'd suggest Format-like > implementation in spirit of the following: Though it only solves part of my particular problem, this is a pretty clever encoding to create mapping functions for structures with arbitrary numbers of type parameters so thanks for sharing. In my case I also need to define a type ('a t) such that I can pass it on in a functor. The functor body needs to know that invariants are maintained after each mapping: the skeleton, as captured by t, and that the same constraints hold for type parameter 'a across mappings. Only then can the functor body easily map back and forth between various encodings of the type parameters while maintaining the skeleton. Right now my solution to stay extensible is to simply syntactically include the file containing the functors "Option", "Pair", etc., using a macro preprocessor. The environment into which it is included defines the constraints on the type variables for this particular instance. This approach works for arbitrary constraints, which is why I'm disappointed that this apparently cannot be done without macros. Somehow it feels like a problem that should be easily solvable within the core language. -- Markus Mottl http://www.ocaml.info markus.mottl@gmail.com ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Caml-list] Late adding of type variable constraints 2015-09-09 14:00 ` Markus Mottl @ 2015-09-09 19:28 ` Mikhail Mandrykin 2015-09-11 15:56 ` Markus Mottl 0 siblings, 1 reply; 8+ messages in thread From: Mikhail Mandrykin @ 2015-09-09 19:28 UTC (permalink / raw) To: caml-list, Markus Mottl; +Cc: OCaml List On Wednesday, September 09, 2015 10:00:34 AM Markus Mottl wrote: > In my case I also need to define a type ('a t) such that I can pass it > on in a functor. The functor body needs to know that invariants are > maintained after each mapping: the skeleton, as captured by t, and > that the same constraints hold for type parameter 'a across mappings. > Only then can the functor body easily map back and forth between > various encodings of the type parameters while maintaining the > skeleton. > Probably the defunctionalization encoding used in the paper on Lightweight Higher-Kinded Polymorphism in OCaml (https://ocamllabs.github.io/higher/lightweight-higher-kinded-polymorphism.pdf) can help in some way. The corresponding library and examples are available at https://github.com/ocamllabs/higher. The implementation used in the library doesn't perfectly fit the case, because the suggested encoding puts the type `t' capturing the type skeleton on an unknown depth inside the list of type constructor applications. I tried the following slight variation on the initial encoding: type ('p, 'f) app = App of 'p * 'f type nil type ('a, 'b) typ = .. (* To be used as `('a, M.t) typ' for different `M.t's *) (* Slight change made to pull `M.t' (see below) from the inside of the parameter list *) (* A couple of helper functors similar to the ones in the library *) module Newtype1 (T : sig type 'a t end) = struct type 'a s = 'a T.t type t type (_, _) typ += Mk : 'a T.t -> (('a, nil) app, t) typ let inj v = Mk v let prj (Mk v) = v end module Newtype2 (T : sig type ('a, 'b) t end) = struct type ('a, 'b) s = ('a, 'b) T.t type t type (_, _) typ += Mk : ('a, 'b) T.t -> (('a, ('b, nil) app) app, t) typ let inj v = Mk v let prj (Mk v) = v end (* The common signature for all the skeletons regardless of the number of type parameters *) module type S = sig type t type ('a, 'b) mapper = ('a, t) typ -> ('b, t) typ type task = (* This is just an example of how the mappers can be used *) Task : ('a, t) typ * ('a, 'b) mapper * ('b, 'c) mapper * ('c, 'a) mapper * (('a, t) typ -> string) -> task end (* The skeleton is captured by M.t .*) (* The passed type is "('a, M.t) typ" *) module Performer (M : S) = struct let perform = function | M.Task (v, m1, m2, m3, s) -> let s = v |> m1 |> m2 |> m3 |> s in prerr_endline s end (* Example skeleton definitions *) module Pair = struct include Newtype2 (struct type ('a, 'b) t = 'a * 'b end) type ('a, 'b) mapper = ('a, t) typ -> ('b, t) typ type task = Task : ('a, t) typ * ('a, 'b) mapper * ('b, 'c) mapper * ('c, 'a) mapper * (('a, t) typ -> string) -> task end module Singleton = struct include Newtype1 (struct type 'a t = 'a end) type ('a, 'b) mapper = ('a, t) typ -> ('b, t) typ type task = Task : ('a, t) typ * ('a, 'b) mapper * ('b, 'c) mapper * ('c, 'a) mapper * (('a, t) typ -> string) -> task end (* Example usages *) let mappers = (fun (x, y) -> int_of_string x, int_of_string y), (fun (x, y) -> float_of_int x, float_of_int y), (fun (x, y) -> string_of_float x, string_of_float y) let pair_tasks = let open Pair in let m1, m2, m3 = mappers in let wrap m x = x |> prj |> m |> inj in let m1, m2, m3 = wrap m1, wrap m2, wrap m3 in [|Task (inj ("1", "2"), m1, m2, m3, fun x -> let a, b = prj x in a ^ ", " ^ b); Task (inj (1, 2), m2, m3, m1, fun x -> let a, b = prj x in string_of_int a ^ ", " ^ string_of_int b)|] let mappers = int_of_string, float_of_int, string_of_float let singleton_tasks = let open Singleton in let m1, m2, m3 = mappers in let wrap m x = x |> prj |> m |> inj in let m1, m2, m3 = wrap m1, wrap m2, wrap m3 in [|Task (inj (1), m2, m3, m1, fun x -> "Just " ^ string_of_int @@ prj x); Task (inj ("1"), m1, m2, m3, fun x -> "Just " ^ prj x)|] module Pair_performer = Performer (Pair) module Single_performer = Performer (Singleton) let () = Pair_performer.perform pair_tasks.(0) (* "1", "2" --> 1, 2 --> 1., 2. --> "1.", "2." --> "1., 2."*) (* Stderr: 1., 2. *) let () = Single_performer.perform singleton_tasks.(1) (* "1" --> 1 --> 1. --> "1." --> "Just 1." *) (* Stderr: Just 1.*) let () = Pair_performer.perform pair_task.(1) (* 1, 2 -> 1., 2. --> "1.", "2." --> Failure: int_of_string *) -- Mikhail Mandrykin Linux Verification Center, ISPRAS web: http://linuxtesting.org e-mail: mandrykin@ispras.ru ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Caml-list] Late adding of type variable constraints 2015-09-09 19:28 ` Mikhail Mandrykin @ 2015-09-11 15:56 ` Markus Mottl 2015-09-11 16:24 ` Mikhail Mandrykin 0 siblings, 1 reply; 8+ messages in thread From: Markus Mottl @ 2015-09-11 15:56 UTC (permalink / raw) To: Mikhail Mandrykin; +Cc: caml-list, OCaml List On Wed, Sep 9, 2015 at 3:28 PM, Mikhail Mandrykin <mandrykin@ispras.ru> wrote: > Probably the defunctionalization encoding used in the paper on Lightweight > Higher-Kinded Polymorphism in OCaml > (https://ocamllabs.github.io/higher/lightweight-higher-kinded-polymorphism.pdf) can help in some way. The corresponding library and examples > are available at https://github.com/ocamllabs/higher. Thanks a lot for this reference and your code. Though my problem still seems slightly different, I found this to be a very interesting paper. I remember reading Reynolds' work on defunctionalization many years ago, and it's fascinating how his work translates from the object to the type language. > The implementation used in the library doesn't perfectly fit the case, because > the suggested encoding puts the type `t' capturing the type skeleton on an > unknown depth inside the list of type constructor applications. I tried the > following slight variation on the initial encoding: I think the main problem for me is not so much capturing the skeleton, which the initial approach does just fine, but that type constraints on the polymorphic variables are maintained with mapper functions. Maybe the more complicated approaches can be combined to solve the problem, but that is likely not trivial. > module Newtype1 (T : sig type 'a t end) = > struct > type 'a s = 'a T.t > type t > type (_, _) typ += Mk : 'a T.t -> (('a, nil) app, t) typ > let inj v = Mk v > let prj (Mk v) = v > end The compiler will warn above that "prj" is not exhaustive. If I understand this correctly, the reasoning is that it's impossible for two GADT constructors to yield the same type so the warning should be spurious. But wouldn't this require making Newtype1 a generative functor (i.e. adding "()")? Otherwise the type "t" could be seen as equivalent if Newtype1 is applied to the same module more than once, which would allow for more than one "Mk" case and could hence cause a runtime match error. I think for the while being I'll just stick to the syntactic approach. Users who want to extend the number of base types to be mapped over would have to include code with "Pair", "Option", etc. within an environment with augmented constraints. -- Markus Mottl http://www.ocaml.info markus.mottl@gmail.com ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [Caml-list] Late adding of type variable constraints 2015-09-11 15:56 ` Markus Mottl @ 2015-09-11 16:24 ` Mikhail Mandrykin 0 siblings, 0 replies; 8+ messages in thread From: Mikhail Mandrykin @ 2015-09-11 16:24 UTC (permalink / raw) To: caml-list, Markus Mottl; +Cc: OCaml List On Friday, September 11, 2015 11:56:33 AM Markus Mottl wrote: > But wouldn't this require making Newtype1 a generative > functor (i.e. adding "()")? Otherwise the type "t" could be seen as > equivalent if Newtype1 is applied to the same module more than once, > which would allow for more than one "Mk" case and could hence cause a > runtime match error. Yes, indeed. module T : sig type t = int end;; module N1 = Newtype0(T);; module N2 = Newtype0(T);; # N1.prj @@ N2.inj 0;; Exception: Match_failure ("//toplevel//", 10, 10). With generative functors this causes a typing error. In this case it's also possible to use even simpler encoding without open types (`('a, ... ('z, nil) app) ...) app) t'): type ('p, 'f) app = App of 'p * 'f type nil module Newtype1 (T : sig type 'a t end) = struct type 'a t = Mk : 'a T.t -> (('a, nil) app) t let inj v = Mk v let prj (Mk v) = v end -- Mikhail Mandrykin Linux Verification Center, ISPRAS web: http://linuxtesting.org e-mail: mandrykin@ispras.ru ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2015-09-11 16:24 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2015-09-04 20:28 [Caml-list] Late adding of type variable constraints Markus Mottl 2015-09-04 22:19 ` Jacques Garrigue 2015-09-04 23:58 ` Markus Mottl 2015-09-07 18:33 ` Mikhail Mandrykin 2015-09-09 14:00 ` Markus Mottl 2015-09-09 19:28 ` Mikhail Mandrykin 2015-09-11 15:56 ` Markus Mottl 2015-09-11 16:24 ` Mikhail Mandrykin
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox