Перейти к основному содержимому

Интерактивная работа с F# (FSI)

Разработчику

F# Interactive — REPL и прототипирование

В компилируемых языках обычный цикл такой: написали исходник → собрали проект (dotnet build) → запустили (dotnet run). Между правкой и результатом проходят секунды или минуты, а иногда нужно пересобрать весь solution.

F# Interactive (сокращённо FSI, от F# Interactive) — это отдельный режим: вы вводите фрагмент кода, нажимаете завершение ввода, и компилятор сразу показывает тип и значение (или текст ошибки). Такой режим называют REPL (Read–Eval–Print Loop: прочитать → вычислить → напечатать → снова ждать ввод).

FSI удобен, когда вы:

  • только знакомитесь с синтаксисом F# и хотите «пощупать» let, match, списки;
  • проверяете одну функцию или формулу, не создавая проект;
  • смотрите, какой тип вывел компилятор (это лучший учебник по типам);
  • вызываете методы из .NET (System.DateTime, System.IO.File) и смотрите результат;
  • пишете одноразовый скрипт: отчёт, миграция данных, админская утилита.

Полноценное приложение с меню, конфигурацией и тестами по-прежнему живёт в .fsproj. FSI дополняет проект, а не заменяет его: сначала эксперимент в REPL, затем перенос рабочего кода в файлы библиотеки.

Предварительно: F# в экосистеме .NET · Первая программа на F#.


Словарь терминов

ТерминПростыми словами
FSI / F# InteractiveКонсольная или встроенная в IDE сессия, где код выполняется построчно
REPLИнтерактивная оболочка «ввод → ответ» без отдельного .exe
Приглашение >Строка, с которой FSI ждёт ваше выражение
;;Конец ввода многострочного фрагмента (две точки с запятой подряд)
itИмя последнего успешно вычисленного значения в сессии
valСтрока ответа FSI: имя привязки и сигнатура типа
Скрипт .fsxТекстовый файл с кодом F#, который выполняет dotnet fsi
#r, #load, #IДирективы препроцессора скрипта: сборка, файл, каталог поиска

Запуск FSI

