layout: post
title: “Refactoring to remove cyclic dependencies”
description: “Cyclic dependencies: Part 2”
categories: [Design]
seriesId: “Dependency cycles”
seriesOrder: 2


In the previous post, we looked at the concept of dependency cycles, and why they are bad.

In this post, we’ll look at some techniques for eliminating them from your code. Having to do this may seem annoying at first, but really, you’ll come to appreciate that in the long run, “it’s not a bug, it’s a feature!”

Classifying some common cyclic dependencies

Let’s classify the kinds of dependencies you’re likely to run into. I’ll look at three common situations, and for each one, demonstrate some techniques for dealing with them.

First, there is what I will call a “method dependency”.

  • Type A stores a value of type B in a property
  • Type B references type A in a method signature, but doesn’t store a value of type A

Second, there is what I will call a “structural dependency”.

  • Type A stores a value of type B in a property
  • Type B stores a value of type A in a property

Finally, there is what I will call an “inheritance dependency”.

  • Type A stores a value of type B in a property
  • Type B inherits from type A

There are, of course, other variants. But if you know how to deal with these, you can use the same techniques to deal with the others as well.

Three tips on dealing with dependencies in F#

Before we get started, here are three useful tips which apply generally when trying to untangle dependencies.

Tip 1: Treat F# like F#.

Recognize that F# is not C#. If you are willing to work with F# using its native idioms, then it is normally very straightforward to avoid circular dependencies by using a different style of code organization.

Tip 2: Separate types from behavior.

Since most types in F# are immutable, it is acceptable for them to be “exposed” and “anemic”, even. So in a functional design it is common to separate the types themselves from the functions that act on them. This approach will often help to clean up dependencies, as we’ll see below.

Tip 3: Parameterize, parameterize, parameterize.

Dependencies can only happen when a specific type is referenced. If you use generic types, you cannot have a dependency!

And rather than hard coding behavior for a type, why not parameterize it by passing in functions instead? The List module is a great example of this approach, and I’ll show some examples below as well.

Dealing with a “method dependency”

We’ll start with the simplest kind of dependency — what I will call a “method dependency”.

Here is an example.

  1. module MethodDependencyExample =
  2. type Customer(name, observer:CustomerObserver) =
  3. let mutable name = name
  4. member this.Name
  5. with get() = name
  6. and set(value) =
  7. name <- value
  8. observer.OnNameChanged(this)
  9. and CustomerObserver() =
  10. member this.OnNameChanged(c:Customer) =
  11. printfn "Customer name changed to '%s' " c.Name
  12. // test
  13. let observer = new CustomerObserver()
  14. let customer = Customer("Alice",observer)
  15. customer.Name <- "Bob"

The Customer class has a property/field of type CustomerObserver, but the CustomerObserver class has a method which takes a Customer as a parameter, causing a mutual dependency.

Using the “and” keyword

One straightforward way to get the types to compile is to use the and keyword, as I did above.

The and keyword is designed for just this situation — it allows you to have two or more types that refer to each other.

To use it, just replace the second type keyword with and. Note that using and type, as shown below, is incorrect. Just the single and is all you need.

  1. type Something
  2. and type SomethingElse // wrong
  3. type Something
  4. and SomethingElse // correct

But and has a number of problems, and using it is generally discouraged except as a last resort.

First, it only works for types declared in the same module. You can’t use it across module boundaries.

Second, it should really only be used for tiny types. If you have 500 lines of code between the type and the and, then you are doing something very wrong.

  1. type Something
  2. // 500 lines of code
  3. and SomethingElse
  4. // 500 more lines of code

The code snippet shown above is an example of how not to do it.

In other words, don’t treat and as a panacea. Overusing it is a symptom that you have not refactored your code properly.

Introducing parameterization

So, instead of using and, let’s see what we can do using parameterization, as mentioned in the third tip.

If we think about the example code, do we really need a special CustomerObserver class? Why have we restricted it to Customer only? Can’t we have a more generic observer class?

