Почему я не могу FlatMap попробовать?

Учитывая

val strings = Set("Hi", "there", "friend")
def numberOfCharsDiv2(s: String) = scala.util.Try { 
  if (s.length % 2 == 0) s.length / 2 else throw new RuntimeException("grr") 
}

Почему я не могу выполнить FlatMap из Try в результате вызова метода? то есть.

strings.flatMap(numberOfCharsDiv2)
<console>:10: error: type mismatch;
 found   : scala.util.Try[Int]
 required: scala.collection.GenTraversableOnce[?]
              strings.flatMap(numberOfCharsDiv2)

или

for {
  s <- strings
  n <- numberOfCharsDiv2(s)
} yield n
<console>:12: error: type mismatch;
 found   : scala.util.Try[Int]
 required: scala.collection.GenTraversableOnce[?]
            n <- numberOfCharsDiv2(s)

Однако, если я использую Option вместо Try, нет проблем.

def numberOfCharsDiv2(s: String) = if (s.length % 2 == 0) 
  Some(s.length / 2) else None
strings.flatMap(numberOfCharsDiv2) # =>  Set(1, 3)

Какое обоснование не позволяет FlatMap при попытке?

Ответ 1

Посмотрим на подпись flatMap.

def flatMap[B](f: (A) => GenTraversableOnce[B]): Set[B]

Ваш numberOfCharsDiv2 отображается как String => Try[Int]. Try не является подклассом GenTraversableOnce, и поэтому вы получаете ошибку. Вам строго не нужна функция, которая дает Set только потому, что вы используете flatMap на Set. Функция в основном должна возвращать любую коллекцию.

Так почему он работает с Option? Option также не является подклассом GenTraversableOnce, но существует неявное преобразование внутри объекта-компаньона Option, который преобразует его в List.

implicit def option2Iterable[A](xo: Option[A]): Iterable[A] = xo.toList

Тогда остается один вопрос. Почему бы не иметь неявное преобразование для Try? Потому что вы, вероятно, не получите то, что хотите.

flatMap можно рассматривать как map, за которым следует flatten.

Представьте, что у вас есть List[Option[Int]] как List(Some(1), None, Some(2)). Тогда flatten даст вам List(1,2) типа List[Int].

Теперь рассмотрим пример с Try. List(Success(1), Failure(exception), Success(2)) типа List[Try[Int]].

Как сгладить работу с отказом сейчас?

  • Должно ли оно исчезнуть, как None? Тогда почему бы не работать напрямую с Option?
  • Должен ли он быть включен в результат? Тогда это будет List(1, exception, 2). Проблема здесь в том, что тип List[Any], потому что вам нужно найти общий суперкласс для Int и Throwable. Вы теряете тип.

Это должны быть причины, по которым нет неявного преобразования. Конечно, вы можете сами определить это, если принимаете вышеуказанные последствия.

Ответ 2

Проблема заключается в том, что в вашем примере вы не планируете сопоставлять Try. План, который вы делаете, находится над множеством.

Flatmap over Set принимает набор [A] и функцию от A до Set [B]. Как отмечает Кигё в своем комментарии ниже, это не фактическая подпись типа flatmap on Set в Scala, но общая форма плоской карты:

M[A] => (A => M[B]) => M[B]

То есть, он требует некоторого более высокого типа, наряду с функцией, которая работает с элементами типа в этом более высокосортном типе, и возвращает вам тот же более высокий тип с отображенными элементами.

В вашем случае это означает, что для каждого элемента вашего Set flatmap ожидает вызова функции, которая принимает строку, и возвращает набор некоторого типа B, который может быть String (или может быть чем-то еще).

Ваша функция

numberOfCharsDiv2(s: String)

правильно берет String, но неправильно возвращает Try, а не другой Set, как требуется flatmap.

Ваш код будет работать, если вы использовали 'map', поскольку это позволяет вам взять некоторую структуру - в этом случае Set и запустить функцию над каждым элементом, преобразующим ее из A в B без возвращаемого типа функции, соответствующего т.е. возвращение набора

strings.map(numberOfCharsDiv2)

res2: scala.collection.immutable.Set[scala.util.Try[Int]] = Set(Success(1), Failure(java.lang.RuntimeException: grr), Success(3))