Skip to content

Inline signatures in .ml files#217

Closed
alainfrisch wants to merge 1 commit intoocaml:trunkfrom
alainfrisch:inline_sig
Closed

Inline signatures in .ml files#217
alainfrisch wants to merge 1 commit intoocaml:trunkfrom
alainfrisch:inline_sig

Conversation

@alainfrisch
Copy link
Contributor

It is sometimes convenient to specify the external interface of a compilation unit directly in the .ml file. In particular, for "plugin" modules which are supposed to be dynlinked independently, the external interface is empty, and it can be inconvenient having to specify an empty .mli file. Benefits of having an explicit external interface even in such cases are clear: enable unused-stuff warnings to be triggered; prevent accidental use of internal components by other modules.

One of the recommendations of the LaFoSec study (about the security aspects of OCaml) is precisely to allowing defining the interface of a module directly in the .ml file. This pull request implements this recommendation by allowing an initial sig ... end declaration at the top of implementation files. This is currently done purely as syntactic sugar, mapping this to:

   include (struct ... end : sig ... end)

If one agrees on the benefit of the proposal and on the syntax, one could discuss whether we should represent the new form explicitly in the Parsetree and/or compile the construction specifically (to avoid creating the included structure).

FWIW, this extension has been in use at LexiFi for several years (precisely to restrict the interface of plugins, which is often empty or very small).

@c-cube
Copy link
Contributor

c-cube commented Jul 17, 2015

I like this idea. Many of my modules only have a very small, or empty, interface.

@dbuenzli
Copy link
Contributor

One of the recommendations of the LaFoSec study (about the security aspects of OCaml) is precisely to allowing defining the interface of a module directly in the .ml file.

What's their rationale ?

one could discuss whether we should represent the new form explicitly in the Parsetree and/or compile the construction specifically (to avoid creating the included structure).

What's the effect on build and documentation systems ? E.g. an ocamlc -bin-annot on an .ml won't generate a .cmti file at the moment (which IIRC are needed by codoc).

Personally I tend to dislike the "let's have at least two or three different ways of acheiving the same goal" proposals and don't mind empty or small .mli files.

@alainfrisch
Copy link
Contributor Author

What's their rationale ?

The document I've been able to read suggests this proposal without giving an explicit rationale in terms of security. I guess a reason is that preserving abstraction is good in general (and also for security), and making it less invasive to add (even empty) signatures will encourage users to do so; I could also try to argue that being able to detect dead code is also good for security (detects possible bugs), since the study also encourages the use of dead code detection tools. (But I doubt the authors did the connection between the two.)

E.g. an ocamlc -bin-annot on an .ml won't generate a .cmti file at the moment (which IIRC are needed by codoc).

Nothing is changed. You could even apply an extra .mli on top of the inline signature. But codoc could certainly decide to read the .cmt file when there is no .mli in order to extract the typedtree for the inline signature.

@yminsky
Copy link

yminsky commented Jul 17, 2015

I tend to agree with Daniel here. I'm not excited by having yet another way
of expressing signatures for compilation units.

On Fri, Jul 17, 2015, 8:16 AM alainfrisch [email protected] wrote:

What's their rationale ?

The document I've been able to read suggests this proposal without giving
an explicit rationale in terms of security. I guess a reason is that
preserving abstraction is good in general (and also for security), and
making it less invasive to add (even empty) signatures will encourage users
to do so; I could also try to argue that being able to detect dead code is
also good for security (detects possible bugs), since the study also
encourages the use of dead code detection tools. (But I doubt the authors
did the connection between the two.)

E.g. an ocamlc -bin-annot on an .ml won't generate a .cmti file at the
moment (which IIRC are needed by codoc).

Nothing is changed. You could even apply an extra .mli on top of the
inline signature. But codoc could certainly decide to read the .cmt file
when there is no .mli in order to extract the typedtree for the inline
signature.


Reply to this email directly or view it on GitHub
#217 (comment).

@Drup
Copy link
Contributor

Drup commented Jul 17, 2015

Same here, not convinced. What I would like, however, is the ability to put function signatures before an implementation in order to avoid the type constraints.

val f : t1 -> t2 -> t3
let f x y z = ...

instead of

let f (x : t1) (y : t2) (z : t3) = ...

That would be very helpful in the context of gadts in particular ....

@Chris00
Copy link
Member

Chris00 commented Jul 17, 2015

On Fri, 17 Jul 2015 05:16:04 -0700, alainfrisch wrote:

| What's their rationale ?

The document I've been able to read suggests this proposal without giving an explicit rationale in terms of security. I guess a reason is that preserving abstraction is good in general (and also for security), and making it less invasive to add (even empty) signatures will encourage users to do so; I could also try to argue that being able to detect dead code is also good for security (detects possible bugs), since the study also encourages the use of dead code detection tools. (But I doubt the authors did the connection between the two.)

Wouldn't a warning about missing signatures be more effective in achieving these goals?

@Chris00
Copy link
Member

Chris00 commented Jul 17, 2015

On Fri, 17 Jul 2015 06:03:33 -0700, Gabriel Radanne wrote:

Same here, not convinced. What I would like, however, is the ability to put
function signatures before an implementation in order to avoid the type
constraints.

val f : t1 -> t2 -> t3
let f x y z = ...

instead of

let f (x : t1) (y : t2) (z : t3) = ...

But you can write

let f : t1 -> t2 -> t3 =
  fun x y z -> ...

which is not too far from what you want.

@alainfrisch
Copy link
Contributor Author

Wouldn't a warning about missing signatures be more effective in achieving these goals?