So why don’t we create a INameObserver<'T> interface instead, with the same OnNameChanged method, but the method (and interface) parameterized to accept any class?

Here’s what I mean:

  1. module MethodDependency_ParameterizedInterface =
  2. type INameObserver<'T> =
  3. abstract OnNameChanged : 'T -> unit
  4. type Customer(name, observer:INameObserver<Customer>) =
  5. let mutable name = name
  6. member this.Name
  7. with get() = name
  8. and set(value) =
  9. name <- value
  10. observer.OnNameChanged(this)
  11. type CustomerObserver() =
  12. interface INameObserver<Customer> with
  13. member this.OnNameChanged c =
  14. printfn "Customer name changed to '%s' " c.Name
  15. // test
  16. let observer = new CustomerObserver()
  17. let customer = Customer("Alice", observer)
  18. customer.Name <- "Bob"

In this revised version, the dependency has been broken! No and is needed at all. In fact, you could even put the types in different projects or assemblies now!

The code is almost identical to the first version, except that the Customer constructor accepts a interface, and CustomerObserver now implements the same interface. In fact, I would argue that introducing the interface has actually made the code better than before.

But we don’t have to stop there. Now that we have an interface, do we really need to create a whole class just to implement it? F# has a great feature called object expressions which allows you to instantiate an interface directly.

Here is the same code again, but this time the CustomerObserver class has been eliminated completely and the INameObserver created directly.

  1. module MethodDependency_ParameterizedInterface =
  2. // code as above
  3. // test
  4. let observer2 = {
  5. new INameObserver<Customer> with
  6. member this.OnNameChanged c =
  7. printfn "Customer name changed to '%s' " c.Name
  8. }
  9. let customer2 = Customer("Alice", observer2)
  10. customer2.Name <- "Bob"

This technique will obviously work for more complex interfaces as well, such as that shown below, where there are two methods:

  1. module MethodDependency_ParameterizedInterface2 =
  2. type ICustomerObserver<'T> =
  3. abstract OnNameChanged : 'T -> unit
  4. abstract OnEmailChanged : 'T -> unit
  5. type Customer(name, email, observer:ICustomerObserver<Customer>) =
  6. let mutable name = name
  7. let mutable email = email
  8. member this.Name
  9. with get() = name
  10. and set(value) =
  11. name <- value
  12. observer.OnNameChanged(this)
  13. member this.Email
  14. with get() = email
  15. and set(value) =
  16. email <- value
  17. observer.OnEmailChanged(this)
  18. // test
  19. let observer2 = {
  20. new ICustomerObserver<Customer> with
  21. member this.OnNameChanged c =
  22. printfn "Customer name changed to '%s' " c.Name
  23. member this.OnEmailChanged c =
  24. printfn "Customer email changed to '%s' " c.Email
  25. }
  26. let customer2 = Customer("Alice", "x@example.com",observer2)
  27. customer2.Name <- "Bob"
  28. customer2.Email <- "y@example.com"

Using functions instead of parameterization

In many cases, we can go even further and eliminate the interface class as well. Why not just pass in a simple function that is called when the name changes, like this:

  1. module MethodDependency_ParameterizedClasses_HOF =
  2. type Customer(name, observer) =
  3. let mutable name = name
  4. member this.Name
  5. with get() = name
  6. and set(value) =
  7. name <- value
  8. observer this
  9. // test
  10. let observer(c:Customer) =
  11. printfn "Customer name changed to '%s' " c.Name
  12. let customer = Customer("Alice", observer)
  13. customer.Name <- "Bob"

I think you’ll agree that this snippet is “lower ceremony” than either of the previous versions. The observer is now defined inline as needed, very simply:

  1. let observer(c:Customer) =
  2. printfn "Customer name changed to '%s' " c.Name

True, it only works when the interface being replaced is simple, but even so, this approach can be used more often than you might think.

A more functional approach: separating types from functions

As I mentioned above, a more “functional design” would be to separate the types themselves from the functions that act on those types. Let’s see how this might be done in this case.

