F# — функциональный язык программирования для платформы .NET, который компилируется в общий промежуточный код и работает бок о бок с C#. Он появился как исследовательский проект Microsoft Research, но давно стал полноценной частью .NET SDK: проекты собираются через dotnet, пакеты берутся из NuGet, а библиотеки совместимы с остальной экосистемой. F# не требует полностью менять мышление: можно писать императивный код, но язык поощряет неизменяемые данные, сопоставление с образцом и композицию функций. Ниже — практический маршрут от установки до тестов и производительности.

Чем F# отличается от C#

  • Неизменяемость по умолчанию: привязку let нельзя переприсвоить без ключевого слова mutable.
  • Размеченные объединения описывают состояние предметной области без иерархий классов.
  • Сопоставление с образцом проверяет полноту на этапе компиляции.
  • Вывод типов уменьшает шум, но не отключает проверку.
  • Функции — значения: их можно передавать, возвращать и частично применять.
  • Скрипты .fsx выполняются через dotnet fsi без создания проекта.

При этом F# не изолирован: Task, Span<T>, HttpClient и любые NuGet-пакеты доступны напрямую. Это делает его удобным для обработки данных, парсеров, предметных моделей и серверных сервисов.

Установка и структура проекта

Отдельный установщик не нужен: достаточно .NET SDK, в котором уже есть компилятор F# и интерактивная среда. Проверьте окружение и создайте консольный проект.

dotnet --info
dotnet new console -lang "F#" -o demo
cd demo
dotnet run

В отличие от C#, порядок файлов в проекте важен: компилятор обрабатывает их сверху вниз, поэтому зависимости должны быть объявлены раньше. В файле проекта это видно явно.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="Domain.fs" />
    <Compile Include="Program.fs" />
  </ItemGroup>
</Project>

Записи, объединения и сопоставление

Предметную модель удобно описывать записями и размеченными объединениями. Компилятор сам выводит типы полей, а match заставляет обработать все варианты.

type Status =
    | Active
    | Suspended of reason: string

type Account = {
    Id: int
    Name: string
    Status: Status
}

let describe account =
    match account.Status with
    | Active -> $"{account.Name}: активен"
    | Suspended reason -> $"{account.Name}: заблокирован ({reason})"

Если добавить новый вариант, старый код перестанет компилироваться — это и есть главная выгода. В C# похожего эффекта добиваются sealed-иерархиями и switch-выражениями, но F# делает это идиомой по умолчанию.

Option и Result вместо исключений

Для ожидаемых ошибок используют Option<T> и Result<T, TError>. Исключения остаются для действительно исключительных ситуаций, а не для «файл не найден».

let parsePort (value: string) =
    match System.Int32.TryParse value with
    | true, port when port > 0 && port < 65536 -> Ok port
    | _ -> Error "порт должен быть в диапазоне 1–65535"

let port =
    parsePort "8080"
    |> Result.defaultWith (fun message -> failwith message)

Такой код легко тестировать: не нужно ловить исключения, достаточно сравнить результат с ожидаемым значением. Для коллекций применяйте List.choose и Array.choose, чтобы отбросить неудачные элементы без вложенных условий.

Асинхронность

В современном F# основной инструмент — task, который напрямую работает с Task и дружит с асинхронными API .NET. Вычисление возвращает задачу, а let! ждёт результат, не блокируя поток.

open System.Net.Http

let fetch (url: string) = task {
    use client = new HttpClient()
    let! response = client.GetAsync(url)
    response.EnsureSuccessStatusCode() |> ignore
    return! response.Content.ReadAsStringAsync()
}

Старый async { } тоже поддерживается и остаётся полезным для отмены и композиции, но в новом коде чаще выбирают task. Не смешивайте два стиля в одном модуле без необходимости: это усложняет чтение.

Скрипты и пакеты

Для разовых задач проект не нужен. Интерактивная среда dotnet fsi выполняет файлы .fsx, а директивы #r подключают NuGet-пакеты прямо в скрипте.

#r "nuget: FSharp.Data"
open FSharp.Data

let page = Http.RequestString("https://example.com")
printfn "%d" page.Length

Скрипт запускается командой dotnet fsi script.fsx. Это удобно для проверки гипотез, разбора логов и небольших миграций, но в продакшен такой код переносят в проект с тестами и фиксированными версиями зависимостей.

Тесты и качество кода

Тестовый проект создаётся тем же шаблоном, что и в C#. Форматирование лучше отдать Fantomas, а предупреждения — сделать ошибками в CI.

dotnet new xunit -lang "F#" -o tests
dotnet add tests package FsUnit.xUnit
dotnet tool install -g fantomas
dotnet fantomas .
dotnet test

Компилятор F# предупреждает о неполном сопоставлении (FS0025) и о неиспользуемых значениях. В CI полезно включить -warnaserror, иначе предупреждения незаметно накопятся. Включённый Nullable добавляет проверку ссылочных типов, пришедших из C#-библиотек.

Производительность и типичные ошибки

  • Списки F# — связные, вставка в начало дешёвая, доступ по индексу — линейный; для больших наборов берите Array или ResizeArray.
  • Хвостовая рекурсия оптимизируется, но не любую рекурсию компилятор превращает в цикл.
  • [<Struct>] убирает выделение памяти для маленьких записей, но копирует данные.
  • Закрытия и Seq создают промежуточные объекты; в горячем цикле измеряйте, а не угадывайте.
  • Порядок файлов в fsproj — самая частая причина «unknown identifier».
  • Публикация: dotnet publish -c Release -r linux-x64; self-contained увеличивает размер, но убирает зависимость от установленного рантайма.

Чек-лист

  • Версия SDK зафиксирована в global.json.
  • Модель предметной области построена на записях и объединениях, а не на классах с сеттерами.
  • Ожидаемые ошибки возвращаются через Result, исключения остаются для сбоев.
  • В CI включены тесты, Fantomas и warnaserror.
  • Скрипты .fsx не попали в продакшен без тестов.
  • Производительность проверена на реальном объёме данных.

Итог

F# даёт компактную и строгую модель для задач, где важны корректность и преобразование данных. Начать проще с одного проекта и интерактивной среды: опишите типы, замените ветвления сопоставлением, вынесите ошибки в Result. .NET SDK, NuGet и общая инфраструктура означают, что язык не запирает в отдельной экосистеме. Главное — не тащить идиомы C# механически: неизменяемость, композиция и явные типы работают лучше, когда им следуют последовательно.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте