Невозможно использовать flatMap в качестве метода расширения для самописного экземпляра монады
Я пробовал использовать flatMap на WriterT, и это было успешно.
Так что проблема, наверное, в моем типе, но я не могу найти, что с ним не так.
import cats.Monad
import cats.syntax.flatMap._
object Main extends App {
type Optional[A] = A | Null
val maybeInt1: Optional[Int] = 1
val maybeInt2: Optional[Int] = null
given Monad[Optional] with {
def pure[A](x: A): Optional[A] = x
def flatMap[A, B](fa: Optional[A])(f: A => Optional[B]): Optional[B] = {
fa match {
case null => null
case a: A => f(a)
}
}
def tailRecM[A, B](a: A)(f: A => Optional[Either[A, B]]): Optional[B] = {
f(a) match {
case null => null
case Left(a1) => tailRecM(a1)(f)
case Right(b) => b
}
}
}
def f[F[_]: Monad, A, B](a: F[A], b: F[B]) = a.flatMap(_ => b)
println(Monad[Optional].flatMap(maybeInt1)(_ => maybeInt2)) //OK: null
println(f[Optional, Int, Int](maybeInt1, maybeInt2)) // OK: null
println(maybeInt1.flatMap(_ => maybeInt2)) // Compilation Error
}
Ошибка:
value flatMap не является членом Main.Optional[Int].
Был опробован метод расширения, но не удалось полностью
создать : cats.syntax.flatMap.toFlatMapOps([A] =>> Any), A(given_Monad_Optional)
2 ответа
В вашем определении есть несколько проблем.
Проблема 1. Вы используете псевдоним непрозрачного типа для непараметрического типа.
Т.е.
type Optional[A] = A | Null
- это выражение типа, которое будет развернуто при первой возможности. Когда вы используете его в качестве типа результата, вы фактически получаете
val maybeInt1: Int | Null = 1
val maybeInt2: Int | Null = null
Итак, когда в компиляторе есть что-то вроде
implicit def toFlatMapOps[F[_], A](fa: F[A])(implicit F: Monad[F]): MonadOps[F, A]
импортируется из библиотеки scala 2 или эквивалентного расширения в scala 3, и, наконец, доходит до
maybeOption.flatMap
,
затем пытается применить прежний метод расширения,
ему не удается проверить тип выражения
toFlatMapOps(maybeInt1).flatMap(_ => maybeInt2)
Итак, теперь у вас есть
Int | Null
в качестве аргумента, поскольку они уже были расширены, и необходимо вычислить соответствующие
F[_]
и у него есть много решений, таких как
-
F[X] = Int | X , A = Null
-
F[X] = X | Null, A = Int
-
F[X] = A | Null, A = Nothing
-
F[X] = [X] =>> X, A = Int | Null
Таким образом, scala, естественно, терпит неудачу в этой попытке угадать.
Несмотря на то, что компилятор scala 3 может использовать здесь дополнительную информацию, такую как неявное \ контекстное значение, неявное сопоставление значений
Monad
с наивысшим приоритетом здесь
given Monad[Optional]
Теперь можно попытаться применить toFlatMapOps[F = Maybe](mightInt1: Int | Null). Затем, имея
F[X] = X | Null
вам нужно рассчитать
A
знаю это
F[A] = Null | A
и у этого также есть много правдоподобных решений
-
A = Int
-
A = Int | Null
Так что даже если scala не откажет на первом этапе, он застрял бы здесь
Решение 1. Используйте псевдонимы непрозрачного типа
Добавлять
scalacOptions += "-Yexplicit-nulls"
в вашу конфигурацию sbt и попробуйте этот код
import cats.Monad
import cats.syntax.flatMap.given
object Optional:
opaque type Optional[+A] >: A | Null = A | Null
extension [A] (oa: Optional[A]) def value : A | Null = oa
given Monad[Optional] with
def pure[A](x: A): Optional[A] = x
def flatMap[A, B](fa: A | Null)(f: A => B | Null) =
if fa == null then null else f(fa)
def tailRecM[A, B](a: A)(f: A => Optional[Either[A, B]]): Optional[B] =
f(a) match
case null => null
case Left(a1) => tailRecM(a1)(f)
case Right(b) => b
type Optional[+A] = Optional.Optional[A]
@main def run =
val maybeInt1: Optional[Int] = 1
val maybeInt2: Optional[Int] = null
def f[F[_]: Monad, A, B](a: F[A], b: F[B]) = a.flatMap(_ => b)
println(Monad[Optional].flatMap(maybeInt1)(_ => maybeInt2)) //OK: null
println(f(maybeInt1, maybeInt2)) // OK: null
println(maybeInt1.flatMap(_ => maybeInt2)) // Compilation Error
Выпуск 2. Этот тип не монада
Даже в этой фиксированной версии
Optional[A]
не соответствует основным монадическим законам. Рассмотрим этот код.
def orElse[F[_], A](fa: F[Optional[A]])(default: => F[A])(using F: Monad[F]): F[A] =
fa.map(_.value).flatMap(fb => if fb == null then default else F.pure(fb : A))
def filterOne(x: Int): Optional[Int] = if x == 1 then null else x - 1
println(orElse(maybeInt1.map(filterOne))(3))
Первый метод пытается разрешить пропущенные значения с заданным вычисленным монадическим значением, второй просто отфильтровывает их. Итак, что мы ожидаем увидеть при оценке чего-то подобного?
orElse(maybeInt1.map(filterOne))(3)
Берем непустой наверное, потом заменяем
1
с отсутствующим местом, а затем немедленно исправить его с помощью прилагаемого. Так что я ожидал увидеть
3
но на самом деле мы получаем как результат, так как
null
внутри обернутого значения, рассматриваемого как отсутствующая ветвь для внешнего
Optional
во время flatMap. Это потому, что такой наивно определенный тип нарушает закон левого тождества
Ответ Одерского по теме
Это сработает, если вы сделаете Null отдельным классом или скомпилируете с -Yexplicit-nulls.
То, как все Null - это нижний тип. Таким образом, каждый экземпляр класса Optional [C] на самом деле является C.
Я попытался изменить определение Optional на
type Optional[A] = A
то неявное тоже не обнаруживается. Таким образом, проблема не связана с типами объединения. Если Optional [A] определяет реальное объединение, это работает. Это скорее похоже на ограничение вывода типа HK, что он не может вывести идентичности. Думаю, этого и следовало ожидать.