功能性存储库 - 基于 find()/create() 方法构建 get()

Functional reposiotory - build get() based on find()/create() methods

我有这样的代数

object Algebra {
  case class Product(id: String, description: String)
  case class ShoppingCart(id: String, products: List[Product])

  trait ShoppingCarts[F[_]] {
    def create(id: String): F[Unit]
    def get(id: String): F[ShoppingCart]
    def find(id: String): F[Option[ShoppingCart]]
  }
}

我想到了以下实现。但我想知道是否有可能在特征本身内将其实现为通用方法。我试图将上下文绑定到函子以获取对地图的访问权限,但这不是有效的构造。

override def get(id: String): ScRepoState[ShoppingCart] =
  find(id).flatMap {
    case Some(sc) => sc.pure[ScRepoState]
    case None => create(id) *> get(id)
  }

另一个问题是实现 addMany() 方法。我得到了这样的东西

def addMany[F[_] : Monad](cartId: String, products: List[Product])(implicit shoppingCarts: ShoppingCarts[F]): F[ShoppingCart] = {
    for {
      cart    <- shoppingCarts.get(cartId)
      product <- products.pure[F]
      newCart <- product.traverse(product => shoppingCarts.add(cart, product))
    } yield newCart
  }

我很难在单个 for comprehension 块中混合不同的包装器

But I wonder if it would be possible to implement it as generic method within the trait itself.

不完全是。 Scala 2 不允许特征具有参数,但您可以使用抽象 class 代替。您可以不完全使用 trait,也可以使用默认的 class 和所有可派生的实现,例如:

abstract class DefaultShoppingCarts[F[_]: Monad] extends ShoppingCarts[F] {
  override def get(id: String): F[ShoppingCart] =
    find(id).flatMap {
      case Some(sc) => sc.pure[F]
      case None     => create(id) >> get(id)
    }
}

这是我的首选方法,但还有其他方法可以直接更改特征。


您可以向方法添加 Monad 参数:

trait ShoppingCarts[F[_]] {
  def create(id: String): F[Unit]
  def find(id: String): F[Option[ShoppingCart]]
  def get(id: String)(implicit F: Monad[F]): F[ShoppingCart] =
    find(id).flatMap {
      case Some(sc) => sc.pure[F]
      case None     => create(id) >> get(id)
    }
}

这与我们在摘要 class 示例中所做的完全不同,b/c ShoppingCartsuse site 将被迫有一个可用的 monad 而不是 construction site,如果实现者想要重写该方法,即使未使用 Monad[F] 也必须完全复制签名。


您还可以模拟特征参数对抽象隐式定义的作用:

  trait ShoppingCarts[F[_]] {
    implicit protected def F: Monad[F]
    def create(id: String): F[Unit]
    def get(id: String): F[ShoppingCart] =
      find(id).flatMap {
        case Some(sc) => sc.pure[F]
        case None     => create(id) >> get(id)
      }
    def find(id: String): F[Option[ShoppingCart]]
  }

这可行,但在实施 F 成员时,您更有可能 运行 陷入隐含范围的技术问题。


I struggle how to mix different wrappers within single for comprehension block

你不知道。不允许混合。不要对列表使用 for-comprehension,只对 F 使用它。在一些更复杂的情况下,您可能想要嵌套理解,或使用 monad 转换器,但在这里您只需要在 F 中工作。我也不确定 add 的 return 类型是什么,但假设它是 F[ShoppingCart]:

def addMany[F[_] : Monad](cartId: String, products: List[Product])(implicit shoppingCarts: ShoppingCarts[F]): F[ShoppingCart] = {
    for {
      cart    <- shoppingCarts.get(cartId)
      results <- products.traverse(product => shoppingCarts.add(cart, product))
      // results is a list of intermediate carts, get the last one; fallback if list was empty
    } yield results.lastOption.getOrElse(cart)
  }

另外第二个问题下次再单独问