Here is a first pass:

  1. module MethodDependencyExample_SeparateTypes =
  2. module DomainTypes =
  3. type Customer = { name:string; observer:NameChangedObserver }
  4. and NameChangedObserver = Customer -> unit
  5. module Customer =
  6. open DomainTypes
  7. let changeName customer newName =
  8. let newCustomer = {customer with name=newName}
  9. customer.observer newCustomer
  10. newCustomer // return the new customer
  11. module Observer =
  12. open DomainTypes
  13. let printNameChanged customer =
  14. printfn "Customer name changed to '%s' " customer.name
  15. // test
  16. module Test =
  17. open DomainTypes
  18. let observer = Observer.printNameChanged
  19. let customer = {name="Alice"; observer=observer}
  20. Customer.changeName customer "Bob"

In the example above, we now have three modules: one for the types, and one each for the functions. Obviously, in a real application, there will be a lot more Customer related functions in the Customer module than just this one!

In this code, though, we still have the mutual dependency between Customer and CustomerObserver. The type definitions are more compact, so it is not such a problem, but even so, can we eliminate the and?

Yes, of course. We can use the same trick as in the previous approach, eliminating the observer type and embedding a function directly in the Customer data structure, like this:

  1. module MethodDependency_SeparateTypes2 =
  2. module DomainTypes =
  3. type Customer = { name:string; observer:Customer -> unit}
  4. module Customer =
  5. open DomainTypes
  6. let changeName customer newName =
  7. let newCustomer = {customer with name=newName}
  8. customer.observer newCustomer
  9. newCustomer // return the new customer
  10. module Observer =
  11. open DomainTypes
  12. let printNameChanged customer =
  13. printfn "Customer name changed to '%s' " customer.name
  14. module Test =
  15. open DomainTypes
  16. let observer = Observer.printNameChanged
  17. let customer = {name="Alice"; observer=observer}
  18. Customer.changeName customer "Bob"

Making types dumber

The Customer type still has some behavior embedded in it. In many cases, there is no need for this. A more functional approach would be to pass a function only when you need it.

So let’s remove the observer from the customer type, and pass it as an extra parameter to the changeName function, like this:

  1. let changeName observer customer newName =
  2. let newCustomer = {customer with name=newName}
  3. observer newCustomer // call the observer with the new customer
  4. newCustomer // return the new customer

Here’s the complete code:

  1. module MethodDependency_SeparateTypes3 =
  2. module DomainTypes =
  3. type Customer = {name:string}
  4. module Customer =
  5. open DomainTypes
  6. let changeName observer customer newName =
  7. let newCustomer = {customer with name=newName}
  8. observer newCustomer // call the observer with the new customer
  9. newCustomer // return the new customer
  10. module Observer =
  11. open DomainTypes
  12. let printNameChanged customer =
  13. printfn "Customer name changed to '%s' " customer.name
  14. module Test =
  15. open DomainTypes
  16. let observer = Observer.printNameChanged
  17. let customer = {name="Alice"}
  18. Customer.changeName observer customer "Bob"

You might be thinking that I have made things more complicated now — I have to specify the observer function everywhere I call changeName in my code. Surely this is worse than before? At least in the OO version, the observer was part of the customer object and I didn’t have to keep passing it in.

Ah, but, you’re forgetting the magic of partial application! You can set up a function with the observer “baked in”, and then use that function everywhere, without needing to pass in an observer every time you use it. Clever!

  1. module MethodDependency_SeparateTypes3 =
  2. // code as above
  3. module TestWithPartialApplication =
  4. open DomainTypes
  5. let observer = Observer.printNameChanged
  6. // set up this partial application only once (at the top of your module, say)
  7. let changeName = Customer.changeName observer
  8. // then call changeName without needing an observer
  9. let customer = {name="Alice"}
  10. changeName customer "Bob"

But wait… there’s more!

Let’s look at the changeName function again:

  1. let changeName observer customer newName =
  2. let newCustomer = {customer with name=newName}
  3. observer newCustomer // call the observer with the new customer
  4. newCustomer // return the new customer

