vak: (Default)
[personal profile] vak
Rust на первый взгляд не особо отличается от Си++. Тоже классы и методы, ничего особенного. Однако подход ко многим вещам отличается. К примеру, обработка особых ситуаций и ошибок делается совсем по другому. Здесь я составил короткий обзор со ссылками на документацию.

Тип Option

Что, если значение может отсутствать? К примеру, функция может вернуть значение, если оно вычислено успешно, и вернуть что-то другое, если не получилось. Для таких случаев существует тип Option. Это энум с двумя вариантами:
pub enum Option<T> {
None,
Some(T),
}
Если значение val получилось вычислить, возвращаем Some(val). Иначе возвращаем None. Пример такой функции - сложение двух чисел с обнаружением переполнения:
let x = 123.checked_add(567);
Здесь x получит значение Some(690), поскольку переполнения не было.

Тип Result

Часто при возникновении особых ситуаций нужно вернуть дополнительную информацию, уточняющую причину ошибки. Просто None недостаточно. Для этого существует другой тип: Result. Это тоже энум с двумя вариантами:
enum Result<T, E> {
Ok(T),
Err(E),
}
В отличие от Option, в случае особой ситуации вместо None используется вариант Err(x), где значение x содержит код ошибки. Большинство функций стандартной библиотеки возвращает Result.

Тип Error

В энуме Result<T,E> тип E может быть каким угодно, но в стандартной библиотеке принято давать ему интерфейс (трейт) std::error::Error. У трейта Error есть несколько малополезных методов низкого уровня, но главное - его можно напечатать через print!(), и получить объяснение ошибки.

Оператор match

Результат типа Option или Result обычно обрабатывается с помощью оператора match. Пример для Option:
let val = match 123.checked_add(567) {
Some(x) => x,
None => return None,
};
Пример для Result:
let result = File::open("hello.txt");
let file = match result {
Ok(x) => x,
Err(e) => return Err(e),
};

Оператор '?'

Вышеприведённые фрагменты обработки значений типа Result или Option настолько часто встречаются, что для удобства был придуман способ их упростить. Если после выражения поставить вопросительный знак, значение будет обработано посредством match, ровно как написано выше. То есть в случае None (для Option) или Err (для Result) произойдёт преждевременный возврат из текущей функции.

Предыдущие примеры сокращаются до:
let val = 123.checked_add(567)?;

let file = File::open("hello.txt")?;
Заметьте, что чтобы вопросительный знак правильно работал, текущая функция должна быть объявлена как возвращающая значение Option<T> или Result<T, E> соответственно.

Выделение значений

Для типовой обработки значений типа Option или Result есть набор полезных методов.

Для Option<T>, обычно нужно выделить значение из Some(value), а если встретится None - завершиться с ошибкой или выдать альтернативное значение.
  • option.unwrap() - если None, вызвать panic!()
  • option.expect(msg) - если None, вызвать panic!() с указанным сообщением об ошибке
  • option.unwrap_or(val) - если None, выдать указанное значение
  • option.unwrap_or_default() - если None, выдать дефолтное значение для типа T
  • option.unwrap_or_else(func) - если None, вызвать функцию и вернуть её значение
Аналогично для Result<T, E>, обычно требуется выделить значение из Ok(value), а если попадётся Err(e) - завершиться с ошибкой или выдать альтернативное значение.
  • result.unwrap() - если Err, вызвать panic!()
  • result.expect(msg) - если Err, вызвать panic!() с указанным сообщением об ошибке
  • result.unwrap_or(val) - если Err, выдать указанное значение
  • result.unwrap_or_default() - если Err, выдать дефолтное значение для типа T
  • result.unwrap_or_else(func) - если Err, вызвать функцию и вернуть её значение
Иногда для Result надо выделить значение из варианта Err(e), а если попадётся Ok(v) - завершиться с ошибкой или выдать альтернативное значение.
  • result.unwrap_err() - если Ok, вызвать panic!()
  • result.expect_err(msg) - если Ok, вызвать panic!() с указанным сообщением об ошибке

Преобразование Result ⟷ Option

Можно преобразовать значение типа Result в Option:
  • result.ok() - преобразовать Ok(v) в Some(v), а Err(e) в None
  • result.err() - преобразовать Err(e) to Some(e), а Ok(v) в None
Аналогично можно преобразовать Option в Result:
  • option.ok_or(err) - преобразовать Some(v) в Ok(v), а None в Err(err)
  • option.ok_or_else(func) - преобразовать Some(v) to Ok(v), а None в Err(func())
Пример:
let x = 123.checked_add(567).unwrap();
Здесь x получит значение 590. Если бы случилось переполнение, программа завершилась бы с сообщением:
called `Option::unwrap()` on a `None` value

Date: 2021-12-22 03:40 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

Тип Result у них не аппликативный, это, конечно, немножко дефект. Он вообще монада? Я что-то уже не уверен.

Но нужен аппликативный. Я что и наслаждался моим таким же типом, что можно много что нафигачить.

А вообще, в Скале все это делается изощреннее, конечно. Опцию в список только так.

Edited Date: 2021-12-22 03:42 (UTC)

Date: 2021-12-22 05:44 (UTC)
lomeo: (Default)
From: [personal profile] lomeo
Речь не о структуре, аппликативность или "монадность" — это про поведение и законы. Даём нужные операции, соблюдающие определённые законы — получаем монаду или там аппликативный функтор. Для Result, как я понимаю, это сделать можно. Другое дело, что абстрагировать саму монаду не получится из-за отсутствия higher-kinded types.

Date: 2021-12-22 14:16 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

