Подтвердить что ты не робот

22 поля ограничены в Scala 2.11 + Play Framework 2.3 Типы и функции классов

Scala 2.11 отсутствует, и предел 22 полей для классов классов кажется фиксированным (Scala Проблема, Примечания к выпуску).

Это было проблемой для меня какое-то время, потому что я использую классы case для моделирования объектов базы данных, которые имеют более 22 полей в Play + Postgres Async. Мое решение в Scala 2.10 состояло в том, чтобы разбить модели на несколько классов классов, но я считаю, что это решение трудно поддерживать и расширять, и я надеялся, что смогу реализовать что-то, как описано ниже, после переключения на Play 2.3.0-RC1 + Scala 2.11.0:

package entities

case class MyDbEntity(
  id: String,
  field1: String,
  field2: Boolean,
  field3: String,
  field4: String,
  field5: String,
  field6: String,
  field7: String,
  field8: String,
  field9: String,
  field10: String,
  field11: String,
  field12: String,
  field13: String,
  field14: String,
  field15: String,
  field16: String,
  field17: String,
  field18: String,
  field19: String,
  field20: String,
  field21: String,
  field22: String,
  field23: String,
) 

object MyDbEntity {
  import play.api.libs.json.Json
  import play.api.data._
  import play.api.data.Forms._

  implicit val entityReads = Json.reads[MyDbEntity]
  implicit val entityWrites = Json.writes[MyDbEntity]
}

Приведенный выше код не может компилироваться со следующим сообщением как для "Чтений", так и для "Writes":

No unapply function found

Обновление "Считывает" и "Записывает" на:

  implicit val entityReads: Reads[MyDbEntity] = (
    (__ \ "id").read[Long] and
    (__ \ "field_1").read[String]
    ........
  )(MyDbEntity.apply _)  

  implicit val postWrites: Writes[MyDbEntity] = (
    (__ \ "id").write[Long] and
    (__ \ "user").write[String]
    ........
  )(unlift(MyDbEntity.unapply))

Также не работает:

  implementation restricts functions to 22 parameters

  value unapply is not a member of object models.MyDbEntity

Я понимаю, что Scala 2.11 все еще имеет некоторые ограничения на функции, и что-то вроде того, что я описал выше, пока невозможно. Мне это кажется странным, так как я не вижу преимущества отмены ограничений на классы case, если один из его основных случаев для пользователей по-прежнему не поддерживается, поэтому мне интересно, не хватает ли я чего-то.

Указатели на проблемы или детали реализации более чем приветствуются! Спасибо!

4b9b3361

Ответ 1

Это невозможно, из коробки, по нескольким причинам:

Однако можно обойти вторую точку либо:

Сначала создадим отсутствующий FunctionalBuilder:

class CustomFunctionalBuilder[M[_]](canBuild: FunctionalCanBuild[M]) extends FunctionalBuilder {

    class CustomCanBuild22[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22](m1: M[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21], m2: M[A22]) {
def ~[A23](m3: M[A23]) = new CustomCanBuild23[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23](canBuild(m1, m2), m3)

  def and[A23](m3: M[A23]) = this.~(m3)

  def apply[B](f: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => B)(implicit fu: Functor[M]): M[B] =
  fu.fmap[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22, B](canBuild(m1, m2), { case a1 ~ a2 ~ a3 ~ a4 ~ a5 ~ a6 ~ a7 ~ a8 ~ a9 ~ a10 ~ a11 ~ a12 ~ a13 ~ a14 ~ a15 ~ a16 ~ a17 ~ a18 ~ a19 ~ a20 ~ a21 ~ a22 => f(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) })