It has the following steps:

  1. do something to make a result value
  2. call the observer with the result value
  3. return the result value

This is completely generic logic — it has nothing to do with customers at all. So we can rewrite it as a completely generic library function. Our new function will allow any observer function to “hook into” into the result of any other function, so let’s call it hook for now.

  1. let hook2 observer f param1 param2 =
  2. let y = f param1 param2 // do something to make a result value
  3. observer y // call the observer with the result value
  4. y // return the result value

Actually, I called it hook2 because the function f being “hooked into” has two parameters. I could make another version for functions that have one parameter, like this:

  1. let hook observer f param1 =
  2. let y = f param1 // do something to make a result value
  3. observer y // call the observer with the result value
  4. y // return the result value

If you have read the railway oriented programming post, you might notice that this is quite similar to what I called a “dead-end” function. I won’t go into more details here, but this is indeed a common pattern.

Ok, back to the code — how do we use this generic hook function?

  • Customer.changeName is the function being hooked into, and it has two parameters, so we use hook2.
  • The observer function is just as before

So, again, we create a partially applied changeName function, but this time we create it by passing the observer and the hooked function to hook2, like this:

  1. let observer = Observer.printNameChanged
  2. let changeName = hook2 observer Customer.changeName

Note that the resulting changeName has exactly the same signature as the original Customer.changeName function, so it can be used interchangably with it anywhere.

  1. let customer = {name="Alice"}
  2. changeName customer "Bob"

Here’s the complete code:

  1. module MethodDependency_SeparateTypes_WithHookFunction =
  2. [<AutoOpen>]
  3. module MyFunctionLibrary =
  4. let hook observer f param1 =
  5. let y = f param1 // do something to make a result value
  6. observer y // call the observer with the result value
  7. y // return the result value
  8. let hook2 observer f param1 param2 =
  9. let y = f param1 param2 // do something to make a result value
  10. observer y // call the observer with the result value
  11. y // return the result value
  12. module DomainTypes =
  13. type Customer = { name:string}
  14. module Customer =
  15. open DomainTypes
  16. let changeName customer newName =
  17. {customer with name=newName}
  18. module Observer =
  19. open DomainTypes
  20. let printNameChanged customer =
  21. printfn "Customer name changed to '%s' " customer.name
  22. module TestWithPartialApplication =
  23. open DomainTypes
  24. // set up this partial application only once (at the top of your module, say)
  25. let observer = Observer.printNameChanged
  26. let changeName = hook2 observer Customer.changeName
  27. // then call changeName without needing an observer
  28. let customer = {name="Alice"}
  29. changeName customer "Bob"

Creating a hook function like this might seem to add extra complication initially, but it has eliminated yet more code from the main application, and once you have built up a library of functions like this, you will find uses for them everywhere.

By the way, if it helps you to use OO design terminology, you can think of this approach as a “Decorator” or “Proxy” pattern.

Dealing with a “structural dependency”

The second of our classifications is what I am calling a “structural dependency”, where each type stores a value of the other type.

  • Type A stores a value of type B in a property
  • Type B stores a value of type A in a property

For this set of examples, consider an Employee who works at a Location. The Employee contains the Location they work at, and the Location stores a list of Employees who work there.

Voila — mutual dependency!

Here is the example in code:

  1. module StructuralDependencyExample =
  2. type Employee(name, location:Location) =
  3. member this.Name = name
  4. member this.Location = location
  5. and Location(name, employees: Employee list) =
  6. member this.Name = name
  7. member this.Employees = employees

Before we get on to refactoring, let’s consider how awkward this design is. How can we initialize an Employee value without having a Location value, and vice versa.

Here’s one attempt. We create a location with an empty list of employees, and then create other employees using that location:

  1. module StructuralDependencyExample =
  2. // code as above
  3. module Test =
  4. let location = new Location("CA",[])
  5. let alice = new Employee("Alice",location)
  6. let bob = new Employee("Bob",location)
  7. location.Employees // empty!
  8. |> List.iter (fun employee ->
  9. printfn "employee %s works at %s" employee.Name employee.Location.Name)

