简体   繁体   English

Scala中嵌套的Monads组成

[英]Nested Monads Composition in Scala

Here is a code example: 这是一个代码示例:

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"
}

Cannot get it how to implement event with a composition of: propReader(Configuration.NEW_EVENT) 无法获取如何通过以下内容来实现事件: propReader(Configuration.NEW_EVENT)

If there are more than 1 options, it would be great to consider all of them. 如果选项多于1个,那么最好考虑所有选项。

UPDATE thanks to @Travis Brown, I would implement it this way. 感谢@Travis Brown进行更新 ,我将以这种方式实现它。 Here is an updated implementation: 这是更新的实现:

  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))

The difference between this and Travis Brown's implementation: I need to see a difference between not having a key in the map (which is an error, and I need a clear error description of it) and a case when a key exists, but its value either null or empty string. 这与Travis Brown的实现之间的区别:我需要看到在映射中没有键(这是一个错误,并且我需要对其进行清晰的错误描述)与键存在但键值存在的情况之间的区别。 null或空字符串。 So it does not work in the same way as Maps.get, which returns Option. 因此,它与以Maps.get返回Option的方式不同。 So I cannot get rid of FailFast 所以我不能摆脱FailFast

Hope for someone, it will be useful. 希望对某人有用。

The simplest approach would be to map into the result, promoting failures into a successful None : 最简单的方法是将结果映射到结果,将失败升级为成功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"
}

I think it's worth reconsidering the model a bit, though. 我认为值得重新考虑该模型。 Any time you have a function that looks like A => F[B] (like a lookup in a map), you can represent it as a ReaderT[F, A, B] , which gives you nicer kinds of composition—instead of mapping through two layers, you only have one, for example. 任何时候只要您拥有一个看起来像A => F[B]的函数(就像在地图中查找),您就可以将其表示为ReaderT[F, A, B] ,它为您提供了更好的组合方式-而不是例如,通过两层进行映射,您只有一层。

The ReaderT approach also makes it a little nicer to change out the F (via mapK ). ReaderT方法还使更改F (通过mapK )更好mapK For example, suppose as in your example you generally want to work with readers that return their values in a FailFast context, but you need to switch to an Option context occasionally. 例如,假设像您的示例中那样,您通常希望与在FailFast上下文中返回其值的读取器一起工作,但有时需要切换到Option上下文。 That would look like this: 看起来像这样:

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"
}

The OptionalReader here isn't exactly the same as your OptionalValue , since it doesn't include the FailFast layer, but that layer is redundant in your code, since missing values are represented in the Option layer, so the OptionReader approach is likely to be a better fit. OptionalReader这里是不完全一样的OptionalValue ,因为它不包括FailFast层,但这层是在你的代码多余的,因为缺失值在代表Option层,所以OptionReader做法很可能是更合适。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM