Следующие примеры кода предоставляют контекст для моего вопроса.
Класс комнаты инициализируется с делегатом. В первой реализации класса Room нет защиты от делегатов, которые генерируют исключения. Такие исключения будут пузыриться до свойства North, где оценивается делегат (примечание: метод Main () демонстрирует, как экземпляр Room используется в клиентском коде):
public sealed class Room
{
private readonly Func<Room> north;
public Room(Func<Room> north)
{
this.north = north;
}
public Room North
{
get
{
return this.north();
}
}
public static void Main(string[] args)
{
Func<Room> evilDelegate = () => { throw new Exception(); };
var kitchen = new Room(north: evilDelegate);
var room = kitchen.North; //<----this will throw
}
}
Поскольку я предпочел бы потерпеть неудачу при создании объекта, а не при чтении свойства North, я изменил конструктор на private и ввел статический фабричный метод с именем Create (). Этот метод перехватывает исключение, выданное делегатом, и генерирует исключение оболочки, имеющее значимое сообщение об исключении:
public sealed class Room
{
private readonly Func<Room> north;
private Room(Func<Room> north)
{
this.north = north;
}
public Room North
{
get
{
return this.north();
}
}
public static Room Create(Func<Room> north)
{
try
{
north?.Invoke();
}
catch (Exception e)
{
throw new Exception(
message: "Initialized with an evil delegate!", innerException: e);
}
return new Room(north);
}
public static void Main(string[] args)
{
Func<Room> evilDelegate = () => { throw new Exception(); };
var kitchen = Room.Create(north: evilDelegate); //<----this will throw
var room = kitchen.North;
}
}
Оказывает ли блок try-catch метод Create () нечистым?
c#
functional-programming
pure-function
Рок Энтони Джонсон
источник
источник
Create
, также нечист, потому что он ее вызывает.Create
функция не защищает вас от получения исключения при получении имущества. Если ваш делегат бросит, в реальной жизни очень вероятно, что он будет брошен только при определенных условиях. Скорее всего, условия для броска отсутствуют во время строительства, но они присутствуют при получении имущества.Ответы:
Да. Это фактически нечистая функция. Это создает побочный эффект: выполнение программы продолжается не в том месте, куда функция должна вернуться.
Чтобы сделать его чистой функцией,
return
действительный объект, который инкапсулирует ожидаемое значение из функции, и значение, указывающее на возможное состояние ошибки, например,Maybe
объект или объект единицы работы.источник
Ну да ... и нет.
Чистая функция должна иметь ссылочную прозрачность, то есть вы должны иметь возможность заменить любой возможный вызов чистой функции возвращаемым значением, не изменяя поведения программы. * Ваша функция гарантированно всегда генерирует определенные аргументы, поэтому не существует возвращаемого значения для замены вызова функции, поэтому вместо этого давайте проигнорируем его. Рассмотрим этот код:
Если
func
все чисто, оптимизатор может решить, чтоfunc(arg)
можно заменить его результатом. Он еще не знает, каков результат, но он может сказать, что он не используется - поэтому он может просто сделать вывод, что это утверждение не имеет никакого эффекта, и удалить его.Но если
func(arg)
случится бросить, это утверждение что-то делает - оно бросает исключение! Таким образом, оптимизатор не может удалить его - имеет значение, вызвана функция или нет.Но...
На практике это мало что значит. Исключение - по крайней мере, в C # - это нечто исключительное. Вы не должны использовать это как часть своего обычного потока управления - вы должны пытаться поймать его, и если вы поймаете что-то, обработайте ошибку, чтобы либо вернуть то, что вы делали, либо каким-то образом все еще выполнить это. Если ваша программа не работает должным образом из-за того, что код, который мог дать сбой, был оптимизирован, вы используете неправильные исключения (если это не тестовый код, а когда вы строите для тестов, исключения не должны быть оптимизированы).
Что, как говорится...
Не бросайте исключения из чистых функций с намерением, что они будут перехвачены - есть веская причина, по которой функциональные языки предпочитают использовать монады вместо стек-разворачивания-исключений.
Если бы в C # был такой
Error
класс, как Java (и многие другие языки), я бы предложил броситьError
вместоException
. Это указывает на то, что пользователь функции сделал что-то не так (передал функцию, которая выбрасывает), и такие вещи разрешены в чистых функциях. Но C # не имеетError
класса, и исключения ошибок использования, кажется, происходят изException
. Я предлагаю броситьArgumentException
, давая понять, что функция была вызвана с неверным аргументом.* В вычислительном отношении. Функция Фибоначчи, реализованная с использованием наивной рекурсии, займет много времени для больших чисел и может исчерпать ресурсы машины, но это потому, что при неограниченном времени и памяти функция всегда будет возвращать одно и то же значение и не будет иметь побочных эффектов (кроме выделения память и изменение этой памяти) - это все еще считается чистым.
источник
(value, exception) = func(arg)
и исключить возможность генерирования исключения (в некоторых языках и для некоторых типов исключений вам действительно нужно перечислить его с определением, таким же, как аргументы или возвращаемое значение). Теперь он будет таким же чистым, как и раньше (при условии, что он всегда возвращает aka, выдает исключение, учитывая тот же аргумент). Бросок исключения не является побочным эффектом, если вы используете эту интерпретацию.func
таким образом, блок, который я дал, мог бы быть оптимизирован, потому что вместо разматывания стека было бы закодировано выброшенное исключениеignoreThis
, которое мы игнорируем. В отличие от исключений, которые разматывают стек, исключения, закодированные в типе возврата, не нарушают чистоту, и именно поэтому многие функциональные языки предпочитают использовать их.func(arg)
вернет кортеж(<junk>, TheException())
и, следовательно,ignoreThis
будет содержать кортеж(<junk>, TheException())
, но поскольку мы никогда не используемignoreThis
исключение, это ни на что не повлияет.Одним из соображений является то, что блок try - catch не является проблемой. (Исходя из моих комментариев к вопросу выше).
Основная проблема заключается в том, что свойство North является вызовом ввода-вывода .
На этом этапе выполнения кода программе необходимо проверить ввод-вывод, предоставленный клиентским кодом. (Не имеет значения, что входные данные представлены в форме делегата или что входные данные уже были переданы номинально).
Как только вы потеряете контроль над входом, вы не сможете гарантировать, что функция чистая. (Особенно если функцию можно скинуть).
Мне непонятно, почему вы не хотите проверять звонок на Move [Check] Room? Согласно моему комментарию к вопросу:
Как сказал выше Барт ван Инген Шенау:
В общем, любой тип отложенной загрузки неявно откладывает ошибки до этой точки.
Я бы предложил использовать метод Move [Check] Room. Это позволит вам разделить нечистые аспекты ввода / вывода в одном месте.
Аналогично ответу Роберта Харви :
Автор кода должен определить, как обрабатывать (возможное) исключение из входных данных. Затем метод может вернуть объект Room, или объект Null Room, или, возможно, выбросить исключение.
В этом случае это зависит от:
источник
Хм ... я не чувствовал себя правильно по этому поводу. Я думаю, что вы пытаетесь сделать так, чтобы другие люди правильно выполняли свою работу, не зная, что они делают.
Поскольку функция передается потребителем, который может быть написан вами или другим человеком.
Что если передача функции недопустима для запуска во время создания объекта Room?
Исходя из кода, я не был уверен, что делает Север.
Допустим, если функция состоит в том, чтобы забронировать номер, и для этого нужно время и период бронирования, то вы получите исключение, если у вас нет готовой информации.
Что делать, если это долгосрочная функция? Вы не захотите блокировать вашу программу при создании объекта Room, что если вам нужно создать объект 1000 Room?
Если бы вы были тем, кто напишет потребителя, использующего этот класс, вы бы убедились, что переданная функция написана правильно, не так ли?
Если есть другой человек, который пишет потребителю, он / она может не знать «особого» условия создания объекта Room, что может вызвать исполнение, и они почесывают голову, пытаясь выяснить, почему.
Другое дело, что не всегда хорошо бросать новое исключение. Причина в том, что она не будет содержать информацию о первоначальном исключении. Вы знаете, что получаете ошибку (дружественный меажа для общего исключения), но вы не знаете, что это за ошибка (нулевая ссылка, индекс вне границы, деление на ноль и т. Д.). Эта информация очень важна, когда нам нужно провести расследование.
Вы должны обрабатывать исключение только тогда, когда вы знаете, что и как с ним обращаться.
Я думаю, что ваш оригинальный код достаточно хорош, единственное, что вы, возможно, захотите обработать исключение на «Main» (или любом слое, который знает, как его обработать).
источник
Я не соглашусь с другими ответами и скажу нет . Исключения не приводят к тому, что в противном случае чистая функция становится нечистой.
Любое использование исключений может быть переписано для использования явных проверок результатов ошибок. Исключением может быть только синтаксический сахар. Удобный синтаксический сахар не делает чистый код нечистым.
источник
f
иg
являются чистыми функциями, тоf(x) + g(x)
должны иметь одинаковый результат независимо от того, какой порядок вы оцениваетеf(x)
иg(x)
. Но еслиf(x)
throwsF
иg(x)
throwsG
, то исключение, выброшенное из этого выражения, будет,F
еслиf(x)
вычисляется первым, аG
еслиg(x)
оценивается первым - это не то, как должны вести себя чистые функции! Когда исключение закодировано в возвращаемом типе, этот результат будет выглядеть примерно так,Error(F) + Error(G)
независимо от порядка вычисления.