Pull to refresh

Comments 24

Я бы хотел отметить, что есть скажем vavr.io (и это только один пример), когда в Java применяется ровно такой же подход. Так что Scala в этом месте от Java почти не отличается.

Конечно, я говорил про идеоматические подходы в каждом из языков.

Так что Scala в этом месте от Java почти не отличается.

Если вы автор библиотеки, например, вы скорее всего не будете тащить внешнюю зависимость от vavr или чего-то подобного. Соответственно, весь ваш арсенал будет ограничен тем, что есть в стандартном наборе. Так что отличия есть, и они весьма существенны.

А почему бы и нет? Например в resilience4j оно используется и ничем не мешает.

Очевидно же почему: чтобы не навязывать пользователю вашей библиотеки нерелевантных решений и чтобы избежать jar hell.


Вот сделали вы библиотеку для логгинга, которая зависит от project-reactor. Те, у кого в проекте нет реактивщины или, например Akka вместо project-reactor, — точно не оценят и будут искать другую библиотеку без этих нерелевантных зависимостей. Ваша библиотека не пройдет фильтр естественного отбора.


Понятно, что если у вас в описании к библиотеке написано, что она про FP и fault tolerance в Java, тащить vavr логично. Но я же говорю про библиотеки в общем случае.

Но я же говорю про библиотеки вообще.

Мне кажется что здесь и ошибка. Библиотеки не живут в вакууме, за редким исключением. И мы в данном случае говорим про обработку ошибок в ФП стиле, значит что моя библиотека будет отдавать ошибку в этой парадигме и делать свою реализацию для Try|Either вообще нет смысла.

Зависит от размеров библиотеки или фреймворка. Я обычно не стесняясь затаскиваю как зависимость что-то типа груви, если нужен скриптовый язык в фреймворке. А уж vavr это вообще мелочь, которая кажется с собой вообще ничего не тянет.

Хм… а я вот как-то стал очень разборчивым после всех этих депендеси хеллов в спарко-хадупе и прочих замечательных фрейморках, которые не могут без того, чтобы скачать пару гигабайт из мейвен централа, а потом на старте навернуться с чем-нибудь типа NoSuchMethodError. Лучше я возьму десять утилитарных zero-dependency либ, с каждой из которых легко разобраться в отдельности, чем одного огромного монстра, с котором непонятно что делать.

Не, я вас вполне понимаю (особенно в спарко-хадупе, где ты затянул свою версию httpcomponents, и но отвалилось хз в каком месте).

Я просто говорю немного о другом — в 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?

Есть, они же sum types, их нельзя указать ad-hoc, есть возможность объявить именованное объединение. Как и в Scala через sealed family или в Scala 3 через enum (который тоже только синтаксический сахар).

Типы-суммы и типы-объединения всё-таки немного разные вещи. Нельзя в Хаскеле написать 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

Согласен, как я писал выше — это как раз разница между tagged и untagged. Второе это T × Tag.

Тут я конечно имел в виду сумму, а написал продукт, должно быть:


List Int = Unit + (Int × List Int) = Unit + Int × Int × ... = Int^n
Int^n + Unit <> (Int + Unit) ^n
Sign up to leave a comment.

Articles