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

Install toplevel printers using [@@toplevel_printer] #10559

Closed
wants to merge 2 commits into from

Conversation

nojb
Copy link
Contributor

@nojb nojb commented Aug 5, 2021

Following discussion in #10557, this PR is to discuss the patch proposed in #7770 written originally by @jeremiedimino to automatically install values annotated with [@@toplevel_printer] as toplevel printers.

Fixes #7770

@nojb
Copy link
Contributor Author

nojb commented Aug 6, 2021

One issue I noticed while playing with this is that that the printer only takes effect after the first time the module (or rather, its .cmi) is loaded:

nojebar@PERVERSESHEAF:~/ocaml/local/bin$ cat foo.mli
type t
val t : t
val print : Format.formatter -> t -> unit [@@toplevel_printer]
nojebar@PERVERSESHEAF:~/ocaml/local/bin$ cat foo.ml
type t = int * int
let t = (12,34)
let print ppf (a, b) = Format.fprintf ppf "(%d,%d)" a b
nojebar@PERVERSESHEAF:~/ocaml/local/bin$ ./ocamlc -c foo.mli foo.ml
nojebar@PERVERSESHEAF:~/ocaml/local/bin$ rlwrap ./ocaml foo.cmo
        OCaml version 4.14.0+dev0-2021-06-03

# Foo.t;;
- : Foo.t = <abstr>
# Foo.t;;
- : Foo.t = (12,34)

This is because the .cmi is loaded when evaluating the first phrase and is only "acknowledged" afterwards (see the second call to acknowledge_new_cmis in the patch).

@nojb
Copy link
Contributor Author

nojb commented Aug 6, 2021

A second issue, when using #show to show the signature of a module that has not been loaded:

nojebar@PERVERSESHEAF:~/ocaml/local/bin$ rlwrap ./ocaml
        OCaml version 4.14.0+dev0-2021-06-03

# #show Foo;;
module Foo :
  sig type t val t : t val print : Format.formatter -> t -> unit end
Fatal error: exception Topcommon.Undefined_global("Foo")

@Drup
Copy link
Contributor

Drup commented Sep 8, 2021

I'm highly in favor of the feature. I haven't looked at the implementation just yet.

@nojb I don't remember this limitation in the utop implementation, is it there ? If it isn't, why ?

@nojb
Copy link
Contributor Author

nojb commented Sep 9, 2021

@nojb I don't remember this limitation in the utop implementation, is it there ? If it isn't, why ?

I confess I am not a utop user :) I'll give it a try and report back.

@nojb
Copy link
Contributor Author

nojb commented Sep 9, 2021

@nojb I don't remember this limitation in the utop implementation, is it there ? If it isn't, why ?

I confess I am not a utop user :) I'll give it a try and report back.

It turns out that in order to implement completion utop eagerly evaluates what you are typing, so by the time you "actually" evaluate the phrase (ie you type ;;) the required .cmi has already been loaded.

@xavierleroy
Copy link
Contributor

I agree it would be nice to have .cma libraries that auto-install printers when loaded in the toplevel REPL. However, I think attaching an attribute to the declaration of the printer function is not the best way to go about it, if only because .cmi files are loaded on demand.

An example for an alternative design: extend the .cma file format (and the ocamlc -a command) so that it can contain a script (source code?) to be executed by the toplevel REPL when the library is loaded.

@dbuenzli
Copy link
Contributor

dbuenzli commented Nov 6, 2021

An example for an alternative design: extend the .cma file format (and the ocamlc -a command) so that it can contain a script (source code?) to be executed by the toplevel REPL when the library is loaded.

The problem with scripts is that they easily go uncompiled. I played with such a scheme in omod where after loading an object file with basename o an existing corresponding o_top_init.ml side file is loaded. Since I never got to complicate my build systems to check that these files evaluate correctly I did sometimes release broken *_top_init.ml files.

I think it's also nicer if these things can be contained to the compilation units they concern. So pushing what you propose a bit further what about:

  1. Add an API in Sys that can register printers (in the spirit of Printexc.register_printer) and that the toplevel machinery can redefine with it's own fun to do its magic.
  2. Introduce a special function name and sig say val ocaml_toplevel_init : unit -> unit that toplevels calls after they have loaded the compilation unit (and a new cli flag to prevent its evaluation).

This for a module M that would end-up looking something like:

type t = …
…
let pp ppf v = …
let ocaml_toplevel_init () = Sys.register_printer pp

@dbuenzli
Copy link
Contributor

dbuenzli commented Nov 6, 2021

2. Introduce a special function name and sig say val ocaml_toplevel_init : unit -> unit that toplevels calls after they have loaded the compilation unit (and a new cli flag to prevent its evaluation).

Actually if we only care about printers and not making other effects specially for the toplevel we could even eschew the special symbol and boil down the thing to 1. and maybe a new flag in ocaml and load directives to prevent printer install. Then things boil down to:

type t = …
…
let pp ppf v = …
let () = Sys.register_printer pp

And that Sys.register_printer is a nop in regular programs.

@nojb
Copy link
Contributor Author

nojb commented Nov 6, 2021

Actually if we only care about printers and not making other effects specially for the toplevel we could even eschew the special symbol and boil down the thing to 1. and maybe a new flag in ocaml and load directives to prevent printer install. Then things boil down to:

type t = …
…
let pp ppf v = …
let () = Sys.register_printer pp

And that Sys.register_printer is a nop in regular programs.

This seems like a simple solution that does not require any changes to the infrastructure.

@nojb
Copy link
Contributor Author

nojb commented Nov 30, 2021

  1. Add an API in Sys that can register printers

One issue is that the printer-registering machinery in the Toplevel needs to have access to the concrete type of the printer being installed, and Sys.register_printer would have a polymorphic type: (Format.formatter -> 'a -> unit) -> unit.

An example for an alternative design: extend the .cma file format

What about extending the .cmo file format to record the list of toplevel printers (assuming they are annotated with an attribute like [@@toplevel_printer] in the implementation file)?

@dbuenzli
Copy link
Contributor

an attribute like [@@toplevel_printer] in the implementation file)?

It seems that's what @let-def does in https://github.com/let-def/autoprinter

@nojb
Copy link
Contributor Author

nojb commented Sep 25, 2022

I would still like to have something like this, but the patch doesn't work in its current form. Closing.

@nojb nojb closed this Sep 25, 2022
@nojb nojb deleted the toplevel_printers branch September 25, 2022 17:17
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

Successfully merging this pull request may close these issues.

Improved API for toplevel printers
4 participants