Scala中的嵌套Monads组合

问题描述 投票:1回答:1

这是一个代码示例:

import cats.data.Reader

trait Configuration {  

  type FailFast[A] = Either[List[String], A]

  def getValue(name: String)(map: Map[String, String]): FailFast[String] =
    map.get(name)
      .toRight(List(s"$name field not specified"))

  type PropReader[A] = Reader[Map[String, String], A]
  def propReader(name:String): PropReader[FailFast[String]] =
    Reader(map => validation.getValue(name)(map))

  type OptionalValue[A] = PropReader[FailFast[Option[A]]]
  //how to use propReader(Configuration.NEW_EVENT)
  //inside of 'event' to return 'OptionalValue':?
  def event:OptionalValue[String] = ???
}      

object Configuration extends Configuration {
  final val NEW_EVENT = "event.unique"
}

无法得到它如何实现具有以下组成的事件:propReader(Configuration.NEW_EVENT)

如果有多个选项,那么考虑所有这些选项会很棒。

更新感谢@Travis Brown,我会以这种方式实现它。这是一个更新的实现:

  import cats.instances.list._ //for monoid
  import cats.instances.either._

  type FailFast[A] = Either[List[String], A]
  type PropReaderT[A] = ReaderT[FailFast, Map[String, String], A]
  type OptionalReaderT[A] = ReaderT[FailFast, Map[String, String], Option[A]]

  def getValue(name: String)(map: Map[String, String]): FailFast[String] =
    map.get(name).toRight(List(s"$name field not specified"))

  def propReader(name: String): PropReaderT[String] =
    ReaderT(getValue(name))

  def value2Option(value:String):Option[String] =
    if (value == null || value.isEmpty) Option.empty
    else Some(value)

  def event: OptionalReaderT[String] =
    propReader(Configuration.KEY1)
      .map(result => value2Option(result))

这与Travis Brown的实现之间的区别:我需要看到地图中没有键(这是一个错误,我需要一个明确的错误描述)和一个键存在的情况之间的区别,但它的价值null或空字符串。因此它与Maps.get的工作方式不同,后者返回Option。所以我无法摆脱FailFast

希望有人,这将是有用的。

scala monads optional composition scala-cats
1个回答
2
投票

最简单的方法是将结果映射到成功的None中:

import cats.data.Reader

trait Configuration {
  type FailFast[A] = Either[List[String], A]
  type PropReader[A] = Reader[Map[String, String], A]
  type OptionalValue[A] = PropReader[FailFast[Option[A]]]

  def getValue(name: String)(map: Map[String, String]): FailFast[String] =
    map.get(name).toRight(List(s"$name field not specified"))

  def propReader(name:String): PropReader[FailFast[String]] =
    Reader(getValue(name))

  def event: OptionalValue[String] = propReader(Configuration.NEW_EVENT).map(
    result => Right(result.right.toOption)
  )
}      

object Configuration extends Configuration {
  final val NEW_EVENT = "event.unique"
}

不过,我认为值得重新考虑这个模型。任何时候你有一个看起来像A => F[B]的函数(就像在地图中查找一样),你可以将它表示为ReaderT[F, A, B],它可以为你提供更好的组合 - 而不是通过两个层进行映射,例如,你只有一个。

ReaderT方法也使得改变F(通过mapK)更好一点。例如,假设在您的示例中,您通常希望与在FailFast上下文中返回其值的读者一起工作,但您需要偶尔切换到Option上下文。这看起来像这样:

import cats.~>
import cats.arrow.FunctionK
import cats.data.ReaderT

trait Configuration {
  type FailFast[A] = Either[List[String], A]
  type PropReader[A] = ReaderT[FailFast, Map[String, String], A]
  type OptionalReader[A] = ReaderT[Option, Map[String, String], A]

  private def eitherToOption[A](either: FailFast[A]): Option[A] =
    either.right.toOption

  def getValue(name: String)(map: Map[String, String]): FailFast[String] =
    map.get(name).toRight(List(s"$name field not specified"))

  def propReader(name: String): PropReader[String] =
    ReaderT(getValue(name))

  def event: OptionalReader[String] =
    propReader(Configuration.NEW_EVENT).mapK(FunctionK.lift(eitherToOption))
}      

object Configuration extends Configuration {
  final val NEW_EVENT = "event.unique"
}

这里的OptionalReader与你的OptionalValue不完全相同,因为它不包括FailFast层,但是你的代码中该层是多余的,因为缺失的值在Option层中表示,所以OptionReader方法很可能是更合适。

© www.soinside.com 2019 - 2024. All rights reserved.