Comments 24
Конечно, я говорил про идеоматические подходы в каждом из языков.
Так что Scala в этом месте от Java почти не отличается.
Если вы автор библиотеки, например, вы скорее всего не будете тащить внешнюю зависимость от vavr или чего-то подобного. Соответственно, весь ваш арсенал будет ограничен тем, что есть в стандартном наборе. Так что отличия есть, и они весьма существенны.
А почему бы и нет? Например в resilience4j оно используется и ничем не мешает.
Очевидно же почему: чтобы не навязывать пользователю вашей библиотеки нерелевантных решений и чтобы избежать jar hell.
Вот сделали вы библиотеку для логгинга, которая зависит от project-reactor. Те, у кого в проекте нет реактивщины или, например Akka вместо project-reactor, — точно не оценят и будут искать другую библиотеку без этих нерелевантных зависимостей. Ваша библиотека не пройдет фильтр естественного отбора.
Понятно, что если у вас в описании к библиотеке написано, что она про FP и fault tolerance в Java, тащить vavr логично. Но я же говорю про библиотеки в общем случае.
Но я же говорю про библиотеки вообще.
Мне кажется что здесь и ошибка. Библиотеки не живут в вакууме, за редким исключением. И мы в данном случае говорим про обработку ошибок в ФП стиле, значит что моя библиотека будет отдавать ошибку в этой парадигме и делать свою реализацию для Try|Either
вообще нет смысла.
Хм… а я вот как-то стал очень разборчивым после всех этих депендеси хеллов в спарко-хадупе и прочих замечательных фрейморках, которые не могут без того, чтобы скачать пару гигабайт из мейвен централа, а потом на старте навернуться с чем-нибудь типа NoSuchMethodError. Лучше я возьму десять утилитарных zero-dependency либ, с каждой из которых легко разобраться в отдельности, чем одного огромного монстра, с котором непонятно что делать.
Я просто говорю немного о другом — в scala же Try это тоже просто класс. И он реализован на основе других средств языка (возможно — try/catch, я просто не вникал). Т.е. в этом смысле и то и другое реализованы не идентично, но по сути одинаково — как библиотека. То что одна входит в стандартный рантайм, а другая нет — это уже другая сторона медали. Как-то так.
Ну да, про это как раз был мой первоначальный комментарий. Try
просто класс, который все равно откуда придет, — это так только на прикладном уровне. На библиотечном уровне вы не будете подключать все подряд с целью минимизации зависимостей (и как следствие проблем у юзеров вашей библиотеки), поэтому там стандартная поставка имеет большое значение.
Поэтому мне нравится Reactor(или RxJava) — что там есть Mono/Flux
и через них удобно выразить и обработать и ошибки (Mono.error()
) и пустые значения ('Mono.empty()') и асинхронность с коробки.
Вы уже извините, но Option[Try[String]]
это ООП скалаизм а не функциональный стиль. В нормальном функциональном языке у вас есть такое понятие как Union Types, блеклое понятие которых добавили в Scala 3. Так что с версии Scala 3 (когда она выйдет лет так через 5) будет возможно писать что-то вроде Error | String | None
.
UPD — Вроде UT в скале 3 допилили и они выглядят неплохо.
В Scala 3 их добавили совсем для других целей и они почти не участвуют в выводе типов, так что использовать union types вряд ли будет удобно.
В Scala 2 тоже есть union types – всегда можно создать sealed family для этих вариантов, так же как можно создать case class для пары, вопрос тут только в целесообразности. Позволю себе с вами не согласиться – с точки зрения ФП Option[Try[String]]
и Throwable | String | None
это одно и то же.
В Scala 2 тоже есть union types – всегда можно создать sealed family ...
Вы серьезно? Sealed не для этого создавались, это костыль в данном случае.
с точки зрения ФП Option[Try[String]] и Throwable | String | None это одно и то же.
Я такого не говорил, с точки зрения смеси ФП с ООП это костыль, с точки зрения ФП монада в монаде, это увы перебор.
В Scala 3 их добавили совсем для других целей
А по моему у вас неверные сведения, загляните в документацию(секция мотивации):
The primary reason for introducing union types in Scala is that they allow us to guarantee that for every set of types, we can always form a finite LUB. This is both useful in practice (infinite LUBs in Scala 2 were approximated in an ad-hoc way, resulting in imprecise and sometimes incredibly long types) and in theory (the type system of Scala 3 is based on the DOT calculus, which has union types).
Вы серьезно? Sealed не для этого создавались, это костыль в данном случае.
Вы серьезно? Именно для этого, sealed позволяет компилятору определять полноту кейсов и собственно является SUM частью алгебраических типов.
Я такого не говорил, с точки зрения смеси ФП с ООП это костыль, с точки зрения ФП монада в монаде, это увы перебор
Вы этого не говорили, тем не менее с точки зрения теории категорий и то и другое суть копродукт, о монадах речь вообще не шла и тут не важно, монады это или нет
А по моему у вас неверные сведения
Приведенная вами цитата не опровергает моего утверждения. Union types были добавлены для облегчения вывода типов, чтобы преждевременно не выводить Product и т.п. слишком общие и бесполезные типы. Увы, на практике это означает, что при выводе типов результатом почти никогда не будет юнион, иначе вывод типов бы вообще не работал и выводил бы Seq(1, 1.5): Seq[Int|Double]
. В связи с этим вам придется почти всегда вручную указывать тип, что делает его очень неудобным. Плюс юнион не может быть рекурсивным. В целом буду рад ошибаться, но Scala не светит нормальный Haskell-like union в ближайшем будущем.
Кстати, а что такое "Haskell-like union", учитывая что в Хаскеле нет типов-объединений и там программисты точно так же при необходимости вкладывают Either в Maybe?
Типы-суммы и типы-объединения всё-таки немного разные вещи. Нельзя в Хаскеле написать Error | String | None
и получить их объединение (но можно Error Error | String String | None
и получить их сумму).
Типы суммы и типы объединения это одно и то же. Но тут вы правы: можно сказать что этот union-type из Scala 3 это untagged union в отличии от tagged union в Haskell и sealed types/enums в Scala.
Брат жены и жена брата это конечно — люди, но в деталях обычно — разные персонажи. Так же и в ФП: Maybe List и List Maybe это не совсем одно и то же. Придумать подходящую трансформацию из одного в другое получается не всегда.
Пример тут ни к месту, List
это не сумма, это рекурсивный тип, представляющий собой степень (Int^n = Int × Int × ...
), конечно Int^n × Unit <> (Int × Unit) ^ n
.
Объединение это термин из теории множеств, а сумма — категорий, но описывают они одно и то же для типов.
Объединение это термин из теории множеств, а сумма — категорий, но описывают они одно и то же для типов.
Ну уж нет, у них разные свойства:
T | T = T
T + T != T
Тут я конечно имел в виду сумму, а написал продукт, должно быть:
List Int = Unit + (Int × List Int) = Unit + Int × Int × ... = Int^n
Int^n + Unit <> (Int + Unit) ^n
Еще раз про try и Try