Почему я не могу 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))