Как обрабатывать ADT (запечатанный трейт) с помощью конфигурации ZIO
Как я могу вручную добавить описание конфигурации для алгебраического типа данных с помощью ZIO Conf.
В примерах я нашел пример того, как работать с ADT с помощью Magnolia.
Возможно ли это также при добавлении вручную описания конфигурации?
Вот пример:
sealed trait Dance
final case class A(any: Person) extends Dance
final case class B(body: Height) extends Dance
final case class Person(name: String, age: Option[Int])
final case class Height(height: Long)
С магнолией:
val danceConfig = description[Dance]
Вручную:
val danceConfig = ???
1 ответ
Как и следовало ожидать, он подробный. Но есть разные способы сделать это, и это вопрос предпочтений.
Мы постарались быть более подробными, чем требуется, в обоих этих вариантах для лучшего понимания.
Опция 1:
val personConfig =
(string("name") |@| int("age").optional)(Person.apply, Person.unapply)
val heightConfig =
long("height").xmap(Height)(_.height)
val aConfig = nested("any")(personConfig).xmap(A)(_.any)
val bConfig = nested("body")(heightConfig).xmap(B)(_.body)
val cConfig = boolean("can").xmap(C)(_.can)
val dConfig = string("dance").xmap(D)(_.dance)
val danceConfig =
aConfig
.orElseEither(bConfig)
.orElseEither(cConfig)
.orElseEither(dConfig)
.xmap({
case Right(value) => value: Dance
case Left(value) =>
value match {
case Right(value) => value: Dance
case Left(value) =>
value match {
case Right(value) => value: Dance
case Left(value) => value: Dance
}
}
})({
case d @ D(_) => Right(d)
case c @ C(_) => Left(Right(c))
case b @ B(_) => Left(Left(Right(b)))
case a @ A(_) => Left(Left(Left(a)))
}
)
Немного запутано во время записи, но все зависит от типа.
Вариант 2
val personConfig =
(string("name") |@| int("age").optional)(Person.apply, Person.unapply)
val heightConfig =
long("height").xmap(Height)(_.height)
val aConfig = nested("any")(personConfig).xmap(A)(_.any)
val bConfig = nested("body")(heightConfig).xmap(B)(_.body)
val cConfig = boolean("can").xmap(C)(_.can)
val dConfig = string("dance").xmap(D)(_.dance)
val aConfigAsDance =
aConfig.xmapEither(a => Right(a: Dance))({
case a: A => Right(a)
case _ => Left("unable to write back")
})
val bConfigAsDance =
bConfig.xmapEither(a => Right(a: Dance))({
case a: B => Right(a)
case _ => Left("unsable to write back")
})
val cConfigAsDance =
cConfig.xmapEither(a => Right(a: Dance))({
case a: C => Right(a)
case _ => Left("unsable to write back")
})
val dConigAsDance =
dConfig.xmapEither(a => Right(a: Dance))({
case a: D => Right(a)
case _ => Left("unsable to write back")
})
val danceConfig =
aConfigAsDance.orElse(bConfigAsDance).orElse(cConfigAsDance).orElse(dConigAsDance)
Вы уже заметили, что во время части записи (второй аргумент для xmapEither) мы проверяем правильный тип. Пример: ВaConfigAsDance
, небезопасно предполагать, что это может быть только A, и делать asInstanceOf
.
С xmapeither
мы умеем писать безопасный и чистый код и следовали ему.
В будущем zio-config будет предлагать некоторые вспомогательные функции для работы с Either. Это связано с тем, что философия ZIO-Config заключается в том, чтобы предоставить пользователю как можно менее волшебный интерфейс, в то время как вы по-прежнему можете использовать zio-config-magnolia, чтобы сократить их до одной строки, которая
val danceConfig = description[Dance]
Если вам интересно, хорошо, что этот пример вернулся в zio-config. Большое спасибо за этот вопрос и надеюсь, что ответ будет полезным.