8

Are F# module signature files mostly useless, unlike OCaml, because F# doesn't have functors?

What are use cases for F# module signature files?

nau
  • 1,145
  • 8
  • 20
  • Do you mean use cases for `signature files`? On the implementation side, signatures are indispensable as type signatures (how else to declare interfaces) and also as function and value signatures, to fill potential gaps in type inference. – kaefer Mar 04 '18 at 19:39
  • Yes, I meant `signature files`, I've already updated the question. Thank you. – nau Mar 04 '18 at 23:13

2 Answers2

5

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.

Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172
  • Thank you. I should have asked about module signature files from the very beginning, didn't see this would obviously cause ambiguity with type annotations. Although your answer is relevant anyway, I would wait for a few days before accepting it, maybe someone will come up with something interesting. – nau Mar 06 '18 at 12:56
1

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.

psfinaki
  • 1,814
  • 15
  • 29