Модули

Последнее обновление: 15.12.2023

Для организации кода в отдельные блоки в F# применяются модули. По сути модуль представляет группу кода на F# - значения, типы, функции. Помещение кода в модули поволяет объединить и отделить от остального кода программу некоторую функциональность, которая связана некоторой общей идеей или который выполняет определенную задачу.

В общем случае модуль определяется следующим образом:

module название_модуля

После ключевого слова module идет название модуля и после знака равно собственно значения, типы, функции, которые входят в модуль.

Есть два типа определений модуля: определение модуля на уровне файла и локальное определение модуля.

Модуля уровня файла

По умолчанию файл с кодом F# представляет отдельный модуль, название которого совпадает с названием файла без расширения. Также можно явно указать для файла название модуля. В этом случае явное определение модуля на уровне файла должно идти самым первым выражением в файле кода. Если проект содержит несколько файлов, то во всех файлах кроме главного необходимо явным образом указать модуль.

Так, при создании проекта в Visual Studio или с помощью .NET CLI в проект о умолчанию добавляется один файл - Program.fs. По умолчанию он образует модуль "Program".

Теперь добавим в проект еще один модуль. Для этого в папку проекта добавим новый файл, который назовем Hello.fs, и определим в нем следующее содержимое:

module Hello

let message = "Hello"
let printMessage mes = printfn $"{mes}"

Первой строкой в файле определен модуль Hello. Причем при явном определении модуля его название необязательно должно совпадать с названием файла. Но, как писалось выше, если проект имеет несколько файлов, в каждом файле, кроме главного, необходимо явным образом указать модуль.

В данном случае в модуле Hello определено значение message и функция printMessage. При определении модуля уровня файла указание модуля должно идти самым первым выражением в файле.

Все составляющие модуль выражения - определения значений и типов, определения и вызовы функций не надо предварять отступом.

Подключение модуля

Для обращения к функционалу модуля в другом модуле нам необходимо перед имем функции, значения, типа указать через точку имя модуля:

имя_модуля.имя_значения
имя_модуля.имя_функции
имя_модуля.имя_типа

Так, обратимся к функциям из выше добавленного модуля Hello в главном файле Program.fs:

Hello.printMessage "F# on Metanit.com"  // обращение к функции из модуля Hello

printfn $"{Hello.message}"        // обращение к значению из модуля Hello

Так, для обращения к функции printMessage применяется выражение Hello.printMessage "F# on Metanit.com" . Аналогично идет обращение к значению message.

Модули в языке программирования F#

Теперь нам надо добавить файл Hello.fs в набор файлов для компиляции. Для этого откроем главный файл проекта, который имеет расширение .fsproj. По умолчанию он имеет содержимое типа следующего:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>

</Project>

Атрибут Include элемента Compile указывает на компилируемые файлы кода. По умолчанию в нем указан только один файл - "Program.fs". Теперь добавим в него файл Hello.fs:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Hello.fs;Program.fs" />
  </ItemGroup>

</Project>

Причем главный файл - "Program.fs" должен идти в конце. Файлы друг от друга разделяются точкой с запятой.

Запустим проект командой dotnet run:

C:\Users\eugen\fsharp\helloapp>dotnet run
F# on Metanit.com
Hello

C:\Users\eugen\fsharp\helloapp>

Таким образом, главный файл программы "Program.fs" подхватил и использовал функциональность модуля Hello.

Если модуль используется часто, то можно сразу подключить весь модуль с помощью оператора open, после которого указывается имя модуля. Тогда перед названиями функций, значений, типов не надо указывать название их модуля:

open Hello

printMessage "F# on Metanit.com"  // обращение к функции из модуля Hello

printfn $"{message}"        // обращение к значению из модуля Hello

Определение локальных модулей

Модуль уровня файла может содержать множество локальных модулей. Они определяются следующим образом:

module название_модуля =
    код_модуля

При определении локального модуля стоит учитывать, что после названия модуля идет знак равно "=", после которого указываются выражения модуля. При этом если выражения модуля располагаются на последующих строках, они должны предваряться отступом.

Так, изменим код модуля Hello:

module Hello

module Messages = 
    let hello = "Привет"
    let goodMorning = "Добрый день"
    let goodEvening = "Добрый вечер"

let printHello() = printfn $"{Messages.hello}"

Здесь определен локальный модуль Messages с тремя значениями.

После определения локального модуля идет определение функции printHello, которое использует одно из его значений. Стоит отметить, что определение этой функции не предваряется отступом, поэтому она не входит в состов модуля "Messages".

Чтобы обратится к модуле ко всем функциям, типам, значениям, которые определены в локальных модулях, также перед названием функции/типа/значения указывается имя локального модуля:

let printHello() = printfn $"{Messages.hello}"

Теперь используем этот модуль в главном файле проекта - Program.fs:

Hello.printHello()

printfn $"{Hello.Messages.goodMorning}"
printfn $"{Hello.Messages.goodEvening}"

Поскольку значения goodEvening и goodMorning определены в локальном модуле Messages модуля Hello, то для обращения к ним указываются имена всех модулей: Hello.Messages.goodEvening. Фактически структура модулей определяет путь к соответствуеющим функции/типу/значению.

Но также можно использовать оператор open, чтобы подключить и локальные модули

open Hello
open Hello.Messages

printHello()

printfn $"{goodEvening}"
printfn $"{goodMorning}"

Стоит отметить, что поскольку модуль Hello подключен первой строкой, то на второй строке мы можем упростить подключение модуля Messages:

open Hello
open Messages

printHello()

printfn $"{goodMorning}"
printfn $"{goodEvening}"

При этом одни локальные модули могут содержать другие, образуя более сложную структуру. Например:

module Hello

module Messages = 
    let hello = "Hello"
    let goodMorning = "Good Morning"
    let goodEvening = "Good Evening"

    module RusMessages = 
        let hello = "Привет"
        let goodMorning = "Добрый день"
        let goodEvening = "Добрый вечер"

let printHello() = printfn $"{Messages.RusMessages.hello}"

В данном случае в модуль Hello добавлен локальный модуль RusMessages, который представляет локализованные сообщения.

Используем этот модуль в Program.fs:

open Hello

printHello()

printfn "%s" Messages.goodMorning
printfn "%s" Messages.RusMessages.goodMorning

Поскольку в данном случае мы обращаемся к значениям с одним и тем же именем, но которые определены в разных модулях, то для их различения указываются имена содержащих их модулей.

Консольный вывод программы:

Привет
Good Morning
Добрый день
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850