LEARN X · ЗА 16 МИН

F#

Экспресс-тур по F#: функциональный язык .NET за 16 минут — let, конвейер |>, pattern matching, записи, размеченные объединения, Option.

F# — функциональный язык первого класса на платформе .NET: неизменяемость по умолчанию, мощный вывод типов, сопоставление с образцом и идиома конвейера. Весь язык на одной странице через закомментированный код.

Вывод и комментарии

// Однострочный комментарий начинается с двух слешей
(* Многострочный комментарий
   занимает несколько строк *)

/// Тройной слеш — XML-документация (подсказки в IDE)

printfn "Привет, F#!"     // печать с переводом строки -> Привет, F#!
printf  "без перевода"     // печать без перевода строки
printfn "%d + %d = %d" 2 3 (2 + 3)   // форматированный вывод -> 2 + 3 = 5

Значения и типы

Через let связывают имя со значением. Значения неизменяемы по умолчанию, типы выводятся автоматически.

let x = 42            // int — вывод типа сам понял, что это целое
let pi = 3.14         // float (double)
let flag = true       // bool
let name = "Аня"      // string
let ch = 'A'          // char

// Значение неизменяемо: повторное let — это новое связывание (shadowing),
// а не присваивание
// x = 99            // это НЕ присваивание, а сравнение (вернёт false)

// Явная аннотация типа, если нужно
let count : int = 10
let ratio : float = 2.5

// Числовые суффиксы
let big = 1_000_000L  // int64 (нижнее подчёркивание — разделитель разрядов)
let small = 1uy       // byte
let d = 9.99m         // decimal

printfn "%s, тебе %d" name count   // -> Аня, тебе 10

Функции

Функция — тоже значение. Аргументы пишут через пробел, без скобок и запятых.

// Функция двух аргументов; тело — выражение после =
let add x y = x + y
printfn "%d" (add 2 3)        // -> 5

// Каррирование и частичное применение: применяем не все аргументы
let add10 = add 10           // add10 : int -> int
printfn "%d" (add10 5)       // -> 15

// Анонимная функция (лямбда) через fun
let square = fun x -> x * x
printfn "%d" (square 4)      // -> 16

// Композиция функций оператором >> (сначала f, затем g)
let inc x = x + 1
let double x = x * 2
let incThenDouble = inc >> double   // (x+1)*2
printfn "%d" (incThenDouble 3)      // -> 8

// << — композиция в обратном порядке
let doubleThenInc = inc << double   // x*2+1
printfn "%d" (doubleThenInc 3)      // -> 7

// Многострочная функция: тело по отступу (значимые отступы!)
let describe n =
    let half = n / 2
    half + 1                 // последнее выражение — результат

Конвейер |> (pipe forward)

Главная идиома F#: x |> f то же, что f x. Читается слева направо, как поток данных.

// Без конвейера — вложенные вызовы читаются «изнутри наружу»
let r1 = List.sum (List.map square [1; 2; 3])   // -> 14

// С конвейером — читается как цепочка преобразований
let r2 =
    [1; 2; 3]
    |> List.map square    // [1; 4; 9]
    |> List.sum           // 14
printfn "%d" r2          // -> 14

// |> передаёт значение последним аргументом следующей функции
5 |> add 10 |> printfn "%d"   // add 10 5 -> 15

// <| — конвейер влево (реже): f <| x то же, что f x
printfn "%d" <| 2 + 3         // -> 5

Строки

let hello = "Привет"
let world = "мир"

// Конкатенация оператором +
let phrase = hello + ", " + world + "!"   // "Привет, мир!"

// Интерполяция строк ($"...") — F# 5.0+
let n = 7
let msg = $"Число равно {n}, а его квадрат {n * n}"
printfn "%s" msg          // -> Число равно 7, а его квадрат 49

// printfn-спецификаторы: %s строка, %d целое, %f дробь, %b bool, %A любое
printfn "%s %d %.2f %b" "abc" 42 3.14159 true   // -> abc 42 3.14 true