The place we use this feature at LexiFi is for "plugin" modules. Most of them have empty interfaces, and just being able to write "sig end" at the beginning gives a quick way of detecting unused declarations. Moreover, we expose an IDE to end-users, and forcing them to double the number of files (half of them being almost empty) would not be well perceived. This is so true so that, in order the simplify the IDE's interface (and its embedded build manager), it only manages .ml files (not .mli); so that for those end-users, each plugin is made of a single file.

@hcarty
Copy link
Member

hcarty commented Jul 17, 2015

This could also be useful for something like ocamlscript as it would allow you to add sig end to the top of your script/program to get the same benefit as having an empty .mli.

@trefis
Copy link
Contributor

trefis commented Jul 17, 2015

2015-07-17 15:08 GMT+01:00 Hezekiah M. Carty [email protected]:

This could also be useful for something like ocamlscript as it would allow you to add sig end to the top of your script/program to get the same benefit as having an empty .mli.

Alternatively, given the context ocamlscript (and lexify's
alternative) could just consider that if you don't give an .mli then
the interface is empty.

@dbuenzli
Copy link
Contributor

so that for those end-users, each plugin is made of a single file.

So then why even bother these end-users into writing an empty sig end, it seems to me that this whole thing can be handled by your IDE machinery.

@Chris00
Copy link
Member

Chris00 commented Jul 17, 2015

On Fri, 17 Jul 2015 07:09:40 -0700, Thomas Refis wrote:

2015-07-17 15:08 GMT+01:00 Hezekiah M. Carty [email protected]:

This could also be useful for something like ocamlscript as it would allow you to add sig end to the top of your script/program to get the same benefit as having an empty .mli.

Alternatively, given the context ocamlscript (and lexify's alternative) could just consider that if you don't give an .mli then the interface is empty.

IMHO this is cleaner but would need to be turned on by a compiler flag to be backward compatible.

@alainfrisch
Copy link
Contributor Author

So then why even bother these end-users into writing an empty |sig end|,
it seems to me that this whole thing can be handled by your IDE machinery.

Well, sometimes the signature is not empty (plugins can depend on each
other, it's just not the common case).

@xavierleroy
Copy link
Contributor

After all these years, I'm still keen on the idea that the signature (= the user's view) goes in a .mli file distinct from the .ml implementation file. I know other languages do it differently, with a single source file and visibility modifiers, but I still find the 2-file approach healthier. For one thing, the .mli is the natural place to put user documentation that is clearly separate from implementation comments.

Concerning the LaFoSec study, yes, for high-assurance software it makes sense to have an explicit interface for every source file so that internal definitions don't leak out unintentionally. But it is trivial to check for existence of .mli interfaces in a code review tool. I don't see a clear case for changing the language here.

@bluddy
Copy link
Contributor

bluddy commented Jul 20, 2015

I agree that a separate file is good for many reasons, but it also comes with drawbacks, such as higher maintenance costs. One thing that would be nice to have is an attribute that hides a toplevel expression when an mli is implicitly generated -- something like [@no_export]. Additionally, a toplevel [@no_export] would make the whole file local by default, which could be undone piecemeal by [@export] on expressions.

All of these attributes would be ignored if an mli file was provided, or perhaps could generate warnings/errors if the attributes mismatched the mli content.

@murmour
Copy link
Contributor

murmour commented Dec 1, 2015

Regarding the "high maintenance cost" of separate interface files: shouldn't this be handled by the IDE tooling? The tooling approach works great for modern industrial languages. Can't see why it won't work for OCaml. No need to complicate the language itself.

@mshinwell
Copy link
Contributor

I agree with @xavierleroy . However, I would be in favour of a warning when the .mli file is missing, if a certain compiler flag were provided.

@bobzhang
Copy link
Member

I like @bluddy 's idea, having [@@@no_export] and [@@no_export], note that sometimes it is quite hard to write signature files in cases when you use a lot of polymorphic variants and objects.
There are several other use cases for a no_export attributes

  1. Plugin/Unit test
  2. Help dead code elimination, it's quite common that people write module L = List while forget adding a mli file, then this module is exported, my current javascript backend can eliminate this overhead while still have to keep L's whole definition since it is exported

@damiendoligez damiendoligez added this to the 4.04-or-later milestone Dec 23, 2015
@mshinwell mshinwell removed this from the 4.04 milestone Sep 7, 2016
@damiendoligez
Copy link
Member

We discussed this PR at the developer meeting two days ago and the consensus is to reject this PR. On the other hand, it would be a good idea to have a flag to force the compiler to raise an error when the .mli file is missing, but this is the topic for an other PR (https://caml.inria.fr/mantis/view.php?id=7493).

stedolan pushed a commit to stedolan/ocaml that referenced this pull request Feb 20, 2020
lthls pushed a commit to lthls/ocaml that referenced this pull request Jul 20, 2020
mshinwell added a commit to mshinwell/ocaml that referenced this pull request Jul 30, 2020
mshinwell added a commit to mshinwell/ocaml that referenced this pull request Jul 30, 2020
mshinwell added a commit to mshinwell/ocaml that referenced this pull request Jul 30, 2020
lthls pushed a commit to lthls/ocaml that referenced this pull request Sep 23, 2020
lthls pushed a commit to lthls/ocaml that referenced this pull request Sep 23, 2020
lthls pushed a commit to lthls/ocaml that referenced this pull request Sep 23, 2020
lthls pushed a commit to lthls/ocaml that referenced this pull request Sep 23, 2020
lthls pushed a commit to lthls/ocaml that referenced this pull request Sep 24, 2020
lthls pushed a commit to lthls/ocaml that referenced this pull request Sep 24, 2020
EmileTrotignon pushed a commit to EmileTrotignon/ocaml that referenced this pull request Jan 12, 2024
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.