Módulos

En el contexto de F#, un módulo es una agrupación de código de F#, como valores, tipos y valores de función, en un programa de F#. Agrupar el código en módulos ayuda a mantener junto el código relacionado y a evitar conflictos de nombres en los programas.

Sintaxis

// Top-level module declaration.
module [accessibility-modifier] [qualified-namespace.]module-name
declarations
// Local module declaration.
module [accessibility-modifier] module-name =
    declarations

Comentarios

Un módulo de F# es una agrupación de construcciones de código de F#, como tipos, valores, valores de función y código en enlaces do. Se implementa como una clase de Common Language Runtime (CLR) que solo tiene miembros estáticos. Hay dos tipos de declaraciones de módulo, en función de si se incluye todo el archivo en el módulo: una declaración de módulo de nivel superior y una declaración de módulo local. Una declaración de módulo de nivel superior incluye todo el archivo del módulo. Una declaración de módulo de nivel superior solo puede aparecer como la primera declaración de un archivo.

En la sintaxis de la declaración de módulo de nivel superior, el espacio de nombres completo opcional es la secuencia de nombres de espacios de nombres anidados que contiene el módulo. El espacio de nombres completo no tiene que declararse previamente.

No es necesario aplicar sangría a las declaraciones de módulos de nivel superior, pero sí hay que aplicarla a todas las declaraciones de módulos locales. En una declaración de módulo local, solo las declaraciones con sangría de esa declaración de módulo forman parte del módulo.

Si un archivo de código no comienza con una declaración de módulo de nivel superior o una declaración de espacio de nombres, todo el contenido del archivo (incluidos los módulos locales) forma parte de un módulo de nivel superior creado implícitamente que tiene el mismo nombre que el archivo, sin la extensión y con la primera letra convertida en mayúsculas. Por ejemplo, considere el archivo siguiente.

// In the file program.fs.
let x = 40

Este archivo se compilaría como si estuviera escrito de esta manera:

module Program
let x = 40

Si tiene varios módulos en un archivo, debe usar una declaración de módulo local para cada módulo. Si se declara un espacio de nombres envolvente, estos módulos forman parte del espacio de nombres envolvente. Si no se declara un espacio de nombres envolvente, los módulos forman parte del módulo de nivel superior creado implícitamente. En el ejemplo de código siguiente se muestra un archivo de código que contiene varios módulos. El compilador crea implícitamente un módulo de nivel superior denominado Multiplemodules, y MyModule1 y MyModule2 se anidan en dicho módulo.

// In the file multiplemodules.fs.
// MyModule1
module MyModule1 =
    // Indent all program elements within modules that are declared with an equal sign.
    let module1Value = 100

    let module1Function x =
        x + 10

// MyModule2
module MyModule2 =

    let module2Value = 121

    // Use a qualified name to access the function.
    // from MyModule1.
    let module2Function x =
        x * (MyModule1.module1Function module2Value)

Si tiene varios archivos en un proyecto o en una sola compilación, o si va a compilar una biblioteca, debe incluir una declaración de espacio de nombres o una declaración de módulo en la parte superior del archivo. El compilador de F# solo determina un nombre de módulo implícitamente cuando solo hay un archivo en una línea de comandos de proyecto o compilación y se está creando una aplicación.

El valor de accessibility-modifier puede ser uno de los siguientes: public, private y internal. Para obtener más información, consulta Access Control. El valor predeterminado es public.

Referencia a código en módulos

Al hacer referencia a funciones, tipos y valores de otro módulo, debe usar un nombre completo o abrir el módulo. Si usa un nombre completo, debe especificar los espacios de nombres, el módulo y el identificador del elemento del programa que le interese. Para ello, separe cada parte de la ruta de acceso completa con un punto (.), como se indica a continuación.

Namespace1.Namespace2.ModuleName.Identifier

También puede abrir el módulo o uno o varios de los espacios de nombres para simplificar el código. Para obtener más información sobre cómo abrir espacios de nombres y módulos, consulte Declaraciones de importación: la palabra clave open.

En el ejemplo de código siguiente se muestra un módulo de nivel superior que contiene todo el código hasta el final del archivo.

