Композиция из вложенных монад в Скале - PullRequest
1 голос
/ 15 марта 2019

Вот пример кода:

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 параметра, это будетздорово рассмотреть все из них.

ОБНОВЛЕНИЕ благодаря @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))

Разница между этой реализацией и реализацией Трэвиса Брауна: мне нужно увидеть разницу между отсутствием ключа на карте (что является ошибкой, и мне нужна явная ошибкаего описание) и случай, когда ключ существует, но его значение равно нулю или пустой строке.Так что он не работает так же, как Maps.get, который возвращает Option.Поэтому я не могу избавиться от FailFast

Надеюсь, кому-то это будет полезно.

1 Ответ

2 голосов
/ 15 марта 2019

Самым простым подходом было бы отобразить результат, способствуя неудачам в успешном 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, вероятно, будет более подходящим.

...