Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / C++

F# 15 : Code Organization (Modules / Files/ Types)

Rate me:
Please Sign up or sign in to vote.
4.38/5 (4 votes)
22 May 2014CPOL8 min read 16.8K   3  
Now that we have some of the basic types and pattern matching under our belts, I thought it high time that we learnt a bit about how to organize our own code. Throughout some of the posts we have already been through I have been referring to modules such as the “List module”. In this […]

Now that we have some of the basic types and pattern matching under our belts, I thought it high time that we learnt a bit about how to organize our own code. Throughout some of the posts we have already been through I have been referring to modules such as the “List module”. In this post we will see not only how to create our own modules but also why they are important. We shall also see how to organize files/type definitions, and see why this is important.

Organizing Types

Lets start with a simple example, as simple as it gets in fact, suppose we have this code (screen shot this time sorry)

image

It can be seen that we already have an issue. This is down to the fact that in F# any types/functions/values that are needed by the current type/function etc etc are needed to be defined before usage. This in stark contrast to C# say where this would have been totally fine:

void Main()
{
    var doubled = doublerFuction(12);
}


private int doublerFunction(int x)
{
    return x * 2;
}

This simple case is very easy to fix, we just need to swap the order of the lines in the F# code, so it would end up being like this:

image

Happy days, no more issues. That is however a very short term, and not very long sighted win. Surely there must be a better for us to organize our code in F#. Well yes there is.

Help comes in the form of files and modules.

Files

A file in F# is a simply a F# source code file, that may contain arbitrary code, it could contain a mixture of any of the following

  • functions
  • type definitions
  • let bindings
  • modules

But a F# file get special treatment by Visual Studio, an even comes with its own set of Visual Studio context menus. Which are shown right here:

image

Now you may be wondering why you would need to move files up and down. Well it is for the same reason the type definitions did not work earlier, the files have to be in a certain order too. That is to say if you have a function declared in File2.fs that is needed by File1.fs, File2.fs MUST appear before File1.fs in the visual studio IDE.

The Visual Studio IDE even stops you from

  • adding new folders to projects
  • copy + past an existing file in the solution
  • dragging and dropping files to alter their positions in the solution

These things (like them or not)are done to encourage you to organize your F# files in a correct way.

We will see later on that this quite strict enforcements are not actually as bad as they seem, as we can use a few other organizational things to help us out such as:

  • Namespaces
  • Modules
  • A nice auto open feature

Modules

Modules in F# are a way to logically group functions/types and let bindings. According to MSDN it is implemented as a common language runtime (CLR) class that has only static members.

You can declare a module like this:

image

Which in actual code simply looks like this:

module SachaPrintingModule =
 
    let prettyPrint x = 
        printfn "The value of x is %A" x
 
    let somePrintFunction x = 
        prettyPrint x

A module can contain many types, and functions. It can even contain sibling / nested modules. Though it should be noted that as soon as your F# project grows beyond one file you will need to also include namespaces, but more on this later.

Here is an example of sibling modules which are within a single *.fs file (as I say this is only as long as you have less than 2 *.fs files in the project)

module SachaPrintingModule =
 
    let prettyPrint x = 
        printfn "The value of x is %A" x
 
    let somePrintFunction x = 
        prettyPrint x
 
 
module SachaRandomModule =
    let doublerFunction x = 
        x * 2

Well we have seen lots of example of using modules already such as List/Sequence/Print etc etc, but we have yet to see how to use our own. Well to use our own all we need to do is use the following syntax to import the types/functions contained within the module “open FULLY-QUALIFIED-NAME-OF-MODULE”, which once done will allow you to use the types/functions within the opened module. Here is a complete example for a F# project that contains a single F# file (again since it is a single F# file based project we can get away without including namespaces):

module SachaPrintingModule =
  
    let prettyPrint x = 
        printfn "The value of x is %A" x
  
    let somePrintFunction x = 
        prettyPrint x
  
  
module SachaRandomModule =
    let doublerFunction x = 
        x * 2
  
  
module MainModule = 
    open SachaPrintingModule
  
    [<EntryPoint>]
    let main argv = 
        somePrintFunction 12
        0 

The above example shows 2 sibling modules, and a main module (which has the main entry point for the project). The main module opens the “SachaPrintingModule” so it is then able to make use of the contained functions from the “SachaPrintingModule

Namespaces

Namespaces allow you to organize your code into related functionality by enabling you to add a name to a grouping of elements. if you decide to use namespaces they must be the first element of a file, and everything else in the file will become part of the namespace.

Namespaces have a few rules such as:

  • They can not contain values and functions
  • They can contain types, and modules

So if you find you want to use more than types then you will need to start to look at namespaces that contain modules. As I have also stated, once you go past that magical limit of 1*.fs files, you will need to worry about namespaces too.

Though this sounds like a PITA it really isn’t that bad, all you need to do is add a namespace declaration and make sure to fully qualify your module imports with the namespace name where you need to import the module.

Here is an example of a couple of namespaced modules in a multi *.fs file project (this is the actual order they appear in within the project as well). As you can see File1.js /File2.js simply contain function definitions, whilst File3.js makes use of the File1.js and File2.js functions, which it does by opening the modules, where the module name is fully qualified.

The last file in the project Program.fs makes use of the File3.js functions, which as before it does by opening the module.

File1.fs

namespace ConsoleApplication1
    module SachasDoublerModule = 
        let doublerFunction x = x * 2