module Arithmetic

let add x y =
    x + y

let sub x y =
    x - y

Para usar este código desde otro archivo del mismo proyecto, use nombres completos o abra el módulo antes de usar las funciones, como se muestra en los ejemplos siguientes.

// Fully qualify the function name.
let result1 = Arithmetic.add 5 9
// Open the module.
open Arithmetic
let result2 = add 5 9

Módulos anidados

Los módulos se pueden anidar. Es necesario que la sangría de los módulos internos llegue hasta las declaraciones de módulo externo para indicar que son módulos internos, no módulos nuevos. Por ejemplo, compare los dos ejemplos siguientes. En el código siguiente, el módulo Z es un módulo interno.

module Y =
    let x = 1

    module Z =
        let z = 5

Pero el módulo Z está en el mismo nivel que el módulo Y en el código siguiente.

module Y =
    let x = 1

module Z =
    let z = 5

El módulo Z también es un módulo del mismo nivel en el código siguiente, ya que su sangría no llega hasta otras declaraciones del módulo Y.

module Y =
        let x = 1

    module Z =
        let z = 5

Por último, si el módulo externo no tiene declaraciones y va seguido inmediatamente de otra declaración de módulo, se supone que la nueva declaración de módulo es un módulo interno, pero el compilador le advertirá si la sangría de la segunda definición del módulo no es superior a la de la primera.

// This code produces a warning, but treats Z as a inner module.
module Y =
module Z =
    let z = 5

Para eliminar la advertencia, aplique sangría al módulo interno.

module Y =
    module Z =
        let z = 5

Si le interesa que todo el código de un archivo esté en un único módulo externo y quiere que haya módulos internos, no hace falta agregar el signo igual al módulo externo ni aplicar sangría a las declaraciones (incluidas las declaraciones de módulo interno) que se incluirán en el módulo externo. En cambio, es necesario aplicar sangría a las declaraciones del módulo interno. En el código siguiente se muestra este caso.

// The top-level module declaration can be omitted if the file is named
// TopLevel.fs or topLevel.fs, and the file is the only file in an
// application.
module TopLevel

let topLevelX = 5

module Inner1 =
    let inner1X = 1
module Inner2 =
    let inner2X = 5

Módulos recursivos

En F# 4.1 se introduce el concepto de módulos que permiten que todo el código contenido sea mutuamente recursivo. Esto se hace mediante module rec. El uso de module rec puede reducir algunos problemas relacionados con la imposibilidad de escribir de código mutuamente referencial entre tipos y módulos. A continuación se muestra un ejemplo de esto:

module rec RecursiveModule =
    type Orientation = Up | Down
    type PeelState = Peeled | Unpeeled

    // This exception depends on the type below.
    exception DontSqueezeTheBananaException of Banana

    type Banana(orientation : Orientation) =
        member val IsPeeled = false with get, set
        member val Orientation = orientation with get, set
        member val Sides: PeelState list = [ Unpeeled; Unpeeled; Unpeeled; Unpeeled] with get, set

        member self.Peel() = BananaHelpers.peel self // Note the dependency on the BananaHelpers module.
        member self.SqueezeJuiceOut() = raise (DontSqueezeTheBananaException self) // This member depends on the exception above.

    module BananaHelpers =
        let peel (b: Banana) =
            let flip (banana: Banana) =
                match banana.Orientation with
                | Up ->
                    banana.Orientation <- Down
                    banana
                | Down -> banana

            let peelSides (banana: Banana) =
                banana.Sides
                |> List.map (function
                             | Unpeeled -> Peeled
                             | Peeled -> Peeled)

            match b.Orientation with
            | Up ->   b |> flip |> peelSides
            | Down -> b |> peelSides

Tenga en cuenta que tanto la excepción DontSqueezeTheBananaException como la clase Banana se hacen referencia entre sí. Además, el módulo BananaHelpers y la clase Banana también se hacen referencia entre sí. Esto no podría expresarse en F# si se quitara la palabra clave rec del módulo RecursiveModule.

Esta funcionalidad también es posible en los espacios de nombres con F# 4.1.

Vea también