WedX - журнал о программировании и компьютерных науках

Преобразовать класс случая с опциями в класс случая без опций

Имея класс case с вложенными опциями, как мне преобразовать его в класс-близнец без опций, извлекая Some и заменяя None значениями по умолчанию.

Допустим, у нас есть:

case class UserPreferencesOpt(animals: Option[AnimalPreferencesOpt])
case class AnimalPreferencesOpt(dogs: Option[String], cats: Option[String])

case class UserPreferences(animals: AnimalPreferences)
object UserPreferences {
    def getDefault: UserPreferences(AnimalPreferences("bark", "meow")
}
case class AnimalPreferences(dogs: String, cats: String)

Итак, в основном, я хотел бы преобразовать:

UserPreferencesOpt(Some(AnimalPreferencesOpt(Some("Dogs are cool!"), None)))

в:

UserPreferences(AnimalPreferences("Dogs are cool!", "meow")

путем извлечения параметров, если они есть, и замены отсутствующих значением по умолчанию.

Это можно сделать путем сопоставления с образцом и проверки значения за значением, но это всего лишь пример. Очевидно, что во многих случаях в логике домена будет гораздо больше вложенных предпочтений, и мне интересно, можно ли преобразовать его без добавления дополнительных совпадений и случаев, когда добавляются новые предпочтения (например, WeatherPreferences или еще что-то внутри UserPreferences).

Услышав о некоторых продвинутых вещах Scala, таких как Shapeless или макросы, я думаю, что кто-нибудь может порекомендовать это, но есть ли способ добиться этого идиоматично, функционально, в стиле Scala?

13.03.2018

  • Я думаю, вам следует переосмыслить моделирование вашей предметной области. Кажется странным иметь класс case, еще один класс case opt с полями opt и еще больше вложенных классов opt case. 13.03.2018
  • Один с параметрами, который я хотел бы использовать для обработки пользовательского ввода в конечной точке API, параметры служат пользователю, который может захотеть ИСПРАВИТЬ только некоторые настройки. Класс без опций является внутренней модальностью доменной логики, и мне нужно, чтобы все поля хранились в JSON в БД. Редактировать: Не могли бы вы поделиться некоторыми мыслями, как это можно сделать лучше? 13.03.2018

Ответы:


1

Вы можете указать значения по умолчанию в apply в сопутствующем объекте для целевых классов дел. Например.:

case class UserPreferencesOpt(animals: Option[AnimalPreferencesOpt] = None)
case class AnimalPreferencesOpt(dogs: Option[String] = None, cats: Option[String] = None)

case class UserPreferences(animals: AnimalPreferences)
object UserPreferences {
  def apply(o: UserPreferencesOpt): UserPreferences = {
    UserPreferences(
      AnimalPreferences(
        o.animals.getOrElse(AnimalPreferencesOpt())
      ))
  }
}
case class AnimalPreferences(dogs: String, cats: String)

object AnimalPreferences {
  def apply(opt: AnimalPreferencesOpt): AnimalPreferences = {
    AnimalPreferences(
      opt.dogs.getOrElse("bark"),
      opt.cats.getOrElse("meow")
    )
  }
}

Но я согласен с @pedrorijo91, что модель выглядит странно..

13.03.2018
  • Спасибо за Ваш ответ. Если вы также считаете, что эта модель выглядит странно, как мне моделировать и обрабатывать сущности API от пользователя (например, изменять 2 из 3 настроек)? Этот класс case с параметрами можно преобразовать в JSON в моей модели, и с тех пор пустое событие JSON по-прежнему действует. Однако класс без опций представляет модель предметной области (например, с логическими настройками я не различаю None и Some(false), есть только true и false, и если что-то не указано, я добавляю значение по умолчанию в базу данных 13.03.2018
  • Я бы сказал, что вам нужно различать классы моделей и классы пользовательского интерфейса. И переместите свои настройки по умолчанию как можно ближе к пользовательскому интерфейсу. Например. если вы используете play-json, вы можете предварительно определить свой ридер со значениями по умолчанию (используя Reads.pure) и преобразовать непосредственно в классы моделей, с которыми вы работаете внутри, или сначала в промежуточные классы, связанные с пользовательским интерфейсом (потому что, например, если некоторые нетривиальные дополнительные требуется проверка), которые могут быть преобразованы на месте в классы моделей. Для других json-libs думаю, что значения по умолчанию также должны быть доступны. 13.03.2018

  • 2

    Если значения по умолчанию являются статическими, то более простое решение — заполнить его при построении экземпляра, а не иметь класс с параметрами.

    Для вашего примера это будет:

    case class UserPreferences(animals: AnimalPreferences)
    case class AnimalPreferences(dogs: String, cats: String)
    
    object UserPreferences {
      def apply(dogOpt: Option[String], catOpt: Option[String]): UserPreferences = {
        val dog = dogOpt.getOrElse("bark")
        val cat = catOpt.getOrElse("meow")
        UserPreferences(AnimalPreferences(dog, cat))
      }
    }
    
    // then you can create, eg:
    UserPreferences(None, None)
    UserPreferences("Grrr", None)
    UserPreferences("Dogs are cool!", "meow")
    
    13.03.2018

    3

    Другой масштабный способ решить эту проблему — использовать implicit conversions. Вы должны определить implicit methods, которые преобразуют класс toConvert в требуемый класс, например. от UserPreferenceOpt до UserPreference в соответствующих сопутствующих классах.

      case class UserPreferencesOpt(animals: Option[AnimalPreferencesOpt])
      case class AnimalPreferencesOpt(dogs: Option[String], cats: Option[String])
    
      case class UserPreferences(animals: AnimalPreferences)
      case class AnimalPreferences(dogs: String, cats: String)
    
      //Note that, I have used `null` value in case of `None` found in Opt class. Instead of `null`, you can provide default value with some logic here.
      object UserPreferencesOpt {
        implicit def optToUserPref(userPref: UserPreferencesOpt): UserPreferences = UserPreferences(userPref.animals.getOrElse(null))
      }
      object AnimalPreferencesOpt {
        implicit def optToAnimalPref(animalPref: AnimalPreferencesOpt): AnimalPreferences = AnimalPreferences(animalPref.dogs.getOrElse(null), animalPref.cats.getOrElse(null))
      }
      val userPrefOpt:UserPreferencesOpt = UserPreferencesOpt(Some(AnimalPreferencesOpt(Some("Dogs are cool!"), None)))
      val userPref: UserPreferences = userPrefOpt
    
    13.03.2018

    4

    Существует библиотека, поддерживающая эту функцию, например:

    import cats.data.Validated
    import cats.implicits._
    import henkan.optional.all._
    
    case class Message(a: Option[String], b: Option[Int])
    case class Domain(a: String, b: Int)
    
    validate(Message(Some("a"), Some(2))).to[Domain]
    // res0: henkan.optional.ValidateFromOptional.Result[Domain] = Valid(Domain(a,2))
    
    validate(Message(Some("a"), None)).to[Domain]
    // res1: henkan.optional.ValidateFromOptional.Result[Domain] = Invalid(NonEmptyList(RequiredFieldMissing(b)))
    
    12.07.2018
    Новые материалы

    Как создать диаграмму градиентной кисти с помощью D3.js
    Резюме: Из этого туториала Вы узнаете, как добавить градиентную кисть к диаграмме с областями в D3.js. Мы добавим градиент к значениям SVG и применим градиент в качестве заливки к диаграмме с..

    Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что это выглядит сложно…
    Просто начните и учитесь самостоятельно Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что он кажется мне сложным, и я бросил его. Это в основном инструмент..

    Лицензии с открытым исходным кодом: руководство для разработчиков и создателей
    В динамичном мире разработки программного обеспечения открытый исходный код стал мощной парадигмой, способствующей сотрудничеству, инновациям и прогрессу, движимому сообществом. В основе..

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..


    Для любых предложений по сайту: [email protected]