Reputation: 2997
I'm using the JSON type provider to load a JSON file that I created. The minimal input for the type provider looks like this:
{
"conv1": {
"weight": {
"shape": [ 64, 3, 7, 7 ],
"data": [ 1e-30, -0.01077061053365469 ]
}
},
"bn1": {
"eps": 1e-05,
"weight": {
"shape": [ 64 ],
"data": [ 1e-30, 0.2651672959327698 ]
},
"bias": {
"shape": [ 64 ],
"data": [ 1e-30, 0.24643374979496002 ]
}
}
}
Although both weight
parts have the same shape and type, the type provider gives me two different but equivalent types:
type Weight =
inherit IJsonDocument
new : shape: int [] * data: float [] -> Weight
member Data : float []
member JsonValue: JsonValue
member Shape: int []
and
type Weight2 =
inherit IJsonDocument
new : shape: int [] * data: float [] -> Weight2
member Data : float []
member JsonValue: JsonValue
member Shape: int []
First, that's not nice, but maybe it cannot figure out that they mean the same. So I sat down and tried to write a function that unifies both so that I can continue from there—I failed.
My first approach was to use overloading:
type Tensor = {
Data:single[]
Shape:int list
} with
static member Unify1 (w:NN.Weight) = { Data = w.Data |> Array.map single; Shape = w.Shape |> Array.toList }
static member Unify1 (w:NN.Weight2) = { Data = w.Data |> Array.map single; Shape = w.Shape |> Array.toList }
Error FS0438 Duplicate method. The method
Unify1
has the same name and signature as another method in typeTensor
once tuples, functions, units of measure and/or provided types are erased.
Then I tried manual type testing like this:
let unify2 (o:obj) =
match o with
| :? NN.Weight as w -> { Data = w.Data |> Array.map single; Shape = w.Shape |> Array.toList }
| :? NN.Weight2 as w -> { Data = w.Data |> Array.map single; Shape = w.Shape |> Array.toList }
| _ -> failwith "pattern oops"
This variant does not compile because
Error FS3062 This type test with a provided type
JsonProvider<...>.Weight
is not allowed because this provided type will be erased toRuntime.BaseTypes.IJsonDocument
at runtime.
How can I get the type provider to produce a unified type? Alternatively, how would I go about unifying them myself while making the compiler happy?
Upvotes: 2
Views: 161
Reputation: 243051
I agree that the inferred type is not as nice as it could be. The XML type provider has a static parameter Global
which unifies XML elements across the whole document based on their name - so presumably, the JSON provider could do something similar (but it is more tricky, because we would have to identify that two records are "the same" based on either just their fields or the label used in the parent element...). If you are interested in contributing to F# Data, please open an issue to discuss this!
In the meantime, I think a reasonable workaround is to get the underlying JsonValue
and then wrap it back into the provided Weight
type. This will work for both of the weight
records, because they have the same fields:
type NN = JsonProvider<"""{ ... }""">
let processWeight (js:JsonValue) =
let w = NN.Weight(js)
w.Data, w.Shape
let nn = NN.GetSample()
nn.Conv1.Weight.JsonValue |> processWeight
nn.Bn1.Weight.JsonValue |> processWeight
A bit fancier version would be to use static member constraints to access the JsonValue
property in the processWeight
function (so that you can just call nn.Conv1.Weight |> processWeight
), but I did not want to make the sample too complicated.
Upvotes: 1