File2.fs

namespace ConsoleApplication1
    module SachasTriplerModule = 
        let triplerFunction x = x * 3

File3.fs

namespace ConsoleApplication1
    module SachasRandomModule = 

       open ConsoleApplication1.SachasDoublerModule
       open ConsoleApplication1.SachasTriplerModule

       let randomFunction x = (doublerFunction x) + (triplerFunction x) 

Program.fs

namespace ConsoleApplication1
    module MainModule = 
 
    open System
    open System.IO
    open System
    open System.Linq
    open System.Collections.Generic
    open ConsoleApplication1.SachasRandomModule
 
 
    [<EntryPoint>]
    let main argv = 
 
 
        //use the ConsoleApplication1.SachasRandomModule functions
        printfn "The random value of 2 is %A" (randomFunction 2)
 
        Console.ReadLine() |> ignore
        0 

AutoOpen

There is a special attribute that can help you with modules, which is the AutoOpenAttribute, which can be applied to modules, which makes the contents of the module available to other modules in the same namespace.

You can read more about this at MSDN : http://msdn.microsoft.com/en-us/library/ee353721.aspx

Here is the example we just looked at re-written to take advantage of the AutoOpenAttribute

File1.fs

This now makes use of the AutoOpenAttribute to share the module with anything in the same namespace:

namespace ConsoleApplication1
    
    [<AutoOpen>]
    module SachasDoublerModule = 
        let doublerFunction x = x * 2

File2.fs

This now makes use of the AutoOpenAttribute to share the module with anything in the same namespace:

namespace ConsoleApplication1
 
    [<AutoOpen>]
    module SachasTriplerModule = 
        let triplerFunction x = x * 3

File3.fs

See how this one no longer needs to open the ConsoleApplication1. SachasDoublerModule / ConsoleApplication1.SachasTriplerModule

namespace ConsoleApplication1
 
    [<AutoOpen>]
    module SachasRandomModule = 
 
       let randomFunction x = (doublerFunction x) + (triplerFunction x) 

I have not changed the Program.fs code, so that is as previously shown. This was just to show you that you could shared modules using the AutoOpenAttribute

Cyclic Dependencies

So far we have seen how we can use files/modules/namespaces to help us organize our code. However there is one final element I wanted to mention which is actual dependencies in our code. What I mean by that is what would happen if we had a type (say Person type) that depends on another type (say Manager type), and the Manager also depends on the person. That is what is known as a cyclic dependency, which when written down could look something like this:

image

As you can see from the screen shot above, the following hold true:

  • The Person type is not compiling as it has a dependency on the Manager type, which is doesn’t know about due to the position of the Manager type in the F# code file. Ok we could move the Manager type up, but then it would start complaining as it has a dependency on the Person type.
  • Right now the Manager type is ok, as Person is declared above it, so it knows about the Person type.

Mmmm what a pickle. What can we do about this. Luckily F# has a way out of this sort of thing, it is called the “and” keyword, which would allow us to write the above cyclic code in a non cyclic way. Here it is re-written using the “and” keyword:

namespace ConsoleApplication1
   
    [<AutoOpen>]
    module SachasDomain = 

        open System;
        open System.Collections.Generic;
        
        type Person(manager : Manager option, name : string) = 
            member this.Manager = manager  
            member this.Name = name
            override this.ToString() = 
                match this.Manager with
                | Some(Manager) -> String.Format("Person {0} has Manager {1}", 
                    this.Name, (Option.get(this.Manager).Name))  
                | None ->  String.Format("Person {0} has NO Manager", this.Name)
        //**********************************************
        // use the "and" keyword to break the cyclic dependency between
        // "Person" type and "Manager" type
        //**********************************************
        and Manager(name : string) = 

            let underlings = new List<Person>()
            member this.Underlings = underlings
            member this.Name = name

Which has this code that makes use the above code:

namespace ConsoleApplication1
    module MainModule = 

    open System
    open System.IO
    open System
    open System.Linq
    open System.Collections.Generic
    open ConsoleApplication1.SachasDomain
   
    [<EntryPoint>]
    let main argv = 

        let manager = Manager("manager1")
        let p1 = Person(Some(manager), "person 1")
        manager.Underlings |> ignore

        manager.Underlings.Add(p1)
        printfn "Manager %A has (%A) underlings\r\n" manager.Name (manager.Underlings.Count)

        let c = manager.Underlings.Count

        printfn "Person details\r\n%A" (p1.ToString())

        Console.ReadLine() |> ignore
        0

Which when run will give the following results:

image

Now as I stated at the very beginning of this series I am a mere fledgling when it comes to F#, I am not a guru at all. There is however a fabulous series of post by someone whom I do consider to be a F# guru, that person is Scott Wsaschin, who has a truly excellent F# blog http://fsharpforfunandprofit.com, I just wanted to draw your attention to these 3 posts in particular as Scott goes into some very nitty gritty detail about “cyclic dependencies” and how to get rid of them. If you are interested in learning more about F# and in particular “cyclic dependencies” you owe it to yourself to read these 3 posts:

  1. http://fsharpforfunandprofit.com/posts/cyclic-dependencies/
  2. http://fsharpforfunandprofit.com/posts/removing-cyclic-dependencies/
  3. http://fsharpforfunandprofit.com/posts/cycles-and-modularity-in-the-wild/

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
-- There are no messages in this forum --