// Многострочный/дословный литерал @"..." (без экранирования)
let path = @"C:\temp\file.txt"

// Методы .NET доступны напрямую на строках
printfn "%d" hello.Length              // -> 6
printfn "%s" (hello.ToUpper())         // -> ПРИВЕТ
printfn "%b" (phrase.Contains "мир")   // -> true
printfn "%s" ("  край  ".Trim())       // -> край

Условия

В F# if/then/else — это выражение, оно возвращает значение. Ветки должны быть одного типа.

let n = 5

// if как выражение: результат присваивается переменной
let sign =
    if n > 0 then "положительное"
    elif n < 0 then "отрицательное"
    else "ноль"
printfn "%s" sign            // -> положительное

// Однострочный вариант
let absN = if n < 0 then -n else n   // -> 5

// match — мощнее if: сопоставление с образцом
let classify x =
    match x with
    | 0 -> "ноль"
    | 1 -> "один"
    | _ -> "много"            // _ — образец «что угодно»
printfn "%s" (classify 1)   // -> один

Коллекции

// Список (immutable связный список) — квадратные скобки, через ;
let nums = [1; 2; 3; 4; 5]
let emptyList : int list = []
let consed = 0 :: nums            // :: добавляет в начало -> [0; 1; 2; 3; 4; 5]
let joined = [1; 2] @ [3; 4]      // @ соединяет -> [1; 2; 3; 4]

// Диапазон
let oneToTen = [1..10]            // [1; 2; ...; 10]
let evens = [2..2..10]            // с шагом 2 -> [2; 4; 6; 8; 10]

// Массив (mutable, фикс. размер) — [| ... |]
let arr = [| 1; 2; 3 |]
arr.[0] <- 99                      // элементы массива изменяемы
printfn "%d" arr.[0]              // -> 99

// Последовательность (seq, ленивая, как IEnumerable)
let lazyNums = seq { 1..1000000 } // вычисляется по требованию

// Кортеж (tuple) — фиксированный набор значений разных типов
let point = (3, 4)                // int * int
let (px, py) = point              // распаковка -> px=3, py=4
printfn "%d, %d" px py            // -> 3, 4
printfn "%d" (fst point)          // fst -> 3
printfn "%d" (snd point)          // snd -> 4

Работа со списками

let xs = [1; 2; 3; 4; 5]

// map — применить функцию к каждому элементу
let squared = xs |> List.map (fun x -> x * x)   // [1; 4; 9; 16; 25]

// filter — оставить подходящие
let evens = xs |> List.filter (fun x -> x % 2 = 0)   // [2; 4]

// fold — свёртка с аккумулятором (здесь сумма; 0 — начальное значение)
let total = xs |> List.fold (fun acc x -> acc + x) 0   // -> 15

// Готовые агрегаты
printfn "%d" (List.sum xs)      // -> 15
printfn "%d" (List.max xs)      // -> 5
printfn "%d" (List.length xs)   // -> 5

// Генератор списка (list comprehension) с условием
let squaresOfEvens = [ for x in 1..10 do if x % 2 = 0 then yield x * x ]
printfn "%A" squaresOfEvens     // -> [4; 16; 36; 64; 100]

// Цепочка через конвейер
let result =
    [1..10]
    |> List.filter (fun x -> x % 2 = 1)   // нечётные
    |> List.map (fun x -> x * x)          // в квадрат
    |> List.sum                           // суммируем
printfn "%d" result             // -> 165

Размеченные объединения

Discriminated union — тип «одно из перечисленного», часто с данными. Идеален для моделирования вариантов.

// Тип «фигура» — один из нескольких вариантов, каждый со своими данными
type Shape =
    | Circle of radius: float
    | Rectangle of width: float * height: float
    | Point

let area shape =
    match shape with
    | Circle r -> 3.14159 * r * r
    | Rectangle (w, h) -> w * h
    | Point -> 0.0

printfn "%.2f" (area (Circle 2.0))           // -> 12.57
printfn "%.2f" (area (Rectangle (3.0, 4.0))) // -> 12.00

