Hi,
I am currently working on a back-end framework implemented with Opium.
The main point of this framework is to declare the routes in tree-like structure and then be able to generate a describeApi endpoint that describe all the routes of the framework.
A main point of the framework is that routes are defined with two callback. the hookCallback
is intended to retrieve various attributes from the request (as taskId header, etc…). the callback
is user-defined and it is here that the actual process of the request happens.
The two callbacks are designed to be chained together. As this piece of code shows
microService.ml
class microService = object(s)
method routerDriver (app:Opium.App.t) =
...
method routeDriver (route:RouteManager.routeEndpoint) (app:Opium.App.t)=
App.action route.method_ route.path (fun r -> route.endpoint.callbacks.hookCallback r >>= (fun hookResponse -> route.endpoint.callbacks.callback (r, hookResponse)))
method listen () =
...
end
but what is a route endpoint ? it comes from the routeManager, which is responsible to register all the route and supply them when needed
routeManager.ml
type 'a routeEndpoint = {
path:string;
method_:Opium.Method.t;
endpoint: 'a Route.exposedRoute
}
class ['a] routeManager = object(s)
...
method add (routeObj:'a Route.exposedRoute) = ...
method getRouteList () : 'a routeEndpoint list = ...
...
end
let routeManager = new routeManager;;
the type exposedRoute
comes from the file Route.ml where the structure is like
type 'a callbacks = {
callback: Request.t * 'a -> Response.t Lwt.t;
hookCallback: Request.t -> 'a Lwt.t;
}
type 'a exposedRoute = {
method_: Method.t;
route:string;
description: string;
parameters: Parameters.exposedParameters;
returns: Returns.exposedReturns;
callbacks : 'a callbacks;
componentId: string option;
componentInputId: string option;
componentOutputId: string option;
purpose: string;
nature: string option;
}
class ['a] route method_ route = object(s)
method setDescription d = ...
method setHookCallBack hc =...
method setCallback c =...
method expose () = ...
...
end
The route object is used to defined the route attributes and then build the exposed route type with the expose
method.
- my issue is that I have to chain the hookCallback and user callback. Then the hookCallback produce data consumed by the userCallback. the hookCallback is defined at a higher level in the framework, so I do not know in advance the data it produces.
- My first solution was to used parametric polymorphism. So the types are defined with a type parameter
'a
that matches with the data produced by the hookCallback. - But this solution has two drawbacks. First, all the routes are linked with the same data produced. All the hookCallbacks for every routes have to produce the same data type 'a, which seems restrictive even if it could work.
- Second, it triggers a error in the routeManager :
99 | let routeManager = new routeManager;;
^^^^^^^^^^^^
Error: The type of this expression, '_weak1 routeManager,
contains type variables that cannot be generalized
So I cannot declare the routeManager without the implied 'a
type. I would declare the routeManager here so it is too restrictive.
5. I have made an other attempt with the 'a.
quantifier
route.ml
type 'a callbacks = {
callback: Request.t * 'a -> Response.t Lwt.t;
hookCallback: Request.t -> 'a Lwt.t;
}
type exposedRoute = {
method_: Method.t;
route:string;
description: string;
parameters: Parameters.exposedParameters;
returns: Returns.exposedReturns;
callbacks : 'a.'a callbacks;
componentId: string option;
componentInputId: string option;
componentOutputId: string option;
purpose: string;
nature: string option;
}
but I get an error in my expose method, which I do not get :
68 | callbacks = {callback; hookCallback};
^^^^^^^^^^^^^^^^^^^^^^^^
Error: This field value has type 'a callbacks which is less general than
'a0. 'a0 callbacks
How can I declare a type 'a
that is specific to every route ?