Skip to content

Instantly share code, notes, and snippets.

@lkaczanowski
Forked from swlaschin/duplicate_load.md
Created December 18, 2020 14:48
Show Gist options
  • Save lkaczanowski/263712516aef535e1a1b3b64a1941a7d to your computer and use it in GitHub Desktop.
Save lkaczanowski/263712516aef535e1a1b3b64a1941a7d to your computer and use it in GitHub Desktop.
Example of duplicate #load

The duplicate #load problem and how to fix it

Symptom: A weird error such as:

error FS0001: This expression was expected to have type
    'FSI_0011.MyType'
but here has type
    'FSI_0012.MyType'

How can they be different types when you know for sure that MyType is only defined once!


Steps to reproduce

Say that you have a file called ModuleA.fsx

type A = {a:int}

And then you #load it into a file called ModuleB.fsx

#load "ModuleA.fsx"

// alias for type A
type B = ModuleA.A

// instance of type A
let b :ModuleA.A = {a=1}

And do the same in ModuleC.fsx

#load "ModuleA.fsx"

// alias for type A
type C = ModuleA.A

// instance of type A
let c :ModuleA.A = {a=1}

Finally, you reference both B and C in ModuleD.fsx. Now you get errors!

#load "ModuleB.fsx"
#load "ModuleC.fsx"

// compare the instances in the modules
ModuleB.b = ModuleC.c // ERROR
(*
ModuleD.fsx(12,13): error FS0001: This expression was expected to have type
    'FSI_0011.ModuleA.A'
but here has type
    'FSI_0012.ModuleA.A'
*)

// create two values via the type aliases
let b : ModuleB.B = {a=1}
let c : ModuleC.C = {a=1}
b = c  // ERROR
(*
ModuleD.fsx(16,5): error FS0001: This expression was expected to have type
    'ModuleB.B'
but here has type
    'ModuleC.C'
*)

A real world example

  • ModuleA contains the domain types
  • ModuleB contains the implementation of the core business logic (referencing the domain)
  • ModuleC contains the DTO to Domain conversion logic (referencing the domain but not the core business logic)
  • ModuleD is the top level API/Shell/Program script which references all three of the above.

In the top level program (ModuleD), it would be common to have a pipeline like this:

json
|> Dto.jsonToDto  // implemented in ModuleC (DTO)
|> Dto.toDomain // implemented in ModuleC (DTO), but now referencing a domain type defined in ModuleA (Domain)
|> MyWorkflow.execute // implemented in ModuleB (implementation), referencing the SAME domain type in ModuleA (Domain)

And the pipeline now won't compile because of the incompatible types.


The fix!

In moduleD, load both B and C in a single #load, not two separate ones. Like this:

#load "ModuleB.fsx" "ModuleC.fsx"

or

#load "ModuleB.fsx" 
      "ModuleC.fsx"

And now all the errors go away! Here's the updated ModuleD.fsx:

#load "ModuleB.fsx" "ModuleC.fsx"

// compare the instances in the modules
ModuleB.b = ModuleC.c // NO ERROR

// create two values via the type aliases
let b : ModuleB.B = {a=1}
let c : ModuleC.C = {a=1}
b = c  // NO ERROR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment