Для организации кода в отдельные блоки в 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
.
Теперь нам надо добавить файл 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 Добрый день