// Option — встроенное объединение для «значение есть / нет»
// type Option<'T> = Some of 'T | None
let tryDivide a b =
    if b = 0 then None        // нет результата
    else Some (a / b)         // есть результат

match tryDivide 10 2 with
| Some v -> printfn "Результат: %d" v   // -> Результат: 5
| None   -> printfn "Деление на ноль"

// defaultValue — значение по умолчанию, если None
printfn "%d" (tryDivide 10 0 |> Option.defaultValue -1)   // -> -1

Записи

Record — именованный набор полей. Неизменяем; для «изменения» создают копию через with.

// Объявление типа-записи
type Person = { Name: string; Age: int }

// Создание — поля выводят тип записи автоматически
let alice = { Name = "Алиса"; Age = 30 }
printfn "%s, %d" alice.Name alice.Age      // -> Алиса, 30

// Копия с изменённым полем (исходная запись не меняется)
let older = { alice with Age = 31 }
printfn "%d" alice.Age     // -> 30 (оригинал цел)
printfn "%d" older.Age     // -> 31

// Записи сравниваются и печатаются структурно «из коробки»
printfn "%b" (alice = { Name = "Алиса"; Age = 30 })   // -> true
printfn "%A" older         // -> { Name = "Алиса"; Age = 31 }

// Деструктуризация записи в match/let
let { Name = nm; Age = ag } = alice
printfn "%s %d" nm ag      // -> Алиса 30

Сопоставление с образцом

match — сердце F#: разбор по структуре, охранные условия и активные шаблоны.

// Образцы списков: пустой, один элемент, голова :: хвост
let rec sumList lst =
    match lst with
    | [] -> 0                      // пустой список
    | head :: tail -> head + sumList tail   // голова + сумма хвоста
printfn "%d" (sumList [1; 2; 3])   // -> 6

// Охранное условие (guard) через when
let grade score =
    match score with
    | s when s >= 90 -> "A"
    | s when s >= 75 -> "B"
    | _ -> "C"
printfn "%s" (grade 80)            // -> B

// Сопоставление кортежей
let describePoint p =
    match p with
    | (0, 0) -> "начало координат"
    | (x, 0) -> $"на оси X: {x}"
    | (0, y) -> $"на оси Y: {y}"
    | (x, y) -> $"точка ({x}, {y})"
printfn "%s" (describePoint (5, 0))   // -> на оси X: 5

// Активный шаблон (active pattern) — свои именованные образцы
let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
let parity n =
    match n with
    | Even -> "чётное"
    | Odd  -> "нечётное"
printfn "%s" (parity 7)            // -> нечётное

Императивные элементы и .NET

F# умеет и в мутабельность, классы и всю экосистему .NET, когда это нужно.

// mutable — изменяемая переменная; присваивание оператором <-
let mutable counter = 0
counter <- counter + 1
counter <- counter + 1
printfn "%d" counter        // -> 2

// Циклы (императивный стиль)
for i in 1..3 do
    printf "%d " i          // -> 1 2 3
printfn ""

let mutable k = 0
while k < 3 do
    k <- k + 1

// Класс с конструктором, полем и методом
type Counter(start: int) =
    let mutable value = start          // приватное изменяемое поле
    member this.Value = value          // свойство только для чтения
    member this.Increment() =          // метод
        value <- value + 1

let c = Counter(10)
c.Increment()
c.Increment()
printfn "%d" c.Value        // -> 12

// Прямое использование библиотек .NET
open System

printfn "%f" (Math.Sqrt 16.0)        // -> 4.000000
printfn "%s" (DateTime.Now.ToString "yyyy-MM-dd")   // текущая дата
printfn "%d" (Int32.Parse "123" + 1) // -> 124

// Безопасный разбор через .NET с кортежем результата
match Int32.TryParse "42" with
| true, v  -> printfn "Разобрано: %d" v   // -> Разобрано: 42
| false, _ -> printfn "Не число"
Поддержать проект