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

Lwt_fmt #548

Merged
merged 4 commits into from
Apr 9, 2018
Merged

Lwt_fmt #548

merged 4 commits into from
Apr 9, 2018

Conversation

Drup
Copy link
Member

@Drup Drup commented Feb 14, 2018

This is a prelude to solving #310. This adds a new module Lwt_fmt which exposes some lwt-powered printf function in the style of Format. The functions return promises and are compatible with all the Format/Fmt combinators. The implementation is not complicated, but uses some fairly subtle Format tricks. I added some tests to ensure that the behavior of flushing and composition was correct.

For now, the module is in lwt_unix. I believe this should not be the case, but I expect @aantron to have better ideas on how and what to split where.

cc @Octachron @c-cube (@dbuenzli) as fellow Format fetishists.


val printf : ('a, Format.formatter, unit, unit Lwt.t) format4 -> 'a
(** Returns a promise that prints on the standard output.
Similar to [Format.printf]. *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{!Format.printf}


val eprintf : ('a, Format.formatter, unit, unit Lwt.t) format4 -> 'a
(** Returns a promise that prints on the standard error.
Similar to [Format.eprintf]. *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{!Format.eprintf}


(** This module bridges the gap between [Format] and [Lwt].
Although it is not required, it is recommended to use this module with the
[Fmt] library.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These toplevel module names could be {!links}.

val fprintf : formatter -> ('a, Format.formatter, unit, unit Lwt.t) format4 -> 'a

val kfprintf :
(Format.formatter -> unit Lwt.t -> 'a) ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to have (formatter -> 'a) rather than (Format.formatter -> unit Lwt.t -> 'a) for the continuation argument: with write_pending exposed, it is still possible to write fprintf from the formatter -> 'a version and it avoids exposing the underlying Format.formatter of the formatter argument.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, that is right, but I'm pretty sure people will forget to call write_pending if you don't do it for them, which will lead to very weird bugs.

I'm trying to think if exposing the Format.formatter is a bad thing or not. It's not really unsafe, but I'm not sure if it's useful or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In term of coherency of the API, I think that in any case it would be better to expose a formatter rather than Format.formatter otherwise kfprintf provides a strange entry point to features only available for Format.formatter (e.g. modifying margin, tags).

I agree that people will probably forget the call to write_pending, even if this could be mitigated by adding Do not forget to call {!write_pending} to the documentation and the limited amount of people using kprintf without reading the documentation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe we can keep the unit Lwt.t and just expose a formatter.

Do you think I need to expose a function to retrieve the Format.formatter too ?

Copy link
Contributor

@Octachron Octachron Feb 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I think that formatter -> unit Lwt.t -> 'a is a good compromise.

Do you think I need to expose a function to retrieve the Format.formatter too ?

I think that It would be nice to avoid it. I believe that letting make_formatter handle the configuration of the formatter is fine since safely modifying the underlying formatter is as hard as safely creating a second formatter with the same output channel. Nevertheless, in this case, it might be interesting to add the part of the Format API not exposed to kfprintf, i.e. the tabulation boxes and pp_print_if_newline.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that point sort of moot, given the existence of %t and %a? Lwt_fmt.fprintf fmt "%t" (fun fmt -> ....) will always work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True,%t and %a should be enough for tabulation boxes and friends.

@hakuch
Copy link

hakuch commented Feb 14, 2018

For what reason would I want the formatting functions to return promises? If I were just reading the documentation for this module, it would not be readily apparent to me that these functions would be blocking.

@Drup
Copy link
Member Author

Drup commented Feb 14, 2018

@hakuch The underlying channel on which the printing is done can be blocking. That is why the Lwt_io module is there.

@hakuch
Copy link

hakuch commented Feb 14, 2018

Thanks, @Drup. That makes sense!

@aantron
Copy link
Collaborator

aantron commented Feb 14, 2018

This seems pretty nice. My only real concern is where it should go. I don't mind ultimately adding a new module to Lwt, but what are people's opinions on that, as opposed to:

  • A separate repo,
  • Somewhere inside Lwt_io,
  • elsewhere?

val stderr : formatter (** Formatter printing on {!Lwt_io.stdout}. *)

val make_formatter :
commit:(unit -> unit Lwt.t) -> fmt:Format.formatter -> formatter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a () argument at the end, so future versions can add optional arguments?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

@c-cube
Copy link
Collaborator

c-cube commented Feb 15, 2018

This looks cool. I think it should go in the core lwt library, provided it's only accessed through module aliases (and thus is of minimal overhead for people who don't use it).

@hcarty
Copy link
Contributor

hcarty commented Feb 15, 2018

I like it as a top-level module slightly more than in Lwt_io. There are no extra requirements so it would be nice to keep it in the main Lwt repository.

@aantron
Copy link
Collaborator

aantron commented Feb 15, 2018

It currently depends on Lwt_io.stdout and .stderr, so that makes it impossible to put it in the core library (in the exact form as it is now). However, in principle, it should be possible to put it in the core and still have some kind of stdout, etc. I believe stdout (EDIT: from Pervasives) is interpreted by both jsoo and BuckleScript.

@Drup
Copy link
Member Author

Drup commented Feb 15, 2018

I strongly think this should be in Lwt itself, and I'm not going to bother creating a package for that alone. I think leftpad-ifying the OCaml ecosystem by creating lot's of 100-lines packages that would be perfectly at home in other packages is a complete waste of time and is very user hostile.
Also, putting it inside lwt means that people might actually use it, which increases the chance we can one day deprecate all the bad functions in Lwt_io.

As for Lwt_io, the problem is that it already has *printf functions, and wrong ones even. We can't really add new formatting functions to it without a lot of name contortions.

Currently, the functions of Lwt_fmt that depends on Lwt_io are of_channel, stdout, stderr, printf, eprintf.
We could split those out, but I'm not sure what's the best way to organize it.

@aantron
Copy link
Collaborator

aantron commented Feb 24, 2018

Very well. Perhaps this is already proposed, but I suggest we merge this as is, putting it into lwt.unix. We can move it to the core lwt if/when Lwt_io is untangled. That change should be backwards-compatible for any users (if we have a release): anyone who uses Lwt_fmt will have to depend on lwt.unix, which depends on lwt, so either way people will be pulling in a package with the Lwt_fmt module.

@Drup
Copy link
Member Author

Drup commented Feb 24, 2018

@aantron That seems like a good direction for me. Let me fix what I discussed with @Octachron above and we will be good to go! :)

@Drup
Copy link
Member Author

Drup commented Apr 7, 2018

I just added a new commit that make the API a bit more regular, as discussed with @Octachron . If CI is happy, this is ready to be merged as far as I'm concerned.

@aantron aantron merged commit e5295cb into ocsigen:master Apr 9, 2018
@aantron
Copy link
Collaborator

aantron commented Apr 9, 2018

Ok, thank you!

@rgrinberg
Copy link
Contributor

Something that's unrelated but still important:

Is there a plan for Lwt to move away from having multiple top level modules per library? It would be really nice if Lwt switched to using (wrapped true) or made all modules apart from Lwt internal (the way dbuenzli does it).

The reason why I'm raising this here is that this PR adds yet another toplevel module which is a bit unfortunate.

@Drup
Copy link
Member Author

Drup commented Apr 12, 2018

@rgrinberg lwt has multiple distinct libraries. If you were to wrap everything, Lwt_fmt would become Lwt_unix.Fmt, not Lwt.Fmt. In the end, it would be strictly more inconsistent than the current situation, and I don't see which gain you would have (all the modules are already prefixed).

@rgrinberg
Copy link
Contributor

If you were to wrap everything, Lwt_fmt would become Lwt_unix.Fmt, not Lwt.Fmt

To me this is a good thing. Yes, this can look a little less aesthetically pleasing, but it streamlines understanding new code for people as you no longer have to think about which library introduces a particular module.

@aantron
Copy link
Collaborator

aantron commented Apr 12, 2018

I want to reorganize the module structure of Lwt eventually, but I have no concrete plan to do it at the moment.

I think putting modules now in lwt.unix under Lwt. can be done with module aliases, but, we may have redesigned the Unix binding by then anyway (#328).

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.

8 participants