  def apply[B](f: B => (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22))(implicit fu: ContravariantFunctor[M]): M[B] =
  fu.contramap(canBuild(m1, m2), (b: B) => { val (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) = f(b); new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(a1, a2), a3), a4), a5), a6), a7), a8), a9), a10), a11), a12), a13), a14), a15), a16), a17), a18), a19), a20), a21), a22) })

  def apply[B](f1: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => B, f2: B => (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22))(implicit fu: InvariantFunctor[M]): M[B] =
  fu.inmap[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22, B](
    canBuild(m1, m2), { case a1 ~ a2 ~ a3 ~ a4 ~ a5 ~ a6 ~ a7 ~ a8 ~ a9 ~ a10 ~ a11 ~ a12 ~ a13 ~ a14 ~ a15 ~ a16 ~ a17 ~ a18 ~ a19 ~ a20 ~ a21 ~ a22 => f1(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) },
    (b: B) => { val (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) = f2(b); new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(new ~(a1, a2), a3), a4), a5), a6), a7), a8), a9), a10), a11), a12), a13), a14), a15), a16), a17), a18), a19), a20), a21), a22) }
  )

  def join[A >: A1](implicit witness1: <:<[A, A1], witness2: <:<[A, A2], witness3: <:<[A, A3], witness4: <:<[A, A4], witness5: <:<[A, A5], witness6: <:<[A, A6], witness7: <:<[A, A7], witness8: <:<[A, A8], witness9: <:<[A, A9], witness10: <:<[A, A10], witness11: <:<[A, A11], witness12: <:<[A, A12], witness13: <:<[A, A13], witness14: <:<[A, A14], witness15: <:<[A, A15], witness16: <:<[A, A16], witness17: <:<[A, A17], witness18: <:<[A, A18], witness19: <:<[A, A19], witness20: <:<[A, A20], witness21: <:<[A, A21], witness22: <:<[A, A22], fu: ContravariantFunctor[M]): M[A] =
  apply[A]((a: A) => (a: A1, a: A2, a: A3, a: A4, a: A5, a: A6, a: A7, a: A8, a: A9, a: A10, a: A11, a: A12, a: A13, a: A14, a: A15, a: A16, a: A17, a: A18, a: A19, a: A20, a: A21, a: A22))(fu)

  def reduce[A >: A1, B](implicit witness1: <:<[A1, A], witness2: <:<[A2, A], witness3: <:<[A3, A], witness4: <:<[A4, A], witness5: <:<[A5, A], witness6: <:<[A6, A], witness7: <:<[A7, A], witness8: <:<[A8, A], witness9: <:<[A9, A], witness10: <:<[A10, A], witness11: <:<[A11, A], witness12: <:<[A12, A], witness13: <:<[A13, A], witness14: <:<[A14, A], witness15: <:<[A15, A], witness16: <:<[A16, A], witness17: <:<[A17, A], witness18: <:<[A18, A], witness19: <:<[A19, A], witness20: <:<[A20, A], witness21: <:<[A21, A], witness22: <:<[A22, A], fu: Functor[M], reducer: Reducer[A, B]): M[B] =
  apply[B]((a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) =>  reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.append(reducer.unit(a1: A), a2: A), a3: A), a4: A), a5: A), a6: A), a7: A), a8: A), a9: A), a10: A), a11: A), a12: A), a13: A), a14: A), a15: A), a16: A), a17: A), a18: A), a19: A), a20: A), a21: A), a22: A))(fu)

  def tupled(implicit v: VariantExtractor[M]): M[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)] =
  v match {
    case FunctorExtractor(fu) => apply { (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) }(fu)
    case ContravariantFunctorExtractor(fu) => apply[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)] { (a: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)) => (a._1, a._2, a._3, a._4, a._5, a._6, a._7, a._8, a._9, a._10, a._11, a._12, a._13, a._14, a._15, a._16, a._17, a._18, a._19, a._20, a._21, a._22) }(fu)
    case InvariantFunctorExtractor(fu) => apply[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)]({ (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22) => (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) }, { (a: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)) => (a._1, a._2, a._3, a._4, a._5, a._6, a._7, a._8, a._9, a._10, a._11, a._12, a._13, a._14, a._15, a._16, a._17, a._18, a._19, a._20, a._21, a._22) })(fu)
    }

  }

  class CustomCanBuild23[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23](m1: M[A1 ~ A2 ~ A3 ~ A4 ~ A5 ~ A6 ~ A7 ~ A8 ~ A9 ~ A10 ~ A11 ~ A12 ~ A13 ~ A14 ~ A15 ~ A16 ~ A17 ~ A18 ~ A19 ~ A20 ~ A21 ~ A22], m2: M[A23]) {
  }

}

а затем, предоставив свой собственный экземпляр FunctionalBuilderOps:

implicit def customToFunctionalBuilderOps[M[_], A](a: M[A])(implicit fcb: FunctionalCanBuild[M]) = new CustomFunctionalBuilderOps[M, A](a)(fcb)

Наконец, в отношении первой точки я отправил запрос на перенос, чтобы попытаться упростить текущую реализацию.

