Scala 中的嵌套 Monad 组合
Nested Monads Composition in Scala
这是一个代码示例:
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)
如果有超过 1 个选项,最好考虑所有选项。
UPDATE 感谢@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 不同,后者是 returns 选项。所以我无法摆脱 FailFast
希望对某个人有用,
最简单的方法是映射到结果中,将失败提升为成功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
)。例如,假设在您的示例中,您通常希望与 return 他们在 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
方法可能更合适。
这是一个代码示例:
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)
如果有超过 1 个选项,最好考虑所有选项。
UPDATE 感谢@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 不同,后者是 returns 选项。所以我无法摆脱 FailFast
希望对某个人有用,
最简单的方法是映射到结果中,将失败提升为成功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
)。例如,假设在您的示例中,您通常希望与 return 他们在 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
方法可能更合适。