Нужен установленный .NET SDK (та же версия, что и для обычных проектов F#).

В терминале:

dotnet fsi

Появится приглашение вроде:

Microsoft (R) F# Interactive version ...
For help type #help;;

>

Символ > означает: можно вводить выражение. Однострочное выражение часто достаточно завершить одной ; или Enter (зависит от оболочки); для многострочного блока в классическом FSI в конце ставят ;;:

> let square x = x * x;;
val square : int -> int

> square 7;;
val it : int = 49

Разбор ответа:

  • val square : int -> int — создана функция square; тип читается так: «принимает int, возвращает int». Запись int -> intфункция одного аргумента; у функции двух аргументов будет int -> int -> int.
  • val it : int = 49 — последнее выражение дало число 49; оно доступно как it (как «последний результат» в калькуляторе).

Повторный вызов:

> it + 1;;
val it : int = 50

Имя it каждый раз перезаписывается — удобно для цепочки проб, но легко запутаться, если сессия долгая.

FSI в Visual Studio и VS Code

  • Visual Studio: меню View → F# Interactive или отправка выделенного кода в окно Interactive (сочетания зависят от раскладки).
  • VS Code с расширением Ionide: панель F# Interactive; выделенный фрагмент отправляется в уже запущенную сессию без пересборки всего solution.

В IDE тот же компилятор F#, что и в dotnet build: правила типов и сообщения об ошибках совпадают с «боевым» проектом.


Первые шаги в REPL — что вводить

Привязка и тип

> let add x y = x + y;;
val add : int -> int -> int

Компилятор вывел типы: оба аргумента и результат — int. Если передать строку, будет ошибка несовместимости типов — это нормальная защита F#.

Списки и оператор |>

> [1..5] |> List.map ((*) 2);;
val it : int list = [2; 4; 6; 8; 10]
  • [1..5] — список целых от 1 до 5 включительно.
  • |>передача значения слева в функцию справа: «возьми список и подай его в List.map».
  • ((*) 2) — функция «умножить на 2»; (*) — оператор умножения как функция.

Такие однострочники удобно копировать потом в модуль проекта.

Подключение .NET

> open System;;
> DateTime.Now;;
val it : DateTime = ...

open System открывает пространство имён — можно писать DateTime вместо System.DateTime. FSI видит всю базовую библиотеку .NET так же, как консольное приложение.


Что проверять в REPL

ЗадачаЧто ввестиЗачем
Вывод типаlet f x y = x + yУвидеть int -> int -> int без аннотаций
Ветвлениеmatch на списке или DUОшибка «неполный match» видна сразу
Коллекции[1..5] |> List.sumПривыкнуть к `
API библиотекиopen System.IO и вызов Path.CombineПонять сигнатуру до переноса в проект
Ошибка компиляциинамеренно неверный типСообщение FSxxxx указывает строку в сессии

Интерактивный режим не ослабляет проверки: неполный match по размеченному объединению, смешение string и int — всё отлавливается до запуска .exe.


Скрипты .fsx

Файл с расширением .fsx — программа для FSI. Её не обязательно добавлять в solution; запуск:

dotnet fsi script.fsx

Пример простого скрипта:

// script.fsx
let names = [ "Anna"; "Boris"; "Chen" ]
names
|> List.map (fun n -> n.ToUpper())
|> List.iter (printfn "%s")

Строки с // — комментарии. Последняя цепочка выполнится при запуске файла.

Директивы в начале скрипта

#r "nuget: Newtonsoft.Json, 13.0.3"
#load "SharedLogic.fs"
#I @"C:\libs"
ДирективаНазначение
#r "путь.dll"Подключить уже собранную сборку
#r "nuget: Пакет, версия"Скачать и подключить пакет NuGet (как в проекте, но для скрипта)
#load "File.fs"Выполнить другой исходник F# в той же сессии (типы и функции станут видны)
#I "каталог"Добавить папку, где искать сборки для #r
#helpСписок директив в FSI

#load важен для повторного использования кода: общую логику держат в .fs проекта, в скрипте подгружают тем же порядком зависимостей, что в .fsproj (см. Структура F#-проекта).

Скрипты подходят для:

  • разовой выгрузки данных из CSV и записи отчёта;
  • администрирования (массовое переименование, проверка API);
  • обучения и демо без создания MyTool.fsproj.

Если нужен установщик, служба Windows, публикация в Docker — лучше оформить консольный проект с dotnet publish.


REPL и проект — рекомендуемый порядок

  1. В FSI — набросать функцию на 5–10 строках, прогнать граничные случаи (0, пустой список, None).
  2. Перенести рабочий код в модуль проекта (Domain.fs, Core.fs — см. 190).
  3. dotnet build и dotnet test — зафиксировать поведение тестами.

Подтянуть уже существующий модуль из solution:

#load "src/MyLib/Domain.fs"
open MyLib.Domain

Порядок #load должен повторять зависимости: сначала файл с типами, потом файл, который их использует. Иначе появится ошибка «значение или пространство имён не определено» — та же логика, что и порядок <Compile Include="..."/> в .fsproj.


Состояние сессии и перезапуск

FSI накапливает все let, open и #load с начала сессии:

> let x = 1;;
> let x = 2;; // новая привязка x, старая для этого имени больше не активна

Перезапуск сессии (dotnet fsi заново или Reset в IDE) очищает память — при «странных» ошибках после множества экспериментов это первый шаг.

Повторное определение с тем же именем в проекте ведёт себя иначе, чем в REPL: в файлах .fs порядок и область видимости строго заданы компилятором.


Ограничения

  • Сеть: #r "nuget:..." при первом запуске качает пакет (нужен доступ к nuget.org, как у dotnet restore).
  • Долгие задачи: веб-хост ASP.NET Core, DI-контейнер, фоновые службы удобнее отлаживать в полноценном проекте с точкой останова в IDE.
  • Параллелизм: эксперименты с потоками в REPL возможны, но для production-кода смотрите Асинхронность.
  • Секреты: не вставляйте пароли и ключи в историю FSI на общей машине; для скриптов — переменные окружения или user secrets проекта.

Мини-практикум (5–10 минут)

  1. Запустите dotnet fsi.
  2. Определите let twice x = x * 2 и посмотрите тип.
  3. Выполните [1..10] |> List.filter (fun x -> x % 2 = 0) |> List.sum.
  4. Создайте test.fsx с #help в комментарии и одной функцией; запустите dotnet fsi test.fsx.
  5. Перенесите функцию в первую программу или в отдельный .fs по структуре проекта.

Дальше


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).