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

A way to avoid automatic export of declaration in inferred signatures #5764

Closed
vicuna opened this issue Sep 22, 2012 · 28 comments
Closed

A way to avoid automatic export of declaration in inferred signatures #5764

vicuna opened this issue Sep 22, 2012 · 28 comments

Comments

@vicuna
Copy link

vicuna commented Sep 22, 2012

Original bug ID: 5764
Reporter: @bobzhang
Status: acknowledged (set by @alainfrisch on 2013-09-03T09:20:33Z)
Resolution: open
Priority: normal
Severity: feature
Category: language features
Tags: github
Monitored by: @glondu @hcarty

Bug description

for example file A.ml have a lot of generated code beginning with
__ocaml__blabla, in most cases programmers don't want to expose such functions in A.mli(and it maybe dangerous), so for the automatically generated interface file, it would be nice that the default behavior should hide them, user can write A.mli by hand and expose them if they wish. This also applies generated code by ocaml(lex|yacc)

@vicuna
Copy link
Author

vicuna commented Sep 23, 2012

Comment author: @hcarty

A more backwards compatible and perhaps less shocking approach could be to have 'ocamlc -i foo.ml' not include these values. This could be the default or something which could be set through a command line flag.

@vicuna
Copy link
Author

vicuna commented Sep 23, 2012

Comment author: @bobzhang

hcarty: the problem is that sometimes you don't want to write .mli file, and simply want to hide functions beginning with '__' by default. Your solution still requires people to write or generate .mli file. If user writes .mli file, they can still expose functions beginning with '__', personally, I think the benefit is immediate.
To maintain backwards compatibility, I agree that may be an compilation flag '-no_implicit' will solve the problem
Another thing needs to be considered is functor

module M (A:sig..end) = struct
  let ...
end

I hope that the signature inferred for functor M hide those functions beginning with '__' as well

@vicuna
Copy link
Author

vicuna commented Sep 29, 2012

Comment author: @damiendoligez

For the record, I don't like this idea, it looks too much like an ad-hoc hack.

@vicuna
Copy link
Author

vicuna commented Oct 7, 2012

Comment author: @bobzhang

Why do you think it's an hack? this is just similar to add an underscore _ to remove the warnings, not theoretical interest, but very beneficial to the ocaml programmers.
To be more disciplined, perhaps we could add something similar to F#, private access modifier?

let private f = blabla?

@vicuna
Copy link
Author

vicuna commented Oct 7, 2012

Comment author: meyer

I don't like the idea either. Useful thing to do is to have an update mode for mli file. The compiler will just try to update (equiped with command line option) changed entries in mli file.

@vicuna
Copy link
Author

vicuna commented Oct 7, 2012

Comment author: meyer

I raised #5777

@vicuna
Copy link
Author

vicuna commented Oct 7, 2012

Comment author: @bobzhang

in practice, you never want to read .mli file, the output is too large for generated code. can you explain a bit why you don't like the idea?

@vicuna
Copy link
Author

vicuna commented Oct 7, 2012

Comment author: meyer

mli files are used to expose the selected interface from the ml files and not other way round. If we start marking functions or types inside ml file to make the generated mli files the result will be the same not generating mli files in the first place. More over people will start abusing underscores in the code, and the underscored functions in my taste don't look very appealing.

Reading mli file is fine, it's not needed to hbe type checked anymore, more over the ml files are usually bigger than mli files, especially in case when we use module types. In the case of the updating the mli file when is needed actually the file will be smaller because the modules that's been not changed will have module type.

@vicuna
Copy link
Author

vicuna commented Oct 8, 2012

Comment author: @gasche

A natural solution to your use-case would be to have the extension also generate the .mli file. However, I'm not sure that can be done conveniently (in my experience camlp4 is good at filtering/transforming a given file, but not that convenient for spitting out other files on the side; still the Ocsigen people do that iirc.), and it could be challenging to do compositionally.

Maybe you could, in the .ml file, generate a module along with its interface, and then include it?

Instead of generating:

let __foo = bar
let foo = foobar __foo

you would generate

include (struct 
    let __foo = bar
    let foo = foobar __foo
  end : sig
    val foo : foo_t
  end)

@vicuna
Copy link
Author

vicuna commented Oct 8, 2012

Comment author: @bobzhang

yes, I know this can be 80% done in p4, but given the fact that just tens of lines patches in the compiler can solve the problem once and for all, it's not worth the complexity to use p4. Any feature can be abused, i.e, Obj.magic, personally, I can not buy meyer's argument.

@vicuna
Copy link
Author

vicuna commented Oct 10, 2012

Comment author: lavi

If I sometimes would like such kind of hiding expressed in ml files, I do not think that the name of a value should impact its visibility.

