Flogex
Flogex

Reputation: 664

Cyclic dependency between modules when creating JSON tree

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

Answers (1)

Koenig Lear
Koenig Lear

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

Related Questions