Мне кажется, не особо и надо абстрагировать и писать, мол, что оно монада. Просто по факту. map, flatmap, ap, unit.

Date: 2021-12-22 14:21 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

Это детали имплементации; если к ним добавить

pure, A => Ok(A)
map: (f: A => B) => Result[A] => Result[B]
flatten: Result[Result[A]] => Result[A]
andAlso: (Result[A], Result[B]) => Result[(A,B)]

то и будет все что надо. Практически. Sorry, два года уже не трогал раст, забыл синтаксис.

Edited Date: 2021-12-22 14:22 (UTC)

Date: 2021-12-22 05:40 (UTC)
lomeo: (Default)
From: [personal profile] lomeo
У меня такое ощущение, что уже обсуждали, но всё же — почему не аппликативный? `pure` есть, написать `ap` проблем нет. Понятно, что обобщённого типа как в Хаскеле или Скале не получится, в Rust нет higher-kinded types, но поведение то можно описать.

Date: 2021-12-22 07:32 (UTC)
From: [personal profile] carrauntoohil
Подскажите пожалуйста, где об этом (монады и всё такое) можно "хорошо" почитать. И хаскел и скала и котлин немного умею. И применять вроде бы "функциональный" подход могу в проектах. Но в голове ясности нет, а каша и до "Эврика" как до Китая раком. Буду очень благодарен на любые "наводки". Заранее большое спасибо.

Date: 2022-01-08 05:49 (UTC)
lomeo: (Default)
From: [personal profile] lomeo
Ой, я даже не знаю, что сказать. Если знаете Хаскель, посмотрите для начала typeclassopedia, чтобы понять что вообще есть (на начальном уровне). Какая цель вообще? Чего хочется добиться?
From: [personal profile] carrauntoohil
Оптравил Вам сообщение с описанием задачи на личную почту dreamwidth.

Date: 2021-12-22 09:11 (UTC)
vit_r: default (Default)
From: [personal profile] vit_r
Ошибка не относится к пространству нормального функционирования программы и её обработка может быть только двух типов: или возврат наверх описания ошибки с отладочной информацией, позволяющей однозначно определить причины на верхнем уровне, или остановка выполнения в месте ошибки и judicial recorder с записью отладочной информации, позволяющей и так далее. Всё остальное -- извращение и попытка упрятать мусор под ковёр.

Впрочем, сейчас вся индустрия именно этим и занимается.

Date: 2021-12-22 11:25 (UTC)
ircicq: (Default)
From: [personal profile] ircicq
Есть более общий метод чем Exception:
Conditions (как в CL), когда обработчику наверху предоставляются Restarting points на выбор: как возобновить с того места где ошибка (возврат обратно вниз по стеку)

Date: 2021-12-22 14:18 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

Ой, ничо се. Это у нас когда-то в древние времена на фортране было имплементировано. Типа после того, как электричество починили и мы включили комп, мы вернулись в точку, где были, и решаем, куда дальше.

Date: 2022-01-08 05:46 (UTC)
lomeo: (Default)
From: [personal profile] lomeo
Да, conditions в CL - лучшее в обработке ошибок, что я видел.

Date: 2021-12-22 14:10 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

Второй тип не исключает первый. И не так уж часто надо бросать исключение при первой же ошибке - если мы парсим документ, то лучше собрать все возможные проблемы (вот тут-то аппликативный функтор и нужен).

Насчет же упрятывания под ковер - это типичное программирование на джаве. Имя доктора не нашли? Значит, его имя - пустая строка. Или null. Это все тоже имеет математический аналог. Категория Эйленберга-Мура. Где на всякую хрень есть еще "дефолтное значение". What is the default value for a birthday? Christ circumcision date.

Date: 2021-12-22 14:16 (UTC)
vit_r: default (Default)
From: [personal profile] vit_r
Пардон, зачем объединять два разных механизма, а потом возражать этому объединению?

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

Date: 2021-12-22 08:09 (UTC)
mikerrr: (Default)
From: [personal profile] mikerrr
Опять эти скобочки, неужели сложно от них избавиться?
Все таки, в этом смысле, питон на голову выше этих языков

Date: 2021-12-22 14:13 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

Скала тоже хороша в смысле скобочек. Но давайте не будем путать язык с линейной логикой и языки с интуиционистской. Разные вещи, концептуально. В линейной логике вещи не возникают из ничего и не исчезают в никуда.

А уж полутиповые языки просто не совсем являются языками. Где их математическая модель? И когда будет обнаружена ошибка в коде, в продакшене?

Date: 2021-12-22 16:40 (UTC)
mikerrr: (Default)
From: [personal profile] mikerrr
Я не программист, меня продакшн не волнует (программисты потом перепишут мой код для продакшена). А вот красота кода - очень даже. Для меня скобки - это критично)

Date: 2021-12-22 10:40 (UTC)
From: [personal profile] ivanrubilo
Ну не прям совсем по-другому, в современных стандартах C++ большинство этого всего есть/рассматривается.
std::variant
std::any
std::optional
std::experimental::expected
Edited Date: 2021-12-22 10:42 (UTC)

Date: 2021-12-22 13:39 (UTC)
From: [personal profile] sergegers
+ монадические map(), flat_map() для boost::option

Date: 2021-12-22 14:11 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

В плюсах уже монады есть? Строуструп как-то не сознавался.

Date: 2021-12-22 21:05 (UTC)
kondybas: (Default)
From: [personal profile] kondybas
Это параметрически, на стадии препроцессинга

Date: 2021-12-23 22:04 (UTC)
From: [personal profile] sergegers
чё за бред