I've tried to use flatMap on WriterT and it was successful.
So the problem is with my type probably but I can't find what's wrong with it.
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
}
The error is:
value flatMap is not a member of Main.Optional[Int].
An extension method was tried, but could not be fully constructed:
cats.syntax.flatMap.toFlatMapOps([A] =>> Any), A(given_Monad_Optional)
Your definition has several issues.
Issue 1. You are using non-opaque type alias to a non-parametric type
I.e.
type Optional[A] = A | Nullis a type expression that will be expanded as soon as possible. When you are using it as a result type what you actually get isSo when the compile compiler has something like
imported from scala 2 library or equivalent extension in scala 3, and finally comes to
maybeOption.flatMap,then tries to apply former extension method,
it fails to typecheck the expression
toFlatMapOps(maybeInt1).flatMap(_ => maybeInt2)So now you have
Int | Nullas an argument sinceOptionalhave been already expanded and need to calculate correspondingF[_]andA, it has many solutions such asF[X] = Int | X , A = NullF[X] = X | Null, A = IntF[X] = A | Null, A = NothingF[X] = [X] =>> X, A = Int | NullSo scala naturally fails this attempt to guess.
Despite that scala 3 compiler can use additional information such as implicit\contextual value here, the implicit value matching
Monadwith the highest priority here isNow can can attempt to apply toFlatMapOps[F = Maybe](maybeInt1 : Int | Null) Then having
F[X] = X | Nullyou need to calculate theAknowing thatF[A] = Null | Aand that has many plausible solutions as wellA = IntA = Int | NullSo even if scala wouldn't fail at the first step it'd stuck here
Solution 1. Use Opaque Type Aliases
Add
scalacOptions += "-Yexplicit-nulls"to your sbt config and try this codeIssue 2. This type is not a monad
Even in this fixed version
Optional[A]fails basic monadic laws Consider this codeThe first method attempts to resolve missing values with the given calculated monadic value, the second just filter out ones. So, what do we expect to see when something like this evaluated?
We take non-empty maybe, then replacing the
1with the missing place, and then immediately fixing it using provided3. So I would expect to see3but actually, we gainnullas result sincenullinside the wrapped value considering as a missing branch for outerOptionalduring flatMap. This is because such naively defined type violates the left-identity lawUPDATE Regarding comment by @n-pronouns-m How this definition violates left identity law. Left identity states that
so lets take A = Optional[Int], B = Int, A = null, f(a) = if a == null then 3 else 2
pure(a) is still null, flatMap returns null for every in first argument, so
pure(a).flatMap(f) == nullwhilef(a) == 3