кейс-класс с логикой, что такое идиоматический способ - PullRequest
0 голосов
/ 30 января 2019

Что такое идиоматический способ FP: скажем, у меня есть это

trait Name

object Name{
 def apply(name: String): Name = {
     if (name.trim.isEmpty || name.trim.length < 3)
       InvalidName
     else 
       ValidName(name.trim)
    }
 }

case object InvalidName extends Name
case class ValidName(name:String) extends AnyVal with Name

Теперь у меня есть некоторые вспомогательные функции, такие как

def split = name.splitAt(" ")
//some more functions 

, который более идиоматичен:

  1. Поместите их в класс case самостоятельно, но это каким-то образом заставляет класс case содержать некоторую логику, однако я могу сделать что-то вроде:

    val n = ValidName("john smith")

    val (first, last) = n.split

  2. Поместите их в выделенный объект, но тогда метод будет выглядеть следующим образом:

    def split(n: ValidName) = n.name.splitAt(" ")

  3. Создайте объект с неявным классом, который будет принимать Name и будет вызывать методы

Что вы думаете?

Ответы [ 3 ]

0 голосов
/ 30 января 2019

Я думаю, это зависит от контекста.В этом случае, если большинство используемых вами методов являются лишь незначительными изменениями String методов, вы можете рассмотреть четвертый вариант:

case class Name(name: String)
implicit def NameToString(n: Name) = n.name

Name("Iron Man").split(" ") // Array(Iron, Man)
0 голосов
/ 30 января 2019

Более идиоматический:

case class Name private (name: String) {
  lazy val first :: last :: Nil = name.split(" ").toList
}
object Name {
  def fromString (name: String): Either[String, Name] = {
    if (name.trim.isEmpty || name.trim.length < 3) Left("Invalid name")
    else Right(new Name(name.trim))
  }
}

Или, может быть, такой:

case class Name (first: String, last: String) {
  lazy val fullName = s"$first $last"
}
object Name {
  def fromString (name: String): Either[String, Name] = {
    if (name.trim.isEmpty || name.trim.length < 3) Left("Invalid name")
    else {
      val first :: last :: Nil = name.split(" ").toList
      Right(new Name(first, last))
    }
  }
}

В Scala более идиоматично представлять случаи с помощью использования Either, чем с помощью наследования.Если у вас есть экземпляр N, вы не можете вызывать какие-либо функции, вам, вероятно, придется сопоставить его с шаблоном.Но такой тип, как Either, уже поставляется с такими функциями, как map, fold и т. Д., С которыми легче работать.

Наличие частного конструктора помогает гарантировать, что вы можете создать только действительный Name потому что единственный способ создать его - это метод fromString.

НЕ использовать для этого импликации.Там нет необходимости и сделал бы только для запутанного кода.Не совсем то, для чего это значит.

0 голосов
/ 30 января 2019

Нет большой проблемы с добавлением логики к case class, особенно когда он просто извлекает данные в другом формате.(Это становится проблематичным, когда вы добавляете элементы данных в case class).

Поэтому я бы выбрал вариант 1 и не беспокоился об этом!


В ответ на комментарии *На самом деле 1008 * - это просто ярлык для создания класса с целым набором полезных предварительно реализованных методов.В частности, метод unapply позволяет использовать case class для сопоставления с образцом, а equals выполняет поэлементное сравнение полей двух экземпляров.

И есть множество другихметоды извлечения данных из case class по-разному.Наиболее очевидными являются toString и copy, но есть и другие, такие как hashCode и все, что унаследовано от Product, например productIterator.

, поскольку case class уже имеет методычтобы извлечь данные полезными способами, я не вижу возражений против добавления вашего метода split в качестве другого способа извлечения данных из case class.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...