But this code doesn’t work as we want. We have to set the list of employees for location as empty because we can’t forward reference the alice and bob values..

F# will sometimes allow you to use the and keyword in these situation too, for recursive “lets”. Just as with “type”, the “and” keyword replaces the “let” keyword. Unlike “type”, the first “let” has to be marked as recursive with let rec.

Let’s try it. We will give location a list of alice and bob even though they are not declared yet.

  1. module UncompilableTest =
  2. let rec location = new Location("NY",[alice;bob])
  3. and alice = new Employee("Alice",location )
  4. and bob = new Employee("Bob",location )

But no, the compiler is not happy about the infinite recursion that we have created. In some cases, and does indeed work for let definitions, but this is not one of them!
And anyway, just as for types, having to use and for “let” definitions is a clue that you might need to refactor.

So, really, the only sensible solution is to use mutable structures, and to fix up the location object after the individual employees have been created, like this:

  1. module StructuralDependencyExample_Mutable =
  2. type Employee(name, location:Location) =
  3. member this.Name = name
  4. member this.Location = location
  5. and Location(name, employees: Employee list) =
  6. let mutable employees = employees
  7. member this.Name = name
  8. member this.Employees = employees
  9. member this.SetEmployees es =
  10. employees <- es
  11. module TestWithMutableData =
  12. let location = new Location("CA",[])
  13. let alice = new Employee("Alice",location)
  14. let bob = new Employee("Bob",location)
  15. // fixup after creation
  16. location.SetEmployees [alice;bob]
  17. location.Employees
  18. |> List.iter (fun employee ->
  19. printfn "employee %s works at %s" employee.Name employee.Location.Name)

So, a lot of trouble just to create some values. This is another reason why mutual dependencies are a bad idea!

Parameterizing again

To break the dependency, we can use the parameterization trick again. We can just create a parameterized vesion of Employee.

  1. module StructuralDependencyExample_ParameterizedClasses =
  2. type ParameterizedEmployee<'Location>(name, location:'Location) =
  3. member this.Name = name
  4. member this.Location = location
  5. type Location(name, employees: ParameterizedEmployee<Location> list) =
  6. let mutable employees = employees
  7. member this.Name = name
  8. member this.Employees = employees
  9. member this.SetEmployees es =
  10. employees <- es
  11. type Employee = ParameterizedEmployee<Location>
  12. module Test =
  13. let location = new Location("CA",[])
  14. let alice = new Employee("Alice",location)
  15. let bob = new Employee("Bob",location)
  16. location.SetEmployees [alice;bob]
  17. location.Employees // non-empty!
  18. |> List.iter (fun employee ->
  19. printfn "employee %s works at %s" employee.Name employee.Location.Name)

Note that we create a type alias for Employee, like this:

  1. type Employee = ParameterizedEmployee<Location>

One nice thing about creating an alias like that is that the original code for creating employees will continue to work unchanged.

  1. let alice = new Employee("Alice",location)

Parameterizing with behavior dependencies

The code above assumes that the particular class being parameterized over is not important. But what if there are dependencies on particular properties of the type?

For example, let’s say that the Employee class expects a Name property, and the Location class expects an Age property, like this:

  1. module StructuralDependency_WithAge =
  2. type Employee(name, age:float, location:Location) =
  3. member this.Name = name
  4. member this.Age = age
  5. member this.Location = location
  6. // expects Name property
  7. member this.LocationName = location.Name
  8. and Location(name, employees: Employee list) =
  9. let mutable employees = employees
  10. member this.Name = name
  11. member this.Employees = employees
  12. member this.SetEmployees es =
  13. employees <- es
  14. // expects Age property
  15. member this.AverageAge =
  16. employees |> List.averageBy (fun e -> e.Age)
  17. module Test =
  18. let location = new Location("CA",[])
  19. let alice = new Employee("Alice",20.0,location)
  20. let bob = new Employee("Bob",30.0,location)
  21. location.SetEmployees [alice;bob]
  22. printfn "Average age is %g" location.AverageAge

