Hello Here is the latest OCaml Weekly News, for the week of June 21 to 28, 2022.
The mailing list mode of discuss.ocaml.org seems to have been down for a few days, so I had to manually scrape the messages. My apologies if I missed any. Table of Contents ───────────────── An amusing use of first-class modules: reading from plaintext and compressed files Lwt.5.6.0 (and other Lwt packages) Old CWN An amusing use of first-class modules: reading from plaintext and compressed files ══════════════════════════════════════════════════════════════════════════════════ Archive: <https://discuss.ocaml.org/t/an-amusing-use-of-first-class-modules-reading-from-plaintext-and-compressed-files/10073> Chet_Murthy explained ───────────────────── I was recently trying to write a thing in Rust, and having problems, so I wrote the same thing in OCaml, just to make sure that it was doable. I thought I’d post about it, b/c maybe it’s an example of what we’ll find more tractable, once we have modular implicits. The problem: I have both compressed and plaintext files, and I want to run a function over the uncompressed contents. I’d like a combinator that I can apply to the filename and the function, that will do the work of opening the file, calling the function, closing the file, etc. This isn’t so hard. 1. define a type of READER (and two instances for plaintext and gzipped). This is the equivalent of Rust’s “io::BufRead”. ┌──── │ module type READER = │ sig │ type in_channel │ val open_in : string -> in_channel │ val input_char : in_channel -> char │ val close_in : in_channel -> unit │ end │ let stdreader = (module Stdlib : READER) ;; │ let gzreader = (module Gzip : READER) ;; └──── 2. then define a type of “in channel user” (“ICUSER”) and the generic version of it ┌──── │ module type ICUSER = sig │ type in_channel │ val use_ic : in_channel -> unit │ end │ module type GENERIC_ICUSER = functor (R : READER) -> (ICUSER with type in_channel = R.in_channel) └──── 3. then define our function that takes a generic in_channel, and uses it – “Cat” ┌──── │ module Cat(R : READER) : ICUSER with type in_channel = R.in_channel = struct │ type in_channel = R.in_channel │ let use_ic ic = │ let rec rerec () = │ match R.input_char ic with │ c -> print_char c ; rerec () │ | exception End_of_file -> () │ in rerec () │ end └──── 4. And then write our “with_input_file” function, that takes a filename, the function from #3, and applies it to either a normal in_channel, or one produced from a gzip-reader. ┌──── │ let with_input_file fname (module R : GENERIC_ICUSER) = │ let (module M : READER) = │ if Fpath.(fname |> v |> has_ext "gz") then │ gzreader │ else stdreader in │ let open M in │ let ic = M.open_in fname in │ let module C = R(M) in │ try let rv = C.use_ic ic in close_in ic ; rv │ with e -> close_in ic ; raise e └──── And now we can use it: ┌──── │ with_input_file "/etc/passwd" (module Cat) ;; │ with_input_file "foo.gz" (module Cat) ;; └──── Easy-peasy. I don’t remember enough about the modular implicits proposal to remember if this can be cast in the supported language there, so I suppose I should get some version of that code (or the newer versions from others) up-and-running, and see if this can be made to work. hyphenrf asked and Chet_Murthy replied ────────────────────────────────────── can’t we get rid of the `GENERIC_ICUSER' requirement and just ask for functions that take a packed module of type `READER' by that I mean the signature of `with_input_file' becomes `string -> ((module READER) -> 'a) -> 'a' It’s a good question, and as a newbie user of first-class modules, I don’t know the typing rules well enough to answer. But I did try: ┌──── │ let with_input_file' fname f = │ let (module M : READER) = │ if Fpath.(fname |> v |> has_ext "gz") then │ gzreader │ else stdreader in │ let open M in │ let ic = M.open_in fname in │ f (module M : READER) ic └──── and got ┌──── │ File "ioabs.ml", line 96, characters 24-26: │ 96 | f (module M : READER) ic │ ^^ │ Error: This expression has type M.in_channel │ but an expression was expected of type 'a │ The type constructor M.in_channel would escape its scope └──── ETA: I remember in the modular implicits paper, that there was a lot of wrappering code in structs (that didn’t start off in structs). I wonder if that’s evidence that you really do have to “push up” code to the module level in order to make it work. octachron then said ─────────────────── You don’t need modular implicits to simplify your code. Your packed module type is equivalent to: ┌──── │ type channel = { input_char: unit -> char; close_in: unit -> unit } │ type channel_generator = string -> channel └──── We could go fancy and manifest the type with an existential ┌──── │ type 'a channel = │ { open_fn: string -> 'a; input_char: 'a -> char; close_in: 'a -> unit } │ type chan = Any: 'a channel -> chan └──── but this has mainly the advantage to illustrate the fact that you are never using the non-existentially qualified `'a channel' which means that in the current version of your code, modular (explicits or) implicits is not a good fit: we are not selecting a module to provide functions for a type, we have an object (aka an existentially qualified record) with some hidden inner type that we never need to know. c-cube later said ───────────────── I think it’s kind of counter-productive to want a `in_channel' type at all. This is what I’ve been doing, more and more: ┌──── │ module type INPUT = sig │ val read_char : unit -> char │ val read : bytes -> int -> int -> int │ val close : unit -> unit │ end │ │ type input = (module INPUT) │ │ let open_file (filename:string) : input = │ let ic = open_in filename in │ (module struct │ let read_char() = input_char ic │ let read = input ic │ let close() = close_in ic │ end) │ │ │ let do_sth (module IN:INPUT) = │ IC.read_char (); │ IC.read … └──── This behaves like classic objects in other languages and there’s no complicated typing going on (what with each implementation having its own channel type). Lwt.5.6.0 (and other Lwt packages) ══════════════════════════════════ Archive: <https://discuss.ocaml.org/t/ann-lwt-5-6-0-and-other-lwt-packages/10077> raphael-proust announced ──────────────────────── It is a real pleasure to announce the release of Lwt version 5.6.0 as well as Lwt-domain.0.2.0, Lwt-ppx.2.1.0 and Lwt-react.1.2.0. With this release Lwt is now compatible with OCaml version 5. <https://github.com/ocsigen/lwt/releases/tag/5.6.0> Thank you to the many contributors for the fixes, the improvements, and the OCaml5 compatibility! Check out the changelog for full details on each contribution. Old CWN ═══════ If you happen to miss a CWN, you can [send me a message] and I'll mail it to you, or go take a look at [the archive] or the [RSS feed of the archives]. If you also wish to receive it every week by mail, you may subscribe [online]. [Alan Schmitt] [send me a message] <mailto:alan.schm...@polytechnique.org> [the archive] <https://alan.petitepomme.net/cwn/> [RSS feed of the archives] <https://alan.petitepomme.net/cwn/cwn.rss> [online] <http://lists.idyll.org/listinfo/caml-news-weekly/> [Alan Schmitt] <https://alan.petitepomme.net/>
_______________________________________________ caml-news-weekly mailing list caml-news-weekly@lists.idyll.org http://lists.idyll.org/listinfo/caml-news-weekly