I’m trying to make a hook that wraps the lenses-ppx library.
This hook compiles
module type Lenses = {
type field<'a>
type state
let set: (state, field<'a>, 'a) => state
let get: (state, field<'a>) => 'a
}
module Make = (Lenses: Lenses) => {
type lensApi<'a> = {
state: Lenses.state,
set: (Lenses.field<'a>, 'a) => unit,
get: Lenses.field<'a> => 'a,
}
let use = (state: Lenses.state) => {
let (state, setState) = React.useState(_ => state)
let get = (state, field) => state->Lenses.get(field)
let set = (state, field, value) => state->Lenses.set(field, value)
let setState = (field, value) => setState(state => set(state, field, value))
{
state: state,
get: get(state),
set: setState,
}
}
}
But I can’t make a context out of the same api
module type Lenses = {
type field<'a>
type state
let set: (state, field<'a>, 'a) => state
let get: (state, field<'a>) => 'a
}
module Make = (Lenses: Lenses) => {
type lensApi<'a> = {
state: Lenses.state,
set: (Lenses.field<'a>, 'a) => unit,
get: Lenses.field<'a> => 'a,
}
let use = (state: Lenses.state): lensApi<'a> => {
let (state, setState) = React.useState(_ => state)
let get = (state, field) => state->Lenses.get(field)
let set = (state, field, value) => state->Lenses.set(field, value)
let setState = (field, value) => setState(state => set(state, field, value))
{
state: state,
get: get(state),
set: setState,
}
}
type context<'a> = option<lensApi<'a>>
let initialContext = None
let context = React.createContext(initialContext)
exception LensHookProviderNotFound
let use = () => {
let fields = React.useContext(context)
try {
fields->Option.getExn
} catch {
| _ => raise(LensHookProviderNotFound)
}
}
module Provider = {
// https://forum.rescript-lang.org/t/how-to-use-react-context-in-rescript/897/5?u=tdfairbrother
include React.Context
let make = React.Context.provider(context)
}
@react.component
let make = (~state, ~children) => {
let api = use(state)
<Provider value=Some(api)> {children} </Provider>
}
}
I get the following error
The type of this module contains type variables that cannot be generalized:
(Lenses: Lenses) => {
type lensApi<'a> = {
state: Lenses.state,
set: (Lenses.field<'a>, 'a) => unit,
get: Lenses.field<'a> => 'a,
}
type context<'a> = option<lensApi<'a>>
let initialContext: option<'a>
let context: React.Context.t<option<'_weak1>>
type exn += LensHookProviderNotFound
let use: unit => '_weak1
module Provider: {
type t<'props> = React.Context.t<'props>
external makeProps: (~value: 'props, ~children: React.element, unit) => {
"children": React.element,
"value": 'props,
} =
"" "#rescript-external"
external provider: t<'props> => React.component<
{"children": React.element, "value": 'props},
> =
"Provider" "#rescript-external"
let make: React.component<
{"children": React.element, "value": option<'_weak1>},
>
}
external makeProps: (
~state: 'state,
~children: 'children,
~?key: string,
unit,
) => {"children": 'children, "state": 'state} =
"" "#rescript-external"
let make: {"children": React.element, "state": unit} => React.element
}
This happens when the type system senses there's a mutation/side-effect,
in combination with a polymorphic value.
Using or annotating that value usually solves it.
I know I can solve it using a new GADT type.
Which means changing the api.
type rec anything =
| Any(Lenses.field<'a>, 'a): anything
| State(Lenses.state): anything
type lensApi = {
state: Lenses.state,
// set(Any(SomePolymorphicField, #HelloWorld))
// set(State(Lenses.state))
set: anything => unit,
}
Is there any way I can annotate the first api without forcing consumers to use a new variant?