How can we possibly parameterize this?

Well, let’s try using the same approach as before:

  1. module StructuralDependencyWithAge_ParameterizedError =
  2. type ParameterizedEmployee<'Location>(name, age:float, location:'Location) =
  3. member this.Name = name
  4. member this.Age = age
  5. member this.Location = location
  6. member this.LocationName = location.Name // error
  7. type Location(name, employees: ParameterizedEmployee<Location> list) =
  8. let mutable employees = employees
  9. member this.Name = name
  10. member this.Employees = employees
  11. member this.SetEmployees es =
  12. employees <- es
  13. member this.AverageAge =
  14. employees |> List.averageBy (fun e -> e.Age)

The Location is happy with ParameterizedEmployee.Age, but location.Name fails to compile. obviously, because the type parameter is too generic.

One way would be to fix this by creating interfaces such as ILocation and IEmployee, and that might often be the most sensible approach.

But another way is to let the Location parameter be generic and pass in an additional function that knows how to handle it. In this case a getLocationName function.

  1. module StructuralDependencyWithAge_ParameterizedCorrect =
  2. type ParameterizedEmployee<'Location>(name, age:float, location:'Location, getLocationName) =
  3. member this.Name = name
  4. member this.Age = age
  5. member this.Location = location
  6. member this.LocationName = getLocationName location // ok
  7. type Location(name, employees: ParameterizedEmployee<Location> list) =
  8. let mutable employees = employees
  9. member this.Name = name
  10. member this.Employees = employees
  11. member this.SetEmployees es =
  12. employees <- es
  13. member this.AverageAge =
  14. employees |> List.averageBy (fun e -> e.Age)

One way of thinking about this is that we are providing the behavior externally, rather than as part of the type.

To use this then, we need to pass in a function along with the type parameter. This would be annoying to do all the time, so naturally we will wrap it in a function, like this:

  1. module StructuralDependencyWithAge_ParameterizedCorrect =
  2. // same code as above
  3. // create a helper function to construct Employees
  4. let Employee(name, age, location) =
  5. let getLocationName (l:Location) = l.Name
  6. new ParameterizedEmployee<Location>(name, age, location, getLocationName)

With this in place, the original test code continues to work, almost unchanged (we have to change new Employee to just Employee).

  1. module StructuralDependencyWithAge_ParameterizedCorrect =
  2. // same code as above
  3. module Test =
  4. let location = new Location("CA",[])
  5. let alice = Employee("Alice",20.0,location)
  6. let bob = Employee("Bob",30.0,location)
  7. location.SetEmployees [alice;bob]
  8. location.Employees // non-empty!
  9. |> List.iter (fun employee ->
  10. printfn "employee %s works at %s" employee.Name employee.LocationName)

The functional approach: separating types from functions again

Now let’s apply the functional design approach to this problem, just as we did before.

Again, we’ll separate the types themselves from the functions that act on those types.

  1. module StructuralDependencyExample_SeparateTypes =
  2. module DomainTypes =
  3. type Employee = {name:string; age:float; location:Location}
  4. and Location = {name:string; mutable employees: Employee list}
  5. module Employee =
  6. open DomainTypes
  7. let Name (employee:Employee) = employee.name
  8. let Age (employee:Employee) = employee.age
  9. let Location (employee:Employee) = employee.location
  10. let LocationName (employee:Employee) = employee.location.name
  11. module Location =
  12. open DomainTypes
  13. let Name (location:Location) = location.name
  14. let Employees (location:Location) = location.employees
  15. let AverageAge (location:Location) =
  16. location.employees |> List.averageBy (fun e -> e.age)
  17. module Test =
  18. open DomainTypes
  19. let location = { name="NY"; employees= [] }
  20. let alice = {name="Alice"; age=20.0; location=location }
  21. let bob = {name="Bob"; age=30.0; location=location }
  22. location.employees <- [alice;bob]
  23. Location.Employees location
  24. |> List.iter (fun e ->
  25. printfn "employee %s works at %s" (Employee.Name e) (Employee.LocationName e) )

