Are F# module signature files mostly useless, unlike OCaml, because F# doesn't have functors?
What are use cases for F# module signature files?
Are F# module signature files mostly useless, unlike OCaml, because F# doesn't have functors?
What are use cases for F# module signature files?
Clarification for future readers: this answer was written for the original question, before it was updated. The original question was:
Are F# signatures mostly useless, unlike OCaml, because F# doesn't have functors?
What are use cases for F# signatures?
Though F# doesn't have OCaml modules and functors, it does have interfaces and objects, which provide similar, though a bit more limited functionality. These do require type signatures, because type inference doesn't work on them. For example:
type MyIntf =
abstract member m : sting -> int
let f i = i.m "hello"
// ^ error: cannot lookup member "m" on object "i" of indeterminate type
Notice that I've already needed a type annotation just to define the interface, since there is no other source of type information in the absence of an implementation for member m
. But even besides that, the usage of my interface also doesn't work without an explicit type.
While it's not clear that inferring types for objects and interfaces is actually impossible, F# just doesn't attempt to do it. The official F# policy is, as long as you're purely functional, you get HM type interference, but as soon as you get into objects and members, you're on your own, the compiler won't help you.
To make the above function f
work, I have to annotate the type of the i
parameter:
let f (i : MyIntf) = i.m "hello" // works now
Besides this technical requirement, type annotations are, of course, immensely valuable as documentation (which is guaranteed to not go out of sync), as well as barriers for runaway type inference.
One good use case is when you have a big module with a lot of functions where only a few of them you want to expose.
module Library =
let helperFunc1 x y = ...
let helperFunc2 x y = ...
...
let helperFuncN x y = ...
let apiFunc1 x y = ...
let apiFunc2 x y = ...
Now recall the access control rules:
If no access specifier is used, the default is public, except for let bindings in a type, which are always private to the type.
So to achieve your goal you could pollute 95% of the module with overriding default accessibility OR you could create this signature file that would only specify 5% of the module you want to expose:
module Library =
val apiFunc1: x: int -> y: int -> int
val apiFunc2: x: int -> y: int -> int
And yes, apart from that you get a clear API picture. This approach with signature files is heavily used in the F# compiler code.