From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Authentication-Results: plum.tunbury.org; dkim=pass (1024-bit key; unprotected) header.d=inria.fr header.i=@inria.fr header.a=rsa-sha256 header.s=dc header.b=sTcmcnmG; dkim-atps=neutral Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=192.134.164.83; helo=mail2-relais-roc.national.inria.fr; envelope-from=caml-list-owner@inria.fr; receiver=tunbury.org Received: from mail2-relais-roc.national.inria.fr (mail2-relais-roc.national.inria.fr [192.134.164.83]) by plum.tunbury.org (Postfix) with ESMTP id DDE1B4008D for ; Wed, 24 Jun 2026 04:19:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=inria.fr; s=dc; h=date:from:to:message-id:mime-version: content-transfer-encoding:subject:reply-to:sender:list-id: list-help:list-subscribe:list-unsubscribe:list-post: list-owner:list-archive; bh=nt53ZVgVKmXH5trnWPAUpg6yNIfZ5COz4KlhAEJltpY=; b=sTcmcnmGp6zxZWzQ/4Ezzp8NqeSUTQ8kRDewoXjzeQt5EHNkaCbD8MHm bxJ6/5PcyFzP7FyFAXAi9S06o4PLZYZxnaQErQl8A3MPrTeRKhFPlFIP0 IdRTgN2k5uKF1rymDWshx7iD19yti557Z/Hu+RdjjLlTzm83wI29E4lLT w=; X-CSE-ConnectionGUID: lYgcy8jSROuSGoAuP4AsCg== X-CSE-MsgGUID: gxogyEagRU+sIE0lwCrZhQ== Authentication-Results: mail2-relais-roc.national.inria.fr; dkim=none (message not signed) header.i=none; spf=SoftFail smtp.mailfrom=caml-list-owner@inria.fr; spf=None smtp.helo=postmaster@prod-sympa-app.inria.fr Received-SPF: SoftFail (mail2-relais-roc.national.inria.fr: domain of caml-list-owner@inria.fr is inclined to not designate 128.93.162.27 as permitted sender) identity=mailfrom; client-ip=128.93.162.27; receiver=mail2-relais-roc.national.inria.fr; envelope-from="caml-list-owner@inria.fr"; x-sender="caml-list-owner@inria.fr"; x-conformance=spf_only; x-record-type="v=spf1"; x-record-text="v=spf1 ip4:128.93.142.0/24 ip4:192.134.164.0/24 ip4:128.93.162.160 ip4:128.93.162.3 ip4:128.93.162.88 ip4:89.107.174.7 mx ~all" Received-SPF: None (mail2-relais-roc.national.inria.fr: no sender authenticity information available from domain of postmaster@prod-sympa-app.inria.fr) identity=helo; client-ip=128.93.162.27; receiver=mail2-relais-roc.national.inria.fr; envelope-from="caml-list-owner@inria.fr"; x-sender="postmaster@prod-sympa-app.inria.fr"; x-conformance=spf_only X-IronPort-AV: E=Sophos;i="6.24,221,1774306800"; d="scan'208";a="283091214" Received: from prod-sympa-app.inria.fr ([128.93.162.27]) by mail2-relais-roc.national.inria.fr with ESMTP; 24 Jun 2026 06:19:55 +0200 Received: by prod-sympa-app.inria.fr (Postfix, from userid 990) id C5D8780E3D; Wed, 24 Jun 2026 06:19:55 +0200 (CEST) Received: from mail2-relais-roc.national.inria.fr (mail2-relais-roc.national.inria.fr [192.134.164.83]) by prod-sympa-app.inria.fr (Postfix) with ESMTP id 0FA7D80E3D for ; Wed, 24 Jun 2026 06:19:48 +0200 (CEST) X-CSE-ConnectionGUID: 75aO+zTnRheOJRsLvdLsww== X-CSE-MsgGUID: 5+BiAZiwReiNKyP5tajtJg== IronPort-SDR: 6a3b5ae3_z2BoNLfvl5lDLCxpXPlS7BdCPdHDrqSoVv58jbw5KqRlyMK G20FxSnM8j7wMDWNAjboAFGsy3Rwz/HQqL5EIBg== X-ThreatScanner-Verdict: Negative X-IPAS-Result: =?us-ascii?q?A0G9BgAZVDtqg3IDJ0JahRqBCl8zBwhJhFiDT4UsiHmgH?= =?us-ascii?q?A8BAwENMR4CBAEBAQIBA4FLkQECHwYBBDQTAQIEAwIDAQEBAQEBAQEBAQELA?= =?us-ascii?q?QEBBAEBAQIBAQIEAwEBAQECEAEBAQFASYZPDYJbO4IWLB4SgQQECwENAQE4N?= =?us-ascii?q?AImAhKDYQGCJE8CAbMOfzOBAYIMAQEGgQjbPoFSCYEfLohbAYV1AYJNgi8nG?= =?us-ascii?q?4INhAiJFIJqgiaBDIQcjGaBShwDWSwBVRMXCwcFgWYDgQZuMh2BIz4XNFgbB?= =?us-ascii?q?wWBHYFugQSFAiMfAzl/gT+BJGRmFTA1gQIRHwqBNQMLbT03FBuOFBcPggktg?= =?us-ascii?q?QIHKCd6CmN5pSifVIE+hCyMHJYHqmyZCKlCgX8jgVx9Q4JnCUcDGQ+OOoYzg?= =?us-ascii?q?knJNjU1PQIHAgcBDAEDC4VDjFmBSwEB?= IronPort-PHdr: A9a23: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 m3VtNE8+zlOfcGIyT9mUlxJsDkZ3eq2 IronPort-Data: A9a23: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 IronPort-HdrOrdr: A9a23:eiUIEKDCF78HRRXlHemc55DYdb4zR+YMi2TD83oBLCC9Ffbo8/ xG/c5rtyMc7QxhP03I9ursBEDtex/hHP1OkOosFJOrXAyjl22sKYd98ZKn/juIIU3DH4xmtJ uIGpIWYLabMbEQt6jHCWeDfOrJrLS8gcaVuds= X-Talos-CUID: 9a23:59tY2mB5DBAweOL6E3Y6/WlXCPwjSH3Q61XqOGumBUJyV6LAHA== X-Talos-MUID: =?us-ascii?q?9a23=3A8VKGLw+s8ubjhXnr82l054WQf5oryaWHOkESq5g?= =?us-ascii?q?HneqVDXFsMjWBrjviFw=3D=3D?= X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.24,221,1774306800"; d="scan'208";a="283091133" X-IronPort-Outbreak-Status: No, level 0, Unknown - Unknown X-MGA-submission: =?us-ascii?q?MDFw0WmPFDyKySMStWARAc8DzJitH6AEPFsIYl?= =?us-ascii?q?CKz3nq8SP5Kjdju5W4zHS8VP0XZHdRbSXjQP6V5vhUWIGkaAAOILgRHz?= =?us-ascii?q?zAPrEBOgqz69ntALNcchwxell+n2174YHPDk+sUorL2V20PV6pOFGq+0?= =?us-ascii?q?wS4XLvstGnxp6hEVL8tywnYg=3D=3D?= Received: from mail1.g3.pair.com ([66.39.3.114]) by mail2-smtp-roc.national.inria.fr with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Jun 2026 06:19:46 +0200 Received: from mail1.g3.pair.com (localhost [127.0.0.1]) by mail1.g3.pair.com (Postfix) with ESMTP id E5D0A1600BB; Wed, 24 Jun 2026 00:19:43 -0400 (EDT) Received: from Run.localnet (118.149.159.133.rev.vmobile.jp [133.159.149.118]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mail1.g3.pair.com (Postfix) with ESMTPSA id C4D591600B3; Wed, 24 Jun 2026 00:19:42 -0400 (EDT) Date: Wed, 24 Jun 2026 13:16:24 +0900 From: oleg@okmij.org To: caml-list@inria.fr Message-ID: Mail-Followup-To: oleg@okmij.org, caml-list@inria.fr MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit X-Scanned-By: mailmunge 3.10 on 66.39.3.114 Subject: [Caml-list] Modular explicits in pre-OCaml 5.5 Reply-To: oleg@okmij.org X-Loop: caml-list@inria.fr X-Sequence: 19537 Errors-To: caml-list-owner@inria.fr Precedence: list Precedence: bulk Sender: caml-list-request@inria.fr X-no-archive: yes List-Id: List-Help: , List-Subscribe: , List-Unsubscribe: , List-Post: List-Owner: List-Archive: Archived-At: One of the notable features of the just announced OCaml 5.5 are module-dependent functions, a.k.a. modular explicits. It is a welcome addition: it lets us express higher-ranked types in the simplest way, and is particularly good for typed tagless-final code. It should be mentioned however that modular explicits could be done before, with little or no hassle. Perhaps there is merit to remind of that old trick~-- especially because it works also with statically unknown modules (which are out of scope for OCaml 5.5 modular explicits). As the running example we re-use the pretty-printing example in the OCaml 5.5 announcement (hereafter, Ann55). We start however with a simpler example: pretty-printing a set generated by the `Set.Make` functor. The example follows the pattern of the pp_map function from Ann55. It is simpler than printing a map because it involves no higher-rank types. let pp_set (type a s) (module M: Set.S with type elt = a and type t = s) (pp_elt:Format.formatter->a->unit) (ppf:Format.formatter) (set:s) = if M.is_empty set then Format.fprintf ppf "ø" else let pp_sep ppf () = Format.fprintf ppf ",@ " in Format.fprintf ppf "@[{@ %a@ }@]" (Format.pp_print_seq ~pp_sep pp_elt) (M.to_seq set) It is almost literally the pp_map example from Ann55, with set substituted for map. The main difference is type annotations. This is not a bug: I insist on writing signatures or explicit type annotations for all top-level definitions (except, perhaps, the most trivial). The annotations could be simplified if we introduce type ('e,'s) set = (module Set.S with type elt = 'e and type t = 's) type 'a printer = Format.formatter->'a->unit The example then reads let pp_set : type e s. (e,s) set -> e printer -> s printer = fun (module M) pp_elt ppf set -> (* ... as before ... *) The signature tells at a glance what pp_set is doing (which is one of the benefits of signatures: it is not just for, and not mainly for, the compiler.) We can use pp_set just like pp_map was used in the Ann55 example: module String_set = Set.Make(String) let () = let m = String_set.of_list ["Zero"; "Zero"; "One"; "Un"] in let pp_str = Format.pp_print_string in Format.printf "%a@." (pp_set (module String_set) pp_str) m Our rendition of modular explicits extends beyond statically known modules like String_set. For example, (* Abstract set of elements of types 'e. The implementation is abstract *) type 'e aset = (module Set.S with type elt = 'e) let f : unit -> int aset = fun () -> if Random.bool () then (module Set.Make(Int)) else (module Set.Make(struct type t = int let compare x y = - Int.compare x y end)) The function f randomly returns one of two distinct Set implementations (the Set.t types are not compatible). As an application, we print a list of integers as a set (automatically sorting and removing duplicates): let print_as_set : type a. a aset -> a printer -> a list printer = fun (module M) pp ppf lst -> pp_set (module M) pp ppf (M.of_list lst) let _ = Format.printf "%a@." (print_as_set (f ()) Format.pp_print_int) [1;2;3;1] The result is indeed either "{ 1, 2, 3 }" or "{ 3, 2, 1 }", depending on how die is cast. Let us now tackle the pp_map example from Ann55. It is challenging because of the higher-rank type of the map type 'a Map.S.t. It is tempting to define the module type as type ('k,'v,'m) map = (module Map.S with type key = 'k and type 'a t = 'm constraint 'a = 'v) Alas, it doesn't work: 2 | (module Map.S with type key = 'k and type 'a t = 'm constraint 'a = 'v) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Error: Syntax error: invalid package type: parametrized types are not supported Package types and their `with' constraints come with many restrictions, which is deeply unfortunate. We have to resort to an encoding: module type maps = sig include Map.S type v type mt val of_mt : mt -> v t val to_mt : v t -> mt end type ('k,'v,'m) map = (module maps with type key = 'k and type v = 'v and type mt = 'm) One should mention that this is a strictly more general type of maps: it supports specialized implementations for particular combination of keys and values (e.g., if 'v is bool, we can use Set as the underlying structure.) The pretty-printer of maps is the same as in the Ann55, with two additions: two occurrences of M.of_t. let pp_map : type k v m. (k,v,m) map -> k printer -> v printer -> m printer = fun (module M) pp_key pp_v ppf set -> if M.is_empty (M.of_mt set) then Format.fprintf ppf "ø" else let pp_sep ppf () = Format.fprintf ppf ",@ " in let pp_binding ppf (k,v) = Format.fprintf ppf "@[%a@ =@ %a@]" pp_key k pp_v v in Format.fprintf ppf "@[{@ %a@ }@]" (Format.pp_print_seq ~pp_sep pp_binding) (M.to_seq (M.of_mt set)) It applies to statically *unknown* modules however, unlike pp_map in Ann55 type ('k,'v) amap = (module maps with type key = 'k and type v = 'v) let string_map : type a. unit -> (string,a) amap = fun () -> (module struct include Map.Make(String) type v = a type mt = v t let of_mt = Fun.id let to_mt = Fun.id end) let () = let md = string_map () in let module M = (val (md : (string,int) amap)) in let m = M.of_list ["Zero", 0; "One", 1] in let pp_str = Format.pp_print_int in Format.printf "%a@." (pp_map (module M) Format.pp_print_string pp_str) (M.to_mt m) I should mention that the functions like to_mt come naturally in case of tagless-final interpreters: these are the observation functions. For example: module type lc = sig type 'a repr val int : int -> int repr val lam : ('a repr -> 'b repr) -> ('a -> 'b) repr val app : ('a -> 'b) repr -> ('a repr -> 'b repr) type obst type obs val observe : obst repr -> obs end type ('a,'obs) lc = (module (lc with type obs = 'obs and type obst = 'a)) let ex1 : type obs. (int,obs) lc -> obs = fun (module M) -> let open M in let t1 = app (lam (fun x -> x)) (int 1) in observe t1 In conclusion, it would be great if one day the restrictions on package types were relaxed.