Before we go any further, let’s remove some unneeded code. One nice thing about using a record type is that you don’t need to define “getters”, so the only functions you need in the modules
are functions that manipulate the data, such as AverageAge.

  1. module StructuralDependencyExample_SeparateTypes2 =
  2. module DomainTypes =
  3. type Employee = {name:string; age:float; location:Location}
  4. and Location = {name:string; mutable employees: Employee list}
  5. module Employee =
  6. open DomainTypes
  7. let LocationName employee = employee.location.name
  8. module Location =
  9. open DomainTypes
  10. let AverageAge location =
  11. location.employees |> List.averageBy (fun e -> e.age)

Parameterizing again

Once again, we can remove the dependency by creating a parameterized version of the types.

Let’s step back and think about the “location” concept. Why does a location have to only contain Employees? If we make it a bit more generic, we could consider a location as being a “place”
plus “a list of things at that place”.

For example, if the things are products, then a place full of products might be a warehouse. If the things are books, then a place full of books might be a library.

Here are these concepts expressed in code:

  1. module LocationOfThings =
  2. type Location<'Thing> = {name:string; mutable things: 'Thing list}
  3. type Employee = {name:string; age:float; location:Location<Employee> }
  4. type WorkLocation = Location<Employee>
  5. type Product = {SKU:string; price:float }
  6. type Warehouse = Location<Product>
  7. type Book = {title:string; author:string}
  8. type Library = Location<Book>

Of course, these locations are not exactly the same, but there might be something in common that you can extract into a generic design, especially as there is no behavior requirement attached to
the things they contain.

So, using the “location of things” design, here is our dependency rewritten to use parameterized types.

  1. module StructuralDependencyExample_SeparateTypes_Parameterized =
  2. module DomainTypes =
  3. type Location<'Thing> = {name:string; mutable things: 'Thing list}
  4. type Employee = {name:string; age:float; location:Location<Employee> }
  5. module Employee =
  6. open DomainTypes
  7. let LocationName employee = employee.location.name
  8. module Test =
  9. open DomainTypes
  10. let location = { name="NY"; things = [] }
  11. let alice = {name="Alice"; age=20.0; location=location }
  12. let bob = {name="Bob"; age=30.0; location=location }
  13. location.things <- [alice;bob]
  14. let employees = location.things
  15. employees
  16. |> List.iter (fun e ->
  17. printfn "employee %s works at %s" (e.name) (Employee.LocationName e) )
  18. let averageAge =
  19. employees
  20. |> List.averageBy (fun e -> e.age)

In this revised design you will see that the AverageAge function has been completely removed from the Location module. There is really no need for it, because we can do these
kinds of calculations quite well “inline” without needing the overhead of special functions.

And if you think about it, if we did need to have such a function pre-defined, it would probably be more appropriate to put in the Employee module rather than the Location module.
After all, the functionality is much more related to how employees work than how locations work.

Here’s what I mean:

  1. module Employee =
  2. let AverageAgeAtLocation location =
  3. location.things |> List.averageBy (fun e -> e.age)

This is one advantage of modules over classes; you can mix and match functions with different types, as long as they are all related to the underlying use cases.

Moving relationships into distinct types

In the examples so far, the “list of things” field in location has had to be mutable. How can we work with immutable types and still support relationships?

Well one way not to do it is to have the kind of mutual dependency we have seen. In that design, synchronization (or lack of) is a terrible problem

For example, I could change Alice’s location without telling the location she points to, resulting in an inconsistency. But if I tried to change the contents of the location as well, then I would also need to update the value of Bob as well. And so on, ad infinitum. A nightmare, basically.

The correct way to do this with immutable data is steal a leaf from database design, and extract the relationship into a separate “table” or type in our case.
The current relationships are held in a single master list, and so when changes are made, no synchronization is needed.

