Polymorphic structure lost through module functors? (?)

Not sure about the topic but hoping someone can help.
Trying to build a module type for the relationship between concrete structures (records of values), and “partial” structures (records of optional values, e.g.) I have this code working without a module type collecting things together, but when I build the module type, in functions at a top level, the compiler can no longer deduce the structure of an inner, monomorphic, parameter.

Anyone who can parse the example, how would you describe that, btw?

[more: Is it just structure lost through polymorphic variant?]

Thanks
Alex Mouton

Example code here:

https://rescript-lang.org/try?code=LYewJgrgNgpgBAFwJ4Ad4AUCGAnBBLTKOAXjgG8AoORVeFHfQqmtOMGAMzwDs8EZmsBHGwxu7bAC449XASLEAfHBAp8IbgB52XXv0UUAvhQqhIsOAGUIabCXLNkrAM4JsEAMYIIozQHJZTGBlUjJA4Bh+KTgAnCDjaid4YVJXdy8fGE0OKBBMBANmM2gMBnlpLDlCOAB3PgALFngdHj54UhSHakTaGTLq1LdPb19VdS0cvIKDbqa2Tlb+ewRmaiERMQl7AAoUaVlGKABKEmVKWepnOoQPRpQAOnDImDtzi4AfKxAI3ZOlL5+YTiESi+0MR1W3U+ADkNO1lLDuAILnAEt00cZjBQknA0sNMv5nDYXiErAhMOIcGA4J9rLZtn4ibYIRR1k8ojs9rihhlfHSXvdKodHv0oIo-mdmFc+LcZF0aVZidhts4JQCYCqRdggs9sBDqLTyZTsNT-ojkaijBQgA

I’m not sure exactly what you’re trying to do, but a lot of the time people accidentally make modules too abstract by prematurely annotating them with module types. Remove the module type annotation: module Partial = {, and the example compiles fine.

thats what concerns me, yeah.
Doesn’t sound like a good story for rescript though? “Module types considered broken”?

That’s the intended behaviour. The module type in your example is doing exactly what it’s supposed to do, hiding everything that’s not explicitly listed in the module type. In this case, it is hiding the definition of the partial type, so the outside world only sees partial as an abstract type, so it doesn’t allow accessing the parameter record field.

If you explicitly publish the definition of the partial type, then it works as well:

module Partial: Partial
  with type definite = t
  and type partial = structure<option<float>> = {

The reason I didn’t suggest this in the first place is it’s not necessary. The compiler automatically infers a general-enough module type to make it work, even without the annotation.

Aha! Thats the thing there. thanks.

Im sure its in the archives of the n languages below this one, but is there a way then to just assert a module provides some interface without making it opaque? Thats how it appears to me in descriptions of module functors and to find that its not that way surprises me.

That the compiler can figure it out is great but coming at this from the “Languages are for people” side there is some expression I’m looking for here.

The compiler checks this for you automatically. Use the module where needed and you get a type error if the module doesn’t conform to the required module type. People take advantage of this flexibility in the OCaml world. E.g., you can do:

module StringMap = Map.Make(String)

This gives you a map (dictionary) with string keys, just by passing the standard library’s String module to the Map.Make functor. The String module doesn’t need to explicitly declare itself compatible with the functor’s input module type; it just needs to provide the required items (type t and let compare: (t, t) => int) to be automatically compatible. The compiler checks that.

P.S. Go actually has a very similar ability with its interfaces. So do TypeScript and Flow.

Still surprising that i can only find out my module conforms to the module type of that functor by application, and giving the module the type changes its nature strongly, but Ill have to think about it more.

Thanks as ever for your time Yawaramin

A

1 Like

Well, I should clarify. You can of course annotate the module with a module type, and the compiler will check if it conforms to the type. Just like normal typechecking e.g. let x: int = () will be a type error. Module type errors will be thrown in the same way.

But my point is, the idea that you need to check the conformance manually, that is what I’m saying is not necessary. The compiler will check it for you at the callsite, just like all other typechecking. If it doesn’t conform, it’s easy to fix.

It feels like you’re using functors where you wouldn’t need them necessarily? Would you mind explaining your usecase a little bit more?

For future reference: You’re not using Polymorphic Variants but just a Variant with type parameters.
And they are not really your problem. As @yawaramin pointed out, your module type is the “culprit”.

Let me try to explain: Beside other things module types are used for data hiding. Eg to hide implementation details from the modules interface.
A primitive example:

module type PublicInterface = {
  type t
  let fromString: string => t
  let doSomething: t => unit
}

module Implementation: PublicInterface = {
  type t = string
  let fromString = x => x // could be implemented via an identity external 
  let doSomething = x => Js.log(x)
}

If you want to use a value of type t of this example, it’s only possible to use the defined functions in the Implementation module. The module type says this module has a type named t, but I won't say anything about it's shape. This is data hiding.
So even though the implementation aliases t to string you can’t use this knowledge outside of the Implemetation module.

And that’s exactly what’s happening in your example. Your module type hides the concrete type from the outside world. If you want to have the concrete type publicly known, you need to add it to your module type. Either by having a fixed type defined or by using the with clause.

1 Like

Or by not annotating the module with a module type, and letting the compiler infer the ‘full’ module type without any hiding.

1 Like