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