So a cleaner solution would be e.g. to reuse private keyword:

private let x = ...

and the same for type, class, module...

@vicuna
Copy link
Author

vicuna commented Oct 10, 2012

Comment author: @bobzhang

yes, as I said
let private or private let both sound reasonable

@vicuna
Copy link
Author

vicuna commented Oct 30, 2012

Comment author: turpin

I prefer "private let", because then you may also add "private type", "private module"...

Also, if this was added to the language, allowing this in any structure (not just toplevel modules) would be less ad hoc.

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: berenger

I think, by default everything is private.
You want to be explicit only about what you
want to make visible outside of your module
(what's in the .mli file).

Hence the export or public keyword.

I don't like so much __ because it's some code.
You can't read it loud so it's not as explicit
as using a keyword.
Also, __ as some C taste.
public or private as some C++ taste.
Only export (as in Haskell I think), has a nice taste. ;)

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: @bobzhang

berenger, that will break all existing code, definitely not we want.
I am for such a syntax:

private let
private type
private module

module = struct
private let a = 3
end

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: berenger

I did not know about this private keyword, I'll study its use.
I also did not give any thought at backward compatibility, that's true.

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: @alainfrisch

Hongbo: what would be the type of the following structure?

struct
private type t = A | B
type s = t * t
end

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: turpin

Error: type construcor t escapes its scope

This problem seems similar to the following:

let _ =
let module M = struct type t = A end in
M.A

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: @bobzhang

For private let and private module, remove its corresponding signature
For private type, it's locally transparent, but its signature is abstract datatype.
Any signature involves a private module will result in a compilation error.

IMO, the output should be
type t
type s = t * t

This will not change the typing rules, just add a filter after type checking.
so you can still play with
private type t = private blabla..

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: @alainfrisch

If you remove submodules, you can break well-formedness of type. How do you deal with:

private module X = struct type t = A | B end
type s = X.t

?

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: turpin

The same "escapes its scope" sort of errors seems reasonable. I also have encountered other messages like "... cannot be eliminated from the signature", but I don't remember in which cases. I don't see any strong theoretical issue, and I believe the compiler already addresses similar problems for existing constructs.

@vicuna
Copy link
Author

vicuna commented Oct 31, 2012

Comment author: @bobzhang

As I said, any signature involves a private module type will result in a compilation error.

If a module M is private, then any type declaration involves M will result in an error.

private module X = struct type t = A | B end
type s = X.t
will result in a compilation error

@vicuna
Copy link
Author

vicuna commented Sep 3, 2013

Comment author: @alainfrisch

While modifying the Bisect instrumenter, we had introduced a bug which would have easily been avoided with the required feature. The modified instrumenter added a global value declaration at the top of the compilation unit, to be used at many places in the unit. The resulting code looks like:

=============================================
let __bisect_table = Bisect.get_table "/foo/bar/my_module.mf"

....
....

   <a lot of references to __bisect_table>

=============================================

The __bisect_table identifier ends up in signature inferred for units without an explicit .mli file. The bug then appears for a unit which starts with an "open My_module", where My_module doesn't have an explicit interface. The instrumenter would insert a local "let __bisect_table = ..." above this open statement, and this declaration would then be hidden by the open. All local uses of the __bisect_table identifier would refer to the one exported unintentionally from My_module.

The fix is to use a globally unique name for the identifier (e.g. some hash of the current unit).


Now, if people agree that the feature would be convenient to have in some form:

  1. Instead of using a naming convention (which looks ad hoc) or introducing a new language construct (which might be overkill, considering the request mostly makes sense for generated code), one could use an attribute (as introduced by "extension_points"). This would break the current property that the compiler does not give any meaning to these attributes, though. Opinions?

  2. On the implementation side, while it's very straightforward to deal with value declarations (just filter the "bound_idents" list in the Pstr_value case of Typemod.type_structure), it's less clear how to proceed with e.g. type and module declarations (one needs to apply a well-formedness check on the resulting signature, and I don't know if it already exists in the current code base).

@vicuna
Copy link
Author

vicuna commented Sep 3, 2013

Comment author: @alainfrisch

(I've changed the "Summary" to make it less specific.)

@vicuna
Copy link
Author

vicuna commented Jul 11, 2016

Comment author: @alainfrisch

Same bug striked again: LexiFi/landmarks#2

@vicuna
Copy link
Author

vicuna commented Jul 11, 2016

Comment author: @alainfrisch

#682

@yawaramin
Copy link
Contributor

Can we consider this solved in OCaml 4.08 with the open struct let foo = 0 end construct?

@bobzhang
Copy link
Member

bobzhang commented Mar 19, 2020

Yes, feel free to close this issue

@trefis trefis closed this as completed Mar 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants