簡體   English   中英

扁平化貓中的混合嵌套單子(組成復雜的 Kleislis)

[英]Flattening mixed nested monads in cats (composing complex Kleislis)

我想了解我應該如何編寫返回“復雜”類型的 Kleislis(例如F[Either[A,B]] )。 這是我的意思的一個例子:

這是一個簡單的案例 - 給定兩個 Kleislis

val parseRegistration: Kleisli[Result, Auth.Registration, Auth.Registration] = ???
val hashPassword: Kleisli[Result, Auth.Registration, Auth.Registration] = ???

我可以這樣組合它們:

val validateAndHash = parseRegistration andThen hashPassword

一切都很好。

但是,考慮到更復雜的 Kleisli 類型(返回值為Either[A, B] ),我無法直接組合它們(因為我最終得到了 Kleisli 期望的F[Either[A,B]]只有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似乎沒有幫助,因為FResult不能相互展平。

我確信這是經驗豐富的 scala 貓程序員一直遇到的事情,所以如果您能幫助我了解如何避免讓自己陷入這種混亂,我將不勝感激?

編輯:

我現在定義了一個 function 來反轉嵌套的包裝器類型,以便它們可以被展平:

  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使這變得非常簡單。

首先,讓我們將createUsercreateSession重構為普通方法:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM