S>Возвращаемое значение — есть, а Match по-прежнему не работает!
Да тут есть определенный косяк, что нужно учитывать императивное мышление шапристов, однако если разобраться, то код выполнился в этом случае, только не тот который предполагалосью
S>Ну, то есть на самом деле надо копать — тут не очень понятно, как смешиваются функции, возвращающие Result<A>, с функциями, возвращающими просто A. У автора библиотеки с этим некоторый бардак — в частности, если один из параметров succ или fail сам выбросит исключение, оно неожиданным образом вылетит из Match, напугав автора кода — ведь он-то ожидал как раз избавления от исключений!
Вот это не понял, если ваш код может бросить исключение что в этом пугающего? Очевидно, что мы ограждаемся от исключений только в вызове Try<A>.
S>>Возвращаемое значение — есть, а Match по-прежнему не работает!
vaa>Да тут есть определенный косяк, что нужно учитывать императивное мышление шапристов
Императивное мышление шарпистов тут ни при чём.
vaa>однако если разобраться, то код выполнился в этом случае, только не тот который предполагалось
Естественно. Код выполняется во всех случаях .
Ну, всё верно. Здесь вы вручную утаптываете ваш код в ту самую сигнатуру Func<A> — безаргументной функции, возвращающей одно значение.
Это по-прежнему никак не помогает нам обработать случай с функцией int->int.
S>>Ну, то есть на самом деле надо копать — тут не очень понятно, как смешиваются функции, возвращающие Result<A>, с функциями, возвращающими просто A. У автора библиотеки с этим некоторый бардак — в частности, если один из параметров succ или fail сам выбросит исключение, оно неожиданным образом вылетит из Match, напугав автора кода — ведь он-то ожидал как раз избавления от исключений! vaa>Вот это не понял, если ваш код может бросить исключение что в этом пугающего? Очевидно, что мы ограждаемся от исключений только в вызове Try<A>.
Смысл не в том, чтобы "оградиться от исключений в каком-то коде". Идея railway programming — в том, что вместо исключений мы используем варианты возврата.
Для того, чтобы она работала (хотя бы в том ограниченном смысле, как она может вообще работать), её обработчиками в match должны тоже быть не плоские функции, а соответствующие Try-монады.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Для того, чтобы она работала (хотя бы в том ограниченном смысле, как она может вообще работать), её обработчиками в match должны тоже быть не плоские функции, а соответствующие Try-монады.
Здравствуйте, Serginio1, Вы писали: S> Ну в F# Каррирование упрощает работу https://lsreg.ru/shpargalka-po-f/
Это не в F#, это вообще удивительный мир ФП, где других функций, в общем-то, и нету.
Основное преимущество такого подхода — концептуальная чистота.
Основной недостаток — это отсутствие внятного автодополнения и нормальных перегрузок по типам аргументов.
Отдельным бонусом идёт чудовищная неэффективность — АФАИК, порождение эффективного кода для функций в таком стиле несовместимо с CLR.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
S>>>Возвращаемое значение — есть, а Match по-прежнему не работает! S>Это по-прежнему никак не помогает нам обработать случай с функцией int->int.
Вот вообще непонятно кто так станет делать?
В вашем случае Try возвращает Func<int,int>. И(следите за руками) Match по-прежнему работает!
Но да согласен с появлением нормальных record в C# быть может данный кейс уже не так актуален.
Здравствуйте, vaa, Вы писали:
vaa>Вот вообще непонятно кто так станет делать?
В каком смысле? Сигнатуры функций бывают различными. Вовсе не обязательно функция должна иметь 0 параметров. vaa>В вашем случае Try возвращает Func<int,int>. И(следите за руками) Match по-прежнему работает!
Нет конечно, ничего не работает. WriteLine("DONE") не выполняется; аргумент Fail при исключении тоже не выполнится:
Здравствуйте, Sinclair, Вы писали:
S>Нет конечно, ничего не работает. WriteLine("DONE") не выполняется; аргумент Fail при исключении тоже не выполнится: S>
Здравствуйте, vaa, Вы писали:
vaa>Здравствуйте, Sinclair, Вы писали:
S>>Нет конечно, ничего не работает. WriteLine("DONE") не выполняется; аргумент Fail при исключении тоже не выполнится: S>>
vaa>И откуда возьмется x чтобы лямбда посчиталась? результат работы try функция а не ее результат. хм... матч то тут причем?
Здесь нет кода, вызывающего лямбду. Матч выполняет Try над первым аргументом, результатом будет вот эта лямбда, которая записана в Try. Матч должен выполнить либо первый, либо второй экшн и вернуть Unit.
Где-то в консоли должен быть ОК, тело лямбды никто не выполняет, исключение в нем не возбуждается. (Код не запускал, если что).
Здравствуйте, vaa, Вы писали: S>>Нет конечно, ничего не работает. WriteLine("DONE") не выполняется; аргумент Fail при исключении тоже не выполнится: S>>
vaa>И откуда возьмется x чтобы лямбда посчиталась? результат работы try функция а не ее результат. хм... матч то тут причем?
Всё верно.
Чего мы ожидаем от конструкции Try().Match()?
Что она превратит некую функцию, бросающую исключения, в функцию, которая исключений не бросает, а выполняет действия, указанные в match.
Результатом указанного кода должна стать Action<int>, которая при вызове на любом аргументе выводит "InvalidOperationException".
А в реальности результатом будет Unit. Увы.
Я вижу, вам сложно воспринимать код C#.
Давайте я напишу помедленнее.
Начнём с двух простых методов.
Func<int, int> foo = x => x % 2 == 0 ? 1 : throw new Exception("Oops");
Func<int> bar = () => foo(DateTime.Now.Millisecond);
Первый из них отображает int->int, и иногда бросает исключение.
Второй — всего лишь обёртка над первым, которая в качестве параметра использует текущее значение времени.
Давайте теперь попробуем посмотреть, что можно с ними сделать с помощью Try.
Начнём со второго:
// 1. Working with the parameterless function:
// 1.1. Wrapping into the Try monad:var tryBar = Try(bar);
// 1.2 Direct execution (note the lack of params in Try):var barResult = tryBar.Try(); // Result<int>
// 1.3. Coalescing exceptions into default valuevar barFinalResult = barResult.Match(x => x, ex => 1);
Assert.Equal(1, barFinalResult);
// 1.4. Shortcut coalescing:
barFinalResult = tryBar.Match(x => x, ex => 1);
Assert.Equal(1, barFinalResult);
// 1.5. Returnless matching:
tryBar.Match(x => WriteLine(x), ex => WriteLine(ex));
удобная вроде штука — мы превратили Func<int> throws Exception сначала в Func<Result<int>>, затем просто в Result<int>, а потом и в просто int.
Ну, или если нам результат функции нужен был просто в рамках некоего побочного эффекта (вывод на экран), то мы это сделали в одну строку в 1.5.
Попробуем сделать что-то похожее с foo:
var tryFoo = Try(foo);
Логично ожидать аналогичного поведения — наверное, у tryFoo будет доступен метод Try(int), который тоже вернёт Result<int>, чтобы по нему можно было матчиться.
А методы Match должны принимать int в качестве дополнительного параметра.
Ну, то есть мне логичным видится делать даже не это, а строить обратно Func<int, int> либо Action<int>, в зависимости от сигнатур переданных в Match обработчиков.
Но это бы означало ленивость Match, что противоречит поведению того убогого Match, который реализован в этой библиотеке для функций в стиле bar.
Но нет! у tryFoo есть только один метод Try, у которого нет параметров. И возвращает он вовсе не Result<int>, а Result<Func<int, int>>, который полностью бесполезен для наших целей.
Мы, конечно, можем выковырять этот Func<int, int> из Result, но даже после Match он продолжает сыпать исключениями.
Никаким способом мы из tryFoo не можем получить ожидаемое — Func<int, int>, которая для любого аргумента вернёт 1.
Поэтому здравомыслие автора библиотеки вызывает вопросы.
Если что-то непонятно, спрашивайте.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
vaa>Собственно функция-конструктор Try<A> и возвращает функцию A. vaa>Возможно нужно было Try<T> назвать по другому, например Do<T>
Я к тому, что ошибка должна быть на этапе компиляции
Try((int x) => { WriteLine("DONE"); return x; })
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>Здравствуйте, vaa, Вы писали:
vaa>>Собственно функция-конструктор Try<A> и возвращает функцию A. vaa>>Возможно нужно было Try<T> назвать по другому, например Do<T> S> Я к тому, что ошибка должна быть на этапе компиляции S>
Здравствуйте, samius, Вы писали: S>Метод Try<A> принимает в качестве значения типа A функцию Func<int,int> и возвращает делегат, возвращающий это значение-функцию Func<int,int>.
Можно попробовать убрать нафиг этот метод, т.к. непонятно, насколько он нужен. Пока что выглядит так, что он кроме граблей никакой пользы не приносит — но для полного понимания хотелось бы, конечно, примеров кода на этой библиотеке.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, samius, Вы писали: S>>Метод Try<A> принимает в качестве значения типа A функцию Func<int,int> и возвращает делегат, возвращающий это значение-функцию Func<int,int>. S>Можно попробовать убрать нафиг этот метод, т.к. непонятно, насколько он нужен. Пока что выглядит так, что он кроме граблей никакой пользы не приносит — но для полного понимания хотелось бы, конечно, примеров кода на этой библиотеке.
Что бы понять, насколько этот метод нужен, нужно понять, что мы делаем, и как будет выглядеть код без использования этого метода.
Есть такое ощущение, что мы пытаемся найти обстоятельства, в которых эта библиотека делает что-то интуитивно понятное, но неизвестно заранее, что именно. Но да, с помощью этой библиотеки слишком легко сделать из чего угодно Unit/void, не прибегая к усилиям, и даже не пытаясь вычислить что-нибудь. Но нужна ли такая библиотека для этого? C# и сам с этим справится на раз.
Здравствуйте, Sinclair, Вы писали:
S>Чего мы ожидаем от конструкции Try().Match()? S>Что она превратит некую функцию, бросающую исключения, в функцию, которая исключений не бросает, а выполняет действия, указанные в match.
А с чего это? Вообще, конечно, библиотека укуренная. В ней два момента. Во-первых, судя по наличию TryExtensions.Retry, это ленивое вычисление (наверное, более правильный термин — по требованию/on demand), которое форсируется (выполняется) вызовом определенных методов. Match и Try — это два из нескольких форсирующих методов. Во-вторых, если не смотреть на ленивость, Try это практически аналог Result. Отличие только в обработке исключений в Map. Result.Map(x => throw Exception("Today is a bad day")) выбросит исключение (наверное..., я в данной библиотеке ничего не гарантирую). А вот Try.Map(x => throw Exception("Today is a bad day")) вычислит TryFail(Exception("Today is a bad day")).
Вообще авторы молодцы — сделали три совершенно противоположных друг другу перегрузки. Try<A>(Exception e) == TryFail(e), Try<A>(A a) == TrySucc(a) и наконец правильный конструктор Try<A>(Func<A> a). Условно,
using Try<A> = Func<Result<A>>
method Try<A>(Exception e): Func<Result<A>> {
return () => ResultFail(e);
}
method Try<A>(A a): Func<Result<A>> {
return () => ResultSuccess(e);
}
method Try<A>(Func<A> fn): Func<Result<A>> {
return () => {
try {
ResultSuccess(fn()) // close to TrySucc
} catch(Exception e) {
ResultFail(e) // close to TryFail
}
}
}
method R Match <A, R> (this Try<A> self, Func<A, R> Succ, Func<Exception, R> Fail) {
// force computation
val resultValue: Result<A> = self()
// and process outcomes, same as resultValue.Match(Succ, Fail)if (resultValue.isSuccess()) {
return Succ(resultValue.value);
} else {
return Fail(resultValue.exception);
}
}
method A Try<A>(this Try<A> self) {
return self();
}
Можно для чистоты сделать Try<A> оберткой над Fun<Result<A>>, добавится грязи в виде распаковки/запаковки но принципиально ничего не изменит.
S>Попробуем сделать что-то похожее с foo: S>Логично ожидать аналогичного поведения — наверное, у tryFoo будет доступен метод Try(int), который тоже вернёт Result<int>, чтобы по нему можно было матчиться.
Ожидать — логично. Но перегрузки Try<A>(A) и Try<A>(Exception) не должны были бы существовать вообще, чтобы избежать данной проблемы.
S>А методы Match должны принимать int в качестве дополнительного параметра. S>Ну, то есть мне логичным видится делать даже не это, а строить обратно Func<int, int> либо Action<int>, в зависимости от сигнатур переданных в Match обработчиков. S>Но это бы означало ленивость Match, что противоречит поведению того убогого Match, который реализован в этой библиотеке для функций в стиле bar.
Я понимаю, откуда растут ожидания. Но, к сожалению, нет. Try — это ленивое вычисление значения. Если бы пришлось писать TrySucc(foo) непоняток было бы гораздо меньше.
S>Но нет! у tryFoo есть только один метод Try, у которого нет параметров. И возвращает он вовсе не Result<int>, а Result<Func<int, int>>, который полностью бесполезен для наших целей. S>Мы, конечно, можем выковырять этот Func<int, int> из Result, но даже после Match он продолжает сыпать исключениями. S>Никаким способом мы из tryFoo не можем получить ожидаемое — Func<int, int>, которая для любого аргумента вернёт 1.
Почему никаким? Это же обычный "хитрый функтор":
var tryFoo = Try(foo);
var appliedFoo = tryFoo.Map(fn => fn(DateTime.Now.Millisecond))
appliedFoo.Match(x => x, ex => 1)
Для такого просится extension method на Try<Funс<A, B>>, но в данную библиотеку его не положили. Ну и название метода Try тоже за гранью добра и зла. Execute/Evaluate/ToResult/Perform — лучше бы передавало семантику. ToResult был бы ближе всего, если бы не было on demand вычислений. Или бы не делали кучу "удобств" и заставляли писать вручную Try().Match(...). Может тоже было бы понятней.
S>Поэтому здравомыслие автора библиотеки вызывает вопросы.
В плане применимости — жить можно. Это же монада. Потому и нужно с ней работать, как с монадой — Map/Bind/Apply. А вот всякие extractors — только в конце вычислений. Но без for comprehension/do notation это все очень не практично.
S>Если что-то непонятно, спрашивайте.
Аналогично. Если что не понятно про монады — спрашивайте (на шарпе я не очень пишу ). В качестве упражнения всем интересующимся рекомендую написать базовые методы Map и Bind на основе определения Try как алиаса типа.
Здравствуйте, maxkar, Вы писали:
M>Ожидать — логично. Но перегрузки Try<A>(A) и Try<A>(Exception) не должны были бы существовать вообще, чтобы избежать данной проблемы.
vaa>Почему? Разве функции не первоклассные сущности? Try это контейнер который может хранить все что вам захочется. vaa>Иначе композировать не получится.
Ну с точки зрения ФП все является функцией, а вот в ИП нет. Там я так понял, что есть импликит A к Try<A>
[Pure]