Ответ 2

Мы также разбивали наши модели на несколько классов, но это быстро становилось неуправляемым. Мы используем Slick как наш объектный реляционный сопоставитель, а Slick 2.0 поставляется с генератор кода, который мы используем для создания классов (которые применяются с методами применения и копированием конструкторов для имитации классов классов) вместе с методами для создания экземпляров моделей из Json (мы не генерируем автоматически методы для преобразования моделей в Json, потому что у нас есть слишком много особых случаев для решения). Использование генератора кода Slick не требует использования Slick в качестве реляционного картографического объекта.

Это часть ввода генератора кода - этот метод принимает JsObject и использует его для создания или создания новой модели или обновления существующей модели.

private def getItem(original: Option[${name}], json: JsObject, trackingData: TrackingData)(implicit session: scala.slick.session.Session): Try[${name}] = {
  preProcess("$name", columnSet, json, trackingData).flatMap(updatedJson => {
    ${indent(indent(indent(entityColumnsSansId.map(c => s"""val ${c.name}_Parsed = parseJsonField[${c.exposedType}](original.map(_.${c.name}), "${c.name}", updatedJson, "${c.exposedType}")""").mkString("\n"))))}
    val errs = Seq(${indent(indent(indent(indent(entityColumnsSansId.map(c => s"${c.name}_Parsed.map(_ => ())").mkString(", ")))))}).condenseUnit
    for {
      _ <- errs
      ${indent(indent(indent(indent(entityColumnsSansId.map(c => s"${c.name}_Val <- ${c.name}_Parsed").mkString("\n")))))}
    } yield {
      original.map(_.copy(${entityColumnsSansId.map(c => s"${c.name} = ${c.name}_Val").mkString(", ")}))
        .getOrElse(${name}.apply(id = None, ${entityColumnsSansId.map(c => s"${c.name} = ${c.name}_Val").mkString(", ")}))
    }
  })
}

Например, с нашей моделью ActivityLog выдается следующий код. Если "original" - "None", то это вызывается из метода "createFromJson", и мы создаем новую модель; если "original" is Some (activityLog), то это вызывается из метода "updateFromJson", и мы обновляем существующую модель. Метод "condenseUnit", вызываемый по строке "val errs =...", принимает Seq [Try [Unit]] и создает Try [Unit]; если в Seq есть ошибки, Try [Unit] объединяет сообщения об исключениях. Методы parseJsonField и parseField не генерируются - на них ссылаются только сгенерированный код.

private def parseField[T](name: String, json: JsObject, tpe: String)(implicit r: Reads[T]): Try[T] = {
  Try((json \ name).as[T]).recoverWith {
    case e: Exception => Failure(new IllegalArgumentException("Failed to parse " + Json.stringify(json \ name) + " as " + name + " : " + tpe))
  }
}

def parseJsonField[T](default: Option[T], name: String, json: JsObject, tpe: String)(implicit r: Reads[T]): Try[T] = {
  default match {
    case Some(t) => if(json.keys.contains(name)) parseField(name, json, tpe)(r) else Try(t)
    case _ => parseField(name, json, tpe)(r)
  }
}

