Reputation: 664
I develop a library that creates JSON Hyperschema for a given class using reflection. I chose F# as programming language, but don't have much experience with it.
Like others (this or this question), I face the problem that I would like to create a cyclic dependency between modules, but I can't...
The library's main function should be given a Type
and returns a JsonSchema
.
type JsonSchema = {
Type: JsonType
Links: seq<LinkDescriptor>
}
A JsonType
is a primitive, array, object, or nullable. But an array has an items
field of the type JsonSchema
, and an object has a list of JsonProperties
, which again reference JsonSchema
.
type JsonType =
| JsonBoolean
| JsonInteger
| JsonNumber
| JsonString
| JsonArray of items: JsonSchema
| JsonObject of properties: seq<JsonProperty>
| Nullable of underlyingType: JsonType
type JsonProperty = {
Schema: JsonSchema
Name: string
}
Then there is the LinkDescriptor
type used in JsonSchema
, which, again, has a field of type JsonSchema
.
type LinkDescriptor = {
Rel: string
Href: UriTemplate
HrefSchema: JsonSchema option
}
If I took this approach and just wrote all types in one file and linked them with and
, I would be forced to make the functions that create the types mutually recursive.
let rec getJsonType (t: Type): JsonType =
// Handle primitive types
if (t.IsArray) then
let itemsType = t.GetElementType()
let schema = {
Type = getJsonType itemsType
Links = getLinksForType itemsType
}
JsonArray schema
else JsonObject <| getJsonProperties t
and getJsonProperties (t: Type): seq<JsonProperty> =
getPublicProperties t
|> Seq.map (fun p -> {
Name = p.Name
Schema = {
Type = getJsonType p.PropertyType
Links = getLinksForProperty p
}
})
and getLinksForType (t: Type): seq<LinkDescriptor> =
getLinkAttributes t
|> Seq.map (fun attr -> {
Rel = attr.Rel
Href = attr.Href
HrefSchema =
if isNull attr.HrefSchemaType
then None
else Some { Type = getJsonType attr.HrefSchemaType; Links = Seq.empty<LinkDescriptor> }
})
In reality, these functions are a bit more complex, which is why I want them to be in different modules.
I have read Refactoring to remove cyclic dependencies, but don't know how parameterization could be applied in my case.
The only idea I could come up with is the following: Instead of creating the complete JSON tree all at once, I only create one layer at a time. That is, the JsonType
stores the underlying System.Type
:
type JsonType =
| JsonBoolean
// ...
| JsonArray of Type
| JsonObject of Type
| Nullable of underlyingType: JsonType
Then I could create a JsonType
module that contains functions like getItemsSchema
, which retrieves the JsonSchema
on the basis of the stored type of the JsonArray
. The same goes for getObjectProperties
.
But I am not really happy with this approach, because it feels a bit like a leaky abstraction.
Do you have any suggestions how I can improve above code in order to get rid of the cyclic dependencies?
Upvotes: 0
Views: 100
Reputation: 2436
Given that the logic of your functions is tightly coupled I think they should remain in the same module.
However, if you still want to break them up you can do as follows:
Imagine that you have the following set of functions dependent on each other:
let rec f x = g x + h x
and g x = f x
and h x = f x
In order to declare each one of the in their own module, you could define explicitly their function dependencies. For example function f is dependent on function g and h:
module moduleF =
let _f (g:int->int) (h:int->int) x = g x + h x
You can do the same for g and h, while having them implemented in different modules:
module moduleG =
let _g (f:int->int) x = f x
module moduleH =
let _h (f:int->int) x = f x
Finally, you can integrate them all in a fourth module, providing the necessary dependencies for each of your functions.
module Final =
open ModuleF
open ModuleG
open ModuleH
let rec f x = _f g h x
and g x = _g f x
and h x = _h f x
Upvotes: 2