Here is a very crude example, using a simple list of Relationships.

  1. module StructuralDependencyExample_Normalized =
  2. module DomainTypes =
  3. type Relationship<'Left,'Right> = 'Left * 'Right
  4. type Location= {name:string}
  5. type Employee = {name:string; age:float }
  6. module Employee =
  7. open DomainTypes
  8. let EmployeesAtLocation location relations =
  9. relations
  10. |> List.filter (fun (loc,empl) -> loc = location)
  11. |> List.map (fun (loc,empl) -> empl)
  12. let AverageAgeAtLocation location relations =
  13. EmployeesAtLocation location relations
  14. |> List.averageBy (fun e -> e.age)
  15. module Test =
  16. open DomainTypes
  17. let location = { Location.name="NY"}
  18. let alice = {name="Alice"; age=20.0; }
  19. let bob = {name="Bob"; age=30.0; }
  20. let relations = [
  21. (location,alice)
  22. (location,bob)
  23. ]
  24. relations
  25. |> List.iter (fun (loc,empl) ->
  26. printfn "employee %s works at %s" (empl.name) (loc.name) )

Or course, a more efficient design would use dictionaries/maps, or special in-memory structures designed for this kind of thing.

Inheritance dependencies

Finally, let’s look at an “inheritance dependency”.

  • Type A stores a value of type B in a property
  • Type B inherits from type A

We’ll consider a UI control hierarchy, where every control belongs to a top-level “Form”, and the Form itself is a Control.

Here’s a first pass at an implementation:

  1. module InheritanceDependencyExample =
  2. type Control(name, form:Form) =
  3. member this.Name = name
  4. abstract Form : Form
  5. default this.Form = form
  6. and Form(name) as self =
  7. inherit Control(name, self)
  8. // test
  9. let form = new Form("form") // NullReferenceException!
  10. let button = new Control("button",form)

The thing to note here is that the Form passes itself in as the form value for the Control constructor.

This code will compile, but will cause a NullReferenceException error at runtime. This kind of technique will work in C#, but not in F#, because the class initialization logic is done differently.

Anyway, this is a terrible design. The form shouldn’t have to pass itself in to a constructor.

A better design, which also fixes the constructor error, is to make Control an abstract class instead, and distinguish between non-form child classes (which do take a form in their constructor)
and the Form class itself, which doesn’t.

Here’s some sample code:

  1. module InheritanceDependencyExample2 =
  2. [<AbstractClass>]
  3. type Control(name) =
  4. member this.Name = name
  5. abstract Form : Form
  6. and Form(name) =
  7. inherit Control(name)
  8. override this.Form = this
  9. and Button(name,form) =
  10. inherit Control(name)
  11. override this.Form = form
  12. // test
  13. let form = new Form("form")
  14. let button = new Button("button",form)

Our old friend parameterization again

To remove the circular dependency, we can parameterize the classes in the usual way, as shown below.

  1. module InheritanceDependencyExample_ParameterizedClasses =
  2. [<AbstractClass>]
  3. type Control<'Form>(name) =
  4. member this.Name = name
  5. abstract Form : 'Form
  6. type Form(name) =
  7. inherit Control<Form>(name)
  8. override this.Form = this
  9. type Button(name,form) =
  10. inherit Control<Form>(name)
  11. override this.Form = form
  12. // test
  13. let form = new Form("form")
  14. let button = new Button("button",form)

A functional version

I will leave a functional design as an exercise for you to do yourself.

If we were going for truly functional design, we probably would not be using inheritance at all. Instead, we would use composition in conjunction with parameterization.

But that’s a big topic, so I’ll save it for another day.

Summary

I hope that this post has given you some useful tips on removing dependency cycles. With these various approaches in hand, any problems with module organization should be able to be resolved easily.

In the next post in this series, I’ll look at dependency cycles “in the wild”, by comparing some real world C# and F# projects.

As we have seen, F# is a very opinionated language! It wants us to use modules instead of classes and it prohibits dependency cycles. Are these just annoyances, or do they really make a difference to the way that code is organized?
Read on and find out!