Skip to content

Instantly share code, notes, and snippets.

@swlaschin
Last active December 30, 2015 18:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swlaschin/7865227 to your computer and use it in GitHub Desktop.
Save swlaschin/7865227 to your computer and use it in GitHub Desktop.
==========================
Proposal for automatic integration of F# signature (.fsi) file and "code behind" (.fs) file.
==========================
Currently F# signature files must be maintained separately from the related .fs file,
which is inconvenient at best.
This document describes two different proposals that could alleviate this inconvenience.
* Approach 1: Have the signature file generated from the .fs file
* Approach 2: Have both files editable but keep them in sync
------------------------------------------------------------
Approach 1: Have the signature file generated from the .fs file
------------------------------------------------------------
This approach would use the same model that is used for other generated files, such as dbml,
table adapters, etc.
The signature file would be generated from the .fs file, and would never be edited manually.
The objects in the source file (.fs) would contain attributes that determine whether they
should also be emitted to the signature file.
For example, if the .fs file looks like this:
<example.fs>
/// Comment for OpaqueType
[<SigOpaque>]
type OpaqueType = Opaque of int
/// Comment for ExposedType
[<Sig>]
type ExposedType = { A:int; B:int}
type PrivateType = {A:int}
[<Sig>]
let publicFn x y = x + y
let privateFn x y = x + y
</example.fs>
Then the .fsi file looks like this:
<example.fsi>
/// Comment for OpaqueType
type OpaqueType
/// Comment for ExposedType
type ExposedType = { A:int; B:int}
val publicFn : int -> int -> int
</example.fsi>
Notes:
* The entire .fsi is regenerated every time the .fs file is changed,
so the sig definitions are always in sync with the .fs definitions.
* Only records and unions can be marked as opaque.
* For classes, where some members can be exposed, the Sig attribute would need
to be used on each member explicitly.
* Ideally the relevant comments would also be copied to the sig file
Downsides:
* If the sig file is used as documentation, you might want the order of types to be
different from the .fs file
* You also might want additional documentation in the sig file that is not in the .fs file.
Here's the definition of the attributes:
[<System.AttributeUsage(System.AttributeTargets.Class
||| System.AttributeTargets.Method
||| System.AttributeTargets.Property
||| System.AttributeTargets.Interface
)>]
type SigAttribute() =
inherit System.Attribute()
[<System.AttributeUsage(System.AttributeTargets.Class
||| System.AttributeTargets.Method
||| System.AttributeTargets.Property
||| System.AttributeTargets.Interface
)>]
type SigOpaqueAttribute() =
inherit System.Attribute()
------------------------------------------------------------
Approach 2: Have both files editable but keep them in sync
------------------------------------------------------------
This approach would allow the sig file to be edited independently, but kept in
sync with the .fs file.
The logic would be as follows:
A) If the .fs file is saved, then
* If any type or function in the .fs file is marked with the Sig attribute (as above),
then create the .fsi
* For each type or function in the .fs file marked with the Sig attribute, then
detect the presence of the same type or function in the fsi file. If missing, add it
at the bottom of the .fsi file.
Note that as a result of adding at the bottom, the .fsi file might not compile,
so the author must reorder the definitions as needed. However, once fixed, it should stay fixed.
If the definition of the type or function differs from an existing one in the .fsi file, the old one
is left alone and the new one is added at the bottom. This will cause a compilation error due
to duplicate definitions, but allows easy manual merging of changes.
Updating sig definitions in place with a new version is feasible, but might require code block delimiters
(e.g. comments) to distinguish between editable and generated code.
B) If the .fsi file is saved, then
* For each type or function in the .fsi file, then detect the presence of the same
type or function in the .fs file.
* If missing, add it to the bottom of the .fsi file. For opaque types and functions,
create a non-compilable stub.
Again as a result of adding at the bottom, the .fs file might not compile,
so the author must fix up the code, and reorder the definitions as needed.
@jackfoxy
Copy link

jackfoxy commented Dec 9, 2013

I don't know if this is a realistic use case (since I have never done it), but I can imagine a use case where I would want my test project to have finer-grained access into the code of my app project than the signature files allow. In that case one solution would be to create a shadow project consisting of the .fs files, but not the .fsi files.

I have no experience with IDE add-ins. Is it feasible and desireable to implement a context menu option to generate the signature for the clicked type or let binding in the associated .fsi file, creating a file if it does not already exist in the current project? Relying on manual editing for the rest.

@swlaschin
Copy link
Author

Typically, for code gen, you just edit the properties of the file in VS and pick the code-generator you want to use. Pretty simple! Now that I've got started, I'll have to look into it more...

@7sharp9
Copy link

7sharp9 commented Dec 9, 2013

Im looking for something quick and easy here, maybe programatically running through the compiler, filtering and rendering into a new file. Would that do for initial support?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment