Chapter 11. Packaging

WHAT'S IN THIS CHAPTER?

  • Understanding modules and namespaces

  • Defining assemblies in F#

Although it would be nice to imagine that when classes are written they can stand alone, the truth of the modern development environment makes it clear that a class stands among thousands, if not hundreds of thousands, of other classes, and only so many combinations of characters produce meaningful names. For classes to avoid verbose monikers like OurCompanysGenericLinkedList, some kind of higher packaging and syntactic partitioning system needs to be in place. In the .NET universe, this packaging system is called the assembly, and the syntactic partitioning is the namespace. As a CLR language, F# supports both but also adds a new mechanism from its functional heritage, the module, into the mix.

NAMESPACES

.NET supports a system of lexical scoping, allowing different types of the same name to be neatly sectioned away from one another, known as namespaces. At its heart, a namespace is just a prefix to the typename, one which can be (usually) avoided in practical use via some kind of namespace-inclusion statement, such as using in C# or Imports in Visual Basic. Namespaces have almost no runtime component to them — the CLR references every type internally by its fully qualified name. Namespaces, then, are purely a programmer convenience.

Referencing a Namespace

In F# code, to include the list of types in a namespace in the list of top-level accessible names, use the open keyword followed by the namespace name:

open System
open System.Diagnostics
open System.Reflection

You can use the open statement at a variety of scopes, though because the effects of the statement are felt only after its point of use, some idiomatic F# use holds that all such open statements should appear at the top of the F# source file or script.

As with most .NET languages, when a namespace has been referenced via the open statement, the namespace prefix can be left off the typenames when used:

Console.WriteLine("Much shorter, thank you")

Like many of the other .NET languages, F# auto-opens use a number of different namespaces on behalf of the F# programmer, because the types in those namespaces are considered to be so common that requiring F# programmers to open them manually would be a nuisance. As a result, every F# file has a "silent" list of opens at the top of the file, as if the developer had written:

open Microsoft.FSharp
open Microsoft.FSharp.Core
open Microsoft.FSharp.Core.LanguagePrimitives
open Microsoft.FSharp.Core.Operators
open Microsoft.FSharp.Text
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Core.ExtraTopLevelOperators

Most obviously missing from this list are any of the common .NET Framework Class Library namespaces, such as System.

Defining a Namespace

Because namespaces serve as a mechanism for partitioning similarly named classes away from one another and preventing accidental name conflicts, it's important for the F# developer to also define namespaces in which to define their own types. Doing so in F# is surprisingly easy — namespace followed by the namespace desired opens a new namespace, and that namespace remains open until replaced by a new namespace declaration. (The lack of a namespace declaration does not imply that the code is defined in the "empty" namespace, however — more on this in a moment.)

Thus, defining a type Person in the namespace Examples would look like:

namespace Examples

open System

type Person(fn : string, ln : string, a : int) =
    member this.FirstName = fn
    member this.LastName = ln
    member this.Age = a
    override this.ToString() =
        String.Format("{0} {1} is {2} years old",
            this.FirstName, this.LastName, this.Age)

namespace MoreExamples

type Student() =
    override this.ToString() = "Student"
                                                       
Defining a Namespace

This defines two types, one formally named Examples.Person, and the other formally named MoreExamples.Student. Nesting of namespaces is not allowed in F#; to create a "nested" namespace, such as Examples.Cool, the full "nested" name must be given in the namespace declaration.

Note that F# will not permit any code before the first namespace declaration in an F# file, so general F# coding idiom will have the first line denote the namespace used, or else no namespace is used throughout the file.

MODULES

Modules come to F# from its functional roots, through its inheritance of the OCaml programming language. Traditionally, functional languages have needed a way to partition functions away from other potentially similarly named functions but haven't wanted to "lose" that name-container prefix the way .NET developers have casually tossed aside namespaces. For example, add could mean one of many different things, which List.add clarified.

Further complicating the F# story is that, as previously mentioned, namespaces in the CLR are essentially an abstraction of the languages on top of the platform and not something the CLR recognizes as a formal construct. Thus, the functional style of a namespace "owning" top-level functions could prove to be problematic particularly when interoperating with other .NET languages. As a result, F# chose to incorporate another mechanism, the module, which on the surface of things seems to clash directly with namespaces. However, modules and namespaces have significantly different behavior and serve different purposes.

Referencing a Module

To use a module, F# reuses the open keyword again to much the same effect — opening a module makes the functions and types declared inside that module available without requiring the fully qualified name. At this level, there is little difference between a namespace and a module, and most F# programmers will not even know the difference when using them.

Defining a Module

Defining a module is relatively straightforward, just as the namespace is: The module keyword followed by a legitimate identifier name begins a module definition, and that module declaration is in scope until it is replaced by a new one:

module Examples

module Examples =

    open System

    type Person(fn : string, ln : string, a : int) =
        member this.FirstName = fn
        member this.LastName = ln
        member this.Age = a
        override this.ToString() =
            String.Format("{0} {1} is {2} years old",
this.FirstName, this.LastName, this.Age)

module MoreExamples =

    type Student() =
        override this.ToString() = "Student"
                                                    
Defining a Module

However, something subtle and slightly different is happening here: the first module declaration is establishing an overall container for all subsequent declarations, so the formal name of the Person type in the preceding example will be Examples.Examples.Person. This is reinforced because every subsequent module declaration is a kind of type definition inside the "top-level" declaration, as evidenced because the second declaration requires an = and its contents must be indented, just as a type definition requires.

The F# compiler automatically assigns a default module/namespace name to every given F# source file, defining it to be the same as the source file itself (minus the extension). However, this rule applies only in single-file F# applications or scripts; multi-file applications or libraries must have a first-line namespace or module declaration, so it's good habit to provide one even when unnecessary.

Note that namespaces can (and frequently will) contain modules, like so:

namespace Packaging

module Examples =

    open System

    type Person(fn : string, ln : string, a : int) =
        member this.FirstName = fn
        member this.LastName = ln
        member this.Age = a
        override this.ToString() =
            String.Format("{0} {1} is {2} years old",
                this.FirstName, this.LastName, this.Age)

module MoreExamples =

    type Student() =
        override this.ToString() = "Student"
                                                    
Defining a Module

Much of the F# library is written in this style.

Where modules and namespaces differ wildly is in their definition and, more strikingly, in their contents: Whereas a namespace can only have types defined within it, a module can have types and/or functions and/or values (see Chapter 13) defined within it:

module Packaging

open System

module FunctionalExample =
    let doSomething() =
        Console.WriteLine("I did something!")
    let aValue = 5

The object-oriented mindset might see the F# module as a file-sized class that automatically contains all the top-level-declared elements within it, and that would not be far off the mark — at the IL level, absent any other module or namespace declarations, the module is compiled as a class, the module-level functions as static methods, and the module-level values as properties.

This, then, raises the ugly question of whether the F# developer should prefer modules or types with static members, and no clear answer presents itself. In general, it seems that popular opinion falls on the side of how the code will be used — if the code will be used from other F# programs, a module is preferred (as is the case for much of the F# library), but if the code is intended for consumption by other .NET languages, then classes-with-static-members is likely to be a better approach to take. In particular, if the goal is to create the moral equivalent to the static class from C# 2.0, then the F# module is the right thing to use.

SUMMARY

F#'s support for both namespaces and modules is a new wrinkle in the traditional "name game" around types. In general, the F# developer can find the best mileage to be that of using namespaces to declare the "high-level" names (such as the company name), and modules to group closely related functions and values together in a construct that offers similar — but not exact — kinds of capability as a class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset