Комментарии 27
Очень упрощенный пример… Как оно в реальной жизни будет работать, когда функции не будут такие замечательные — обязательно с одним параметром? Я подозреваю, что этих проверок switch + Success + Failure будет как проверок «if( res != NULL )» в С, или не будет вообще (оставят без проверок) — уж больно многословные и код загромождают.
Как по мне, лучше exceptions для обработки ошибок пока ничего не придумали.
Как по мне, лучше exceptions для обработки ошибок пока ничего не придумали.
+1
В swift можно каррировать функции, так что проблемы с количеством параметров толком нет.
0
В статье по ссылке сам автор статьи пишет — "… Я не уверен, что я найду им применение в Свифте..."
То есть вы предлагаете применить для решения проблемы инструмент, который люди обычно находят странным и редко используют :-)
Получается, чтобы избавиться от массы кода по обработке ошибок, код нужно структурировать в виде как можно более длинной последовательности вызовов функций с одним параметром, а всякие неудобные дополнительные параметры прятать каррированием — насколько легко такой код будет читать?
Я не ради холивара — я понять пытаюсь…
I agree that curried functions are pretty odd at first. They definitely are an acquired taste! I’m not sure if I will find them useful in Swift, time will tell.
То есть вы предлагаете применить для решения проблемы инструмент, который люди обычно находят странным и редко используют :-)
Получается, чтобы избавиться от массы кода по обработке ошибок, код нужно структурировать в виде как можно более длинной последовательности вызовов функций с одним параметром, а всякие неудобные дополнительные параметры прятать каррированием — насколько легко такой код будет читать?
Я не ради холивара — я понять пытаюсь…
+2
Имхо, автор не изобрел ничего нового. В данном случае
Каррирование — тоже не такая уж дикая вещь. Напрямую он мало где пока поддерживается, но точно того же результата можно достичь практически в любом другом языке с помощью анонимных функций. В Swift, если я правильно понимаю, передать функцию с несколькими аргументами в
map
— обычная монада, в которую вводятся данные и над которой производится цепочка вычислений. Это не значит, что всё нужно делать через нее — скорее думать головой и использовать ее там, где это действительно улучшило бы качество кода.Каррирование — тоже не такая уж дикая вещь. Напрямую он мало где пока поддерживается, но точно того же результата можно достичь практически в любом другом языке с помощью анонимных функций. В Swift, если я правильно понимаю, передать функцию с несколькими аргументами в
map
и не потерять в читаемости можно было бы вот так:getData().map({result in processData(result, scheme: someScheme)})
+1
Замечательная статья — плюсую.
Но это лишь самая первая статья Alexandros Salazar из серии блестящих статей, очень осторожно подводящих нас, пришедших из Objective-C и несведущих в основных концепциях функционального программирования, к понятиям Монады (Monads), Функторы, Аппликативные Функторы (Applicative Functors) и Каррирование (Currying) в Swift.
В заключительной статье The Culmination: Final Part (Кульминация — Финальная часть) он приходит к монадическим операторам, которые сейчас отсутствуют в Swift, но по мнению автора являются «Магическим будущим» Swift.
В моем арсенале семь его статей на эту тему.
Продолжайте выкладывать статьи Alexandros Salazar.
В этих статьях с целью краткости и простоты изложения приводится лишь набросок кода, иллюстрирующий ту или иную идею.
Поэтому я изучала его статьи с параллельным кодированием в Swift — «Управление ошибками и Optionals в Swift – интерпретация в коде».
Но это лишь самая первая статья Alexandros Salazar из серии блестящих статей, очень осторожно подводящих нас, пришедших из Objective-C и несведущих в основных концепциях функционального программирования, к понятиям Монады (Monads), Функторы, Аппликативные Функторы (Applicative Functors) и Каррирование (Currying) в Swift.
В заключительной статье The Culmination: Final Part (Кульминация — Финальная часть) он приходит к монадическим операторам, которые сейчас отсутствуют в Swift, но по мнению автора являются «Магическим будущим» Swift.
В моем арсенале семь его статей на эту тему.
Продолжайте выкладывать статьи Alexandros Salazar.
В этих статьях с целью краткости и простоты изложения приводится лишь набросок кода, иллюстрирующий ту или иную идею.
Поэтому я изучала его статьи с параллельным кодированием в Swift — «Управление ошибками и Optionals в Swift – интерпретация в коде».
+1
Это жесть какая-то.
В С++, Java и других языка, поддерживающих исключения код будет выглядеть гораздо проще. Вот примерно так.
А тут тот же Objective C вид сбоку.
В С++, Java и других языка, поддерживающих исключения код будет выглядеть гораздо проще. Вот примерно так.
try {
float quotient = divide(1,0);
}
catch(ZeroDivisionException e) {
println("zero division");
}
А тут тот же Objective C вид сбоку.
-1
Я не уверен, почему в Swift отказались от реализации механизма исключений — скорее всего, из-за проблем совместимости с Objective C и ручным управлением ресурсами. Субъективно мне тоже механизм исключений кажется наиболее логичным способом уведомить об ошибке, но обработку ошибок (особенно если общаемся с сервером и нужен auto-retry) иногда нет-нет да и хочется обернуть в аналогичный функтор. Кроме того, было бы интересно сравнить производительность перехвата исключений и обработки Result.
0
Думаю что это получилось сразу по нескольким причин:
Во-первых, из-за совместимостью с obj-c,
И во-вторых, из-за того что Swift взял курс на функциональщину по сравнению с obj-c.
Дело в том, насколько я знаю, исключения (такие как в C++ и Java) не очень дружат с функциональщиной, так как выброс исключения это по сути side-effect.
Во-первых, из-за совместимостью с obj-c,
И во-вторых, из-за того что Swift взял курс на функциональщину по сравнению с obj-c.
Дело в том, насколько я знаю, исключения (такие как в C++ и Java) не очень дружат с функциональщиной, так как выброс исключения это по сути side-effect.
0
Ну Страуструп как-то добавил исключения в С++ не убив из-за этого совместимость С++. А про сайд-эффект, так передача индикатора неудачного результата по цепочке это точно то же самое, что исключения, только с бойлерплейтом и без оптимизаций компилятором. Сайд-эффект же не в самом выбросе исключения, а потенциально в его обработке.
-1
Нет, сайд эффект как раз в выбросе исключения.
Есть вот такой пример, в языке Scala есть монада Try.
Суть в том, что на самом деле Try технически не является монадой, т.к. нарушается первый закон
f(x) == unit(x).bind(f).
(где unit(x) — это конечно Success(x)).
Допустим f(x) = x / 0.
Получается
x / 0 == Success(x).bind(_ / 0)
Разворачиваем правую сторону.
x / 0 == try { Success(x / 0) } catch (e) { Failure(e) }
Очевидно что левая и правая стороны не равны, так как в правой стороне мы получим Failure(DivByZero), а в левой стороне мы НЕ получим результата, но при этом будет выброшено исключение.
Так если исключения менее «функциональны», то зачем их вставлять в язык? Лучше уж переиспользовать существующие механизмы, особенно если они лучше ложатся на функциональный подход.
Есть вот такой пример, в языке Scala есть монада Try.
Суть в том, что на самом деле Try технически не является монадой, т.к. нарушается первый закон
f(x) == unit(x).bind(f).
(где unit(x) — это конечно Success(x)).
Допустим f(x) = x / 0.
Получается
x / 0 == Success(x).bind(_ / 0)
Разворачиваем правую сторону.
x / 0 == try { Success(x / 0) } catch (e) { Failure(e) }
Очевидно что левая и правая стороны не равны, так как в правой стороне мы получим Failure(DivByZero), а в левой стороне мы НЕ получим результата, но при этом будет выброшено исключение.
Так если исключения менее «функциональны», то зачем их вставлять в язык? Лучше уж переиспользовать существующие механизмы, особенно если они лучше ложатся на функциональный подход.
+2
Всегда был уверен, что сайд эффект — это когда функция делает что-то помимо возврата результата. Тут не так. Тут как раз что справа, что слева происходит одно и то же. Только в случае с исключениями процесс автоматизирован, а в случае с вводом перечисления как результата все контролируется вручную. Наверное есть какие-то ситуации, когда это к лучшему, но в стандартных случаях это как правило минус.
Механизм исключений позволяет не вводить дополнительного возвращаемого типа, позволяет не вводить метод map и при этом писать понятный и простой код. Также позволяет не писать в функциональном стиле, если тебе это не нравится и писать когда это приятно.
Преимущества функционального подхода (помимо эстетических) тут вообще не совсем понятны. Или в Swift есть возможность объявить чистую функцию? Или компилятор умеет доказывать её чистоту?
Механизм исключений позволяет не вводить дополнительного возвращаемого типа, позволяет не вводить метод map и при этом писать понятный и простой код. Также позволяет не писать в функциональном стиле, если тебе это не нравится и писать когда это приятно.
Преимущества функционального подхода (помимо эстетических) тут вообще не совсем понятны. Или в Swift есть возможность объявить чистую функцию? Или компилятор умеет доказывать её чистоту?
-1
>Механизм исключений позволяет не вводить дополнительного возвращаемого типа
Все недостатки в одном предложении. Зачем городить язык с мощной системой типов и потом убивать все исключениями, не отражаенными в сигнатурах функций?
Все недостатки в одном предложении. Зачем городить язык с мощной системой типов и потом убивать все исключениями, не отражаенными в сигнатурах функций?
+1
Тут есть 2 пункта.
В джаве есть исключения, добавляемые в сигнатуру метода. К счастью их можно не использовать и видеть только в кошмарных снах :). Практика показывает, что это плохая идея.
И второй пункт. Вы хотите сказать, что в примере из статьи сигнатура функции отражает что-то кроме того, что в ней может возникнуть исключительная ситуация?
В джаве есть исключения, добавляемые в сигнатуру метода. К счастью их можно не использовать и видеть только в кошмарных снах :). Практика показывает, что это плохая идея.
И второй пункт. Вы хотите сказать, что в примере из статьи сигнатура функции отражает что-то кроме того, что в ней может возникнуть исключительная ситуация?
0
Всегда был уверен, что сайд эффект — это когда функция делает что-то помимо возврата результата.
Все несколько сложнее.
Чистота функции это не совсем про «делать что-то помимо возврата результата». Давайте попробуем посмотреть на следующий псевдокод:
val m: Map[Int, Int]
def get(k: Int): Int {
var v = m.get(k)
if (v == null) {
v = /* pure calc */
m.put(k, v)
}
return v
}
Является ли этот метод чистым? Он ведь что-то делает помимо возврата результата.
Механизм исключений позволяет не вводить дополнительного возвращаемого типа, позволяет не вводить метод map и при этом писать понятный и простой код.
Давайте посмотрим на это с другой стороны. «Метод» map позволяет не вводить дополнительный механизм обработки исключений, при этом позволяет писать понятный и простой код, в котором можно четко сказать на каком участке кода какие исключительные ситуации могут возникнуть, обеспеченный проверку типов компилятором.
Также позволяет не писать в функциональном стиле, если тебе это не нравится и писать когда это приятно.
Мне показалось вы допускаете использование исключений в функциональном коде. По моему опыту лучше этого избегать.
Функциональный подход — другой стиль программирования, со своими плюсами и минусами, которые зачастую не сразу очевидны. В данном случае, плюсом является, на мой взгляд, является типобезопасность. Обычно это обязывает вас обрабатывать большинство краевых случаев, всегда думать о том где какие результаты будут. По факту это означает обнаружение ошибок на более ранних этапах разработки, но как минус (субъективно) более медленное написание кода.
И для этого не обязательно иметь возможность объявить чистую функцию.
Если Вам действительно интересно, то, наверное, Вам стоит поближе познакомится с ФП языками и с тем как ФП применяют в императивных языках. Из меня, увы, плохой рассказчик.
+1
Ну метод явно нечистый, поскольку сайд-эффект налицо.
А то, что метод map позволяет не вводить дополнительный механизм обработки исключений это безусловно хороший лайфхак для языков, в которых обработки исключений нет.
И может быть в функциональных языках действительно можно писать простой и понятный код безо всяких исключений. Но в приведённых примерах код точно такой же, каким был бы с исключениями. Только вот для этого пришлось добавить специальный тип для возврата значения и обязать всех им пользоваться.
О проверке типов компилятором и четком понимании на каком участке кода какие ситуации могут возникнуть тут речи нет, вернее компилятору понять такой код не проще, чем код с исключениями.
Про типобезопасность и обработку краевых случаев тоже непонятно. Возьмём например то самое деление, которое в статье. И в том и в другом случае надо проверить нету ли там нуля. И это должен сделать программист. И он одинаково эффективно забудет об этом и в том и в другом случае и компилятор ему не поможет.
В функциональных языках многое прекрасно. И применению ФП в императивных языках тоже есть место, но оно не в обработке исключительных ситуаций.
И если бы в обмен на отсутствие исключений мы получили возможность объявить чистую функцию, результаты выполнения которой можно кешировать и параллелить, то это можно было бы декларировать фичей языка. Но я так понял об этом нет и речи. Может есть хотя бы модификатор const для методов, как в С++?
А то, что метод map позволяет не вводить дополнительный механизм обработки исключений это безусловно хороший лайфхак для языков, в которых обработки исключений нет.
И может быть в функциональных языках действительно можно писать простой и понятный код безо всяких исключений. Но в приведённых примерах код точно такой же, каким был бы с исключениями. Только вот для этого пришлось добавить специальный тип для возврата значения и обязать всех им пользоваться.
О проверке типов компилятором и четком понимании на каком участке кода какие ситуации могут возникнуть тут речи нет, вернее компилятору понять такой код не проще, чем код с исключениями.
Про типобезопасность и обработку краевых случаев тоже непонятно. Возьмём например то самое деление, которое в статье. И в том и в другом случае надо проверить нету ли там нуля. И это должен сделать программист. И он одинаково эффективно забудет об этом и в том и в другом случае и компилятор ему не поможет.
В функциональных языках многое прекрасно. И применению ФП в императивных языках тоже есть место, но оно не в обработке исключительных ситуаций.
И если бы в обмен на отсутствие исключений мы получили возможность объявить чистую функцию, результаты выполнения которой можно кешировать и параллелить, то это можно было бы декларировать фичей языка. Но я так понял об этом нет и речи. Может есть хотя бы модификатор const для методов, как в С++?
0
Уважаемый poxu, я не имею ни желания ни возможности разводить холивары и повторять о том, что я уже писал выше.
Если Вы хотите понять почему же исключения не приживаются в ФП, каким таким волшебным образом код становится безопаснее и чище, да и еще от добавления каких то непонятных «дополнительных» возвращаемых типов, которых еще и руками нужно обрабатывать, то Вам придется либо самому все потрогать, либо дождаться другого собеседника.
Если Вы хотите понять почему же исключения не приживаются в ФП, каким таким волшебным образом код становится безопаснее и чище, да и еще от добавления каких то непонятных «дополнительных» возвращаемых типов, которых еще и руками нужно обрабатывать, то Вам придется либо самому все потрогать, либо дождаться другого собеседника.
0
>> а в левой стороне мы НЕ получим результата, но при этом будет выброшено исключение.
Это вопрос терминологии. Если исключение считать результатом (т.е. если рассматривать любое выражение в языке как Succes(value)|Error(exception), то все ок. Просто семантика == в данном случае не эквивалентна нормальной функциональной, но это уже другой разговор.
Это вопрос терминологии. Если исключение считать результатом (т.е. если рассматривать любое выражение в языке как Succes(value)|Error(exception), то все ок. Просто семантика == в данном случае не эквивалентна нормальной функциональной, но это уже другой разговор.
0
Ну и отлично, меньше любителей исключений будут писать на Swift ;)
+2
А в свифте можно поднимать в монаду Maybe функции произвольной арности? Хотя бы бинарные? Или — только унарные, только через функцию-член .map?
0
Эта структура на Хаскеле пишется так (и находится в стандартной библиотеке):
Хочу заметить, что можно параметрезировать и «ошибочную» часть
data Either a b = Left a | Right b
instance Functor (Either a) where
fmap _ (Left x) = Left x
fmap f (Right y) = Right (f y)
Хочу заметить, что можно параметрезировать и «ошибочную» часть
0
В Swift можно использовать тип
Мы можем использовать
Мы немного упростили это, предполагая, что
Вместо этого используется подобный, но другой тип
В настоящий момент в Swift перечисления
И финальное «раскрытие»
Either<A, B>
. Это позволит нам вернуть пользователю объект как в случае, если все проходит успешно, так и в случае возникновения ошибки. В Swift можно так реализовать тип Either<A, B>
:enum Either<A, B> {
case Left(A)
case Right(B)
}
Мы можем использовать
Either<NSError, User>
в качестве типа, который на финальном этапе преобразований может быть обработан так:switch either {
case let .Left(error):
// показываем сообщение об ошибке (error)
case let .Right(user):
// делаем что-то с user
}
Мы немного упростили это, предполагая, что
Left
всегда будет NSError
. Вместо этого используется подобный, но другой тип
Result <A>
, который будет содержать либо значение, которое мы ищем, либо ошибку. Его реализация выглядит так:enum Result<A> {
case Error(NSError)
case Value(A)
}
В настоящий момент в Swift перечисления
enum
не могут быть дженериками (generic) на самом топовом уровне, но, могут быть представлены как generic, если их обернуть в «постоянный» class box:final class Box<A> {
let value: A
init(_ value: A) {
self.value = value
}
}
enum Result<A> {
case Error(NSError)
case Value(Box<A>)
}
И финальное «раскрытие»
result: Result <User>
будет выглядеть так:switch result {
case let .Error(error):
// показываем сообщение об ошибке (error)
case let .Value(boxedUser):
let user = boxedUser.value
// делаем что-то с user
}
0
или так
public enum Result {
case Success(@autoclosure() -> T)
case Failure(String)
}
public enum Result {
case Success(@autoclosure() -> T)
case Failure(String)
}
0
К сожалению, так не получится. Уже соблазнялись на это David Owens «Fixed Enum Layout Sizes», но отвергли из-за идемпотентности:
var v1: Int =0
var result1 = Result.Success(v1++)
result1.value // 0
result1.value // 1
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Обработка ошибок в Swift — меч и магия