rescript-compiler
rescript-compiler copied to clipboard
Inlined modules in functors can lead to (almost) impossible-to-fix type errors in the call site.
I have a small module to help me create deserializer from JSON while validating the structure received. Basically you define a module with describing your data-schema like this and use MakeDeserializer to create the parser.
The following is cut-down version of the functor and the module type:
module Deser = {
module type Serializable = {
type t
let fields: array<string>
}
module MakeDeserializer = (Serializable: Serializable) => {
type t = Serializable.t
let fields = Serializable.fields
external _toNativeType: 'a => t = "%identity"
/// Parse a `Js.Json.t` into `result<t, string>`
let fromJSON = (data: Js.Json.t): result<t, _> => {
Ok("just to show, dont use this value"->_toNativeType)
}
}
}
Of the following three (equivalent) modules that use MakeDeserializer, only the last one compiles:
open Belt
module Fails = {
module Parser = Deser.MakeDeserializer({
type t = {
id: string,
verbose_name: string,
}
let fields = []
})
let parse = raw => raw->Parser.fromJSON->Result.map(res => res.verbose_name) // <-- error
}
module AlsoFails = {
include Deser.MakeDeserializer({
type t = {
id: string,
verbose_name: string,
}
let fields = []
})
let parse = raw => raw->fromJSON->Result.map(res => res.verbose_name) // <-- error
}
module Works = {
module Parser = {
module Defs = {
type t = {
id: string,
verbose_name: string,
}
let fields = []
}
include Deser.MakeDeserializer(Defs)
}
let parse = raw => raw->Parser.fromJSON->Result.map(res => res.verbose_name) // <-- works
}
The error the two failing versions give is:
The record field verbose_name can't be found.
If it's defined in another module or file, bring it into scope by:
- Prefixing it with said module name: TheModule.verbose_name
- Or specifying its type:
let theValue: TheModule.theType = {verbose_name: VALUE}
You can see it live in the playground.
Trying to annotate the argument to Result.map doesn't help:
module Fails = {
module Parser = Deser.MakeDeserializer({
type t = {
id: string,
verbose_name: string,
}
let fields = []
})
let parse = raw => raw->Parser.fromJSON->Result.map((res: Parser.t) => res.verbose_name) // <-- still fails
}