private def getItem(original: Option[ActivityLog], json: JsObject, trackingData: TrackingData)(implicit session: scala.slick.session.Session): Try[ActivityLog] = {
  preProcess("ActivityLog", columnSet, json, trackingData).flatMap(updatedJson => {
    val user_id_Parsed = parseJsonField[Option[Int]](original.map(_.user_id), "user_id", updatedJson, "Option[Int]")
    val user_name_Parsed = parseJsonField[Option[String]](original.map(_.user_name), "user_name", updatedJson, "Option[String]")
    val item_id_Parsed = parseJsonField[Option[String]](original.map(_.item_id), "item_id", updatedJson, "Option[String]")
    val item_item_type_Parsed = parseJsonField[Option[String]](original.map(_.item_item_type), "item_item_type", updatedJson, "Option[String]")
    val item_name_Parsed = parseJsonField[Option[String]](original.map(_.item_name), "item_name", updatedJson, "Option[String]")
    val modified_Parsed = parseJsonField[Option[String]](original.map(_.modified), "modified", updatedJson, "Option[String]")
    val action_name_Parsed = parseJsonField[Option[String]](original.map(_.action_name), "action_name", updatedJson, "Option[String]")
    val remote_ip_Parsed = parseJsonField[Option[String]](original.map(_.remote_ip), "remote_ip", updatedJson, "Option[String]")
    val item_key_Parsed = parseJsonField[Option[String]](original.map(_.item_key), "item_key", updatedJson, "Option[String]")
    val created_at_Parsed = parseJsonField[Option[java.sql.Timestamp]](original.map(_.created_at), "created_at", updatedJson, "Option[java.sql.Timestamp]")
    val as_of_date_Parsed = parseJsonField[Option[java.sql.Timestamp]](original.map(_.as_of_date), "as_of_date", updatedJson, "Option[java.sql.Timestamp]")
    val errs = Seq(user_id_Parsed.map(_ => ()), user_name_Parsed.map(_ => ()), item_id_Parsed.map(_ => ()), item_item_type_Parsed.map(_ => ()), item_name_Parsed.map(_ => ()), modified_Parsed.map(_ => ()), action_name_Parsed.map(_ => ()), remote_ip_Parsed.map(_ => ()), item_key_Parsed.map(_ => ()), created_at_Parsed.map(_ => ()), as_of_date_Parsed.map(_ => ())).condenseUnit
    for {
      _ <- errs
      user_id_Val <- user_id_Parsed
      user_name_Val <- user_name_Parsed
      item_id_Val <- item_id_Parsed
      item_item_type_Val <- item_item_type_Parsed
      item_name_Val <- item_name_Parsed
      modified_Val <- modified_Parsed
      action_name_Val <- action_name_Parsed
      remote_ip_Val <- remote_ip_Parsed
      item_key_Val <- item_key_Parsed
      created_at_Val <- created_at_Parsed
      as_of_date_Val <- as_of_date_Parsed
    } yield {
      original.map(_.copy(user_id = user_id_Val, user_name = user_name_Val, item_id = item_id_Val, item_item_type = item_item_type_Val, item_name = item_name_Val, modified = modified_Val, action_name = action_name_Val, remote_ip = remote_ip_Val, item_key = item_key_Val, created_at = created_at_Val, as_of_date = as_of_date_Val))
        .getOrElse(ActivityLog.apply(id = None, user_id = user_id_Val, user_name = user_name_Val, item_id = item_id_Val, item_item_type = item_item_type_Val, item_name = item_name_Val, modified = modified_Val, action_name = action_name_Val, remote_ip = remote_ip_Val, item_key = item_key_Val, created_at = created_at_Val, as_of_date = as_of_date_Val))
    }
  })
}

Ответ 3

Вы можете использовать модуль Jackson Scala. Функция воспроизведения json построена на Jackson Scala. Я не знаю, почему они установили предел поля 22 здесь, а Jackson поддерживает более 22 полей. Возможно, имеет смысл, что вызов функции никогда не может использовать более 22 параметров, но мы можем иметь сотни столбцов внутри сущности БД, поэтому это ограничение здесь смешно и делает игру менее производительной игрушкой. проверьте это:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

object JacksonUtil extends App {
  val mapper = new ObjectMapper with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)


  val t23 = T23("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w")

  println(mapper.writeValueAsString(t23))
 }
case class T23(f1:String,f2:String,f3:String,f4:String,f5:String,f6:String,f7:String,
    f8:String,f9:String,f10:String,f11:String,f12:String,f13:String,f14:String,f15:String,
    f16:String,f17:String,f18:String,f19:String,f20:String,f21:String,f22:String,f23:String)

Ответ 4

случаи, когда классы классов могут не работать; один из этих случаев состоит в том, что классы case не могут принимать более 22 полей. Другой случай может заключаться в том, что вы заранее не знаете о схеме. В этом подходе данные загружаются как RDD объектов строки. Схема создается отдельно с использованием объектов StructType и StructField, которые представляют собой таблицу и поле соответственно. Схема применяется к строке RDD для создания DataFrame в Spark.

Ответ 6

Я попробовал решение Shapeless "Автоматическое определение теневого кода", предложенное в другом ответе, и оно не сработало для наших моделей - выбрасывало исключения StackOverflow (класс case с ~ 30 полями и 4 вложенными наборами классов case с 4-10 поля).

Итак, мы приняли это решение, и он работал безупречно. Подтвердил это, написав тест ScalaCheck. Обратите внимание, что для этого требуется Play Json 2.4.