扁平化猫中的混合嵌套单子(构成复杂的 Kleislis)
Flattening mixed nested monads in cats (composing complex Kleislis)
我想了解我应该如何编写 return“复杂”类型(例如 F[Either[A,B]]
)的 Kleislis。这是我的意思的一个例子:
这是一个简单的案例 - 给定两个 Kleislis
val parseRegistration: Kleisli[Result, Auth.Registration, Auth.Registration] = ???
val hashPassword: Kleisli[Result, Auth.Registration, Auth.Registration] = ???
我可以这样组合它们:
val validateAndHash = parseRegistration andThen hashPassword
一切都很好。
然而,鉴于更复杂的 Kleisli 类型(其中 return 值是 Either[A, B]
),我无法直接组合它们(因为我最终得到 F[Either[A,B]]
Kleisli 只期望 F[A]
).
// relevant type definitions
final case class ServiceError(kind: ErrorKind, message: String = "", cause: Option[Throwable] = None)
final type Result[A] = Either[ServiceError, A]
final case class Registration(email: String, password: String)
final case class Registered(session: Session)
val createUser: Kleisli[F, Auth.Registration, Result[Long]] = ???
val createSession: Kleisli[F, Long, Result[Session]] = ???
// does not compile (no overloaded alternatives match the arguments) - I expected this as
// createUser returns F[Result[Long]] and the createSession Kleisli expects only F[Long]
val createUserAndSession = createUser andThen createSession
好的,正如预期的那样。然后,我没有组合它们,而是尝试简单地执行嵌套映射:
val r1 = validateAndHash(info) // Result[Auth.Registration]
val r2 = r1.map(i => createUser(i)) // Either[ServiceError, F[Result[Long]]]
val r3 = r2.map(_.map(_.map(uid => createSession(uid).map(_.map(Auth.Registered.apply)))))
// Now I've got an Either[ServiceError, F[Either[ServiceError, F[Either[ServiceError, Auth.Registered]]]]]
// in r3! I wanted to end up with F[Result[Auth.Registered]]
所以我最终导致嵌套越来越多,flatten
似乎没有帮助,因为 F
和 Result
不能相互压平。
我敢肯定这是经验丰富的 scala cats 程序员经常遇到的问题,所以如果您能帮助我了解如何避免让自己陷入这种困境,我将不胜感激?
编辑:
我现在定义了一个函数来反转嵌套包装器类型,这样它们就可以被展平:
def invert[S, F[_]: Monad, V](e: Either[S, F[V]]): F[Either[S, V]] =
e match {
case Left(s) => Monad[F].pure(Left(s))
case Right(fv) => fv.map(Right(_))
}
这允许我这样做:
val r1 = validateAndHash(info)
val r2 = invert(r1.map(i => createUser(i))).map(_.flatten)
val r3 = r2.map( i => invert(i.map(uid => createSession(uid).map(_.map(Auth.Registered.apply)))).map(_.flatten))
val r4 = Monad[F].flatten(r3)
这看起来……笨拙?而且对 Either
来说太具体了。有没有更简洁的方法使用猫内置的东西来做到这一点?
我宁愿组合值也不愿玩 Kleisli
,尤其是因为 EitherT
使这变得非常简单。
首先,让我们将 createUser
和 createSession
重构为普通方法:
def createUser[F[_]](registration: Registration): F[Result[Long]] = ???
def createSession[F[_]](long: Long): F[Result[Session]] = ???
现在让我们实施 createUserAndSession
import cats.Monad
import cats.data.EitherT
def createUserAndSession[F[_]](registration: Registration)(implicit ev: Monad[F]): F[Result[Session]] =
EitherT(createUser(registration)).flatMapF(createSession).value
我想了解我应该如何编写 return“复杂”类型(例如 F[Either[A,B]]
)的 Kleislis。这是我的意思的一个例子:
这是一个简单的案例 - 给定两个 Kleislis
val parseRegistration: Kleisli[Result, Auth.Registration, Auth.Registration] = ???
val hashPassword: Kleisli[Result, Auth.Registration, Auth.Registration] = ???
我可以这样组合它们:
val validateAndHash = parseRegistration andThen hashPassword
一切都很好。
然而,鉴于更复杂的 Kleisli 类型(其中 return 值是 Either[A, B]
),我无法直接组合它们(因为我最终得到 F[Either[A,B]]
Kleisli 只期望 F[A]
).
// relevant type definitions
final case class ServiceError(kind: ErrorKind, message: String = "", cause: Option[Throwable] = None)
final type Result[A] = Either[ServiceError, A]
final case class Registration(email: String, password: String)
final case class Registered(session: Session)
val createUser: Kleisli[F, Auth.Registration, Result[Long]] = ???
val createSession: Kleisli[F, Long, Result[Session]] = ???
// does not compile (no overloaded alternatives match the arguments) - I expected this as
// createUser returns F[Result[Long]] and the createSession Kleisli expects only F[Long]
val createUserAndSession = createUser andThen createSession
好的,正如预期的那样。然后,我没有组合它们,而是尝试简单地执行嵌套映射:
val r1 = validateAndHash(info) // Result[Auth.Registration]
val r2 = r1.map(i => createUser(i)) // Either[ServiceError, F[Result[Long]]]
val r3 = r2.map(_.map(_.map(uid => createSession(uid).map(_.map(Auth.Registered.apply)))))
// Now I've got an Either[ServiceError, F[Either[ServiceError, F[Either[ServiceError, Auth.Registered]]]]]
// in r3! I wanted to end up with F[Result[Auth.Registered]]
所以我最终导致嵌套越来越多,flatten
似乎没有帮助,因为 F
和 Result
不能相互压平。
我敢肯定这是经验丰富的 scala cats 程序员经常遇到的问题,所以如果您能帮助我了解如何避免让自己陷入这种困境,我将不胜感激?
编辑:
我现在定义了一个函数来反转嵌套包装器类型,这样它们就可以被展平:
def invert[S, F[_]: Monad, V](e: Either[S, F[V]]): F[Either[S, V]] =
e match {
case Left(s) => Monad[F].pure(Left(s))
case Right(fv) => fv.map(Right(_))
}
这允许我这样做:
val r1 = validateAndHash(info)
val r2 = invert(r1.map(i => createUser(i))).map(_.flatten)
val r3 = r2.map( i => invert(i.map(uid => createSession(uid).map(_.map(Auth.Registered.apply)))).map(_.flatten))
val r4 = Monad[F].flatten(r3)
这看起来……笨拙?而且对 Either
来说太具体了。有没有更简洁的方法使用猫内置的东西来做到这一点?
我宁愿组合值也不愿玩 Kleisli
,尤其是因为 EitherT
使这变得非常简单。
首先,让我们将 createUser
和 createSession
重构为普通方法:
def createUser[F[_]](registration: Registration): F[Result[Long]] = ???
def createSession[F[_]](long: Long): F[Result[Session]] = ???
现在让我们实施 createUserAndSession
import cats.Monad
import cats.data.EitherT
def createUserAndSession[F[_]](registration: Registration)(implicit ev: Monad[F]): F[Result[Session]] =
EitherT(createUser(registration)).flatMapF(createSession).value