2 votes

<trait scellé et objets de cas dynamiques>

J'ai quelques énumérations implémentées en tant que traits scellés et objets de cas. Je préfère utiliser l'approche ADT en raison des avertissements non exhaustifs et surtout parce que nous voulons éviter l'effacement de type. Quelque chose comme ceci :

 sealed abstract class Maker(val value: String) extends Product with Serializable {
    override def toString = value
  }

  object Maker {
    case object ChryslerMaker extends Vendor("Chrysler")
    case object ToyotaMaker extends Vendor("Toyota")
    case object NissanMaker extends Vendor("Nissan")
    case object GMMaker extends Vendor("General Motors")
    case object UnknownMaker extends Vendor("")

    val tipos = List(ChryslerMaker, ToyotaMaker, NissanMaker,GMMaker, UnknownMaker)
    private val fromStringMap: Map[String, Maker] = tipos.map(s => s.toString -> s).toMap

    def apply(key: String): Option[Maker] = fromStringMap.get(key)
  }

Cela fonctionne bien jusqu'à présent, maintenant nous envisageons de fournir aux autres programmeurs un accès à notre code pour leur permettre de le configurer sur site. Je vois deux problèmes potentiels : 1) Les gens qui font des erreurs et écrivent des choses comme :

case object ChryslerMaker extends Vendor("Nissan")

et les gens qui oublient de mettre à jour les tipos

J'ai envisagé d'utiliser un fichier de configuration (JSON ou csv) pour fournir ces valeurs et les lire comme nous le faisons avec beaucoup d'autres éléments, mais toutes les réponses que j'ai trouvées reposent sur des macros et semblent extrêmement dépendantes de la version de scala utilisée (2.12 pour nous).

Ce que j'aimerais trouver c'est : 1a) (Préféré) un moyen de créer dynamiquement les objets de cas à partir d'une liste de chaînes en veillant à ce que les objets soient nommés de manière cohérente avec la valeur qu'ils contiennent 1b) (Acceptable) si cela s'avère trop difficile un moyen d'obtenir les objets et les valeurs pendant la phase de test 2) Vérifiez que le nombre d'éléments dans la liste correspond au nombre d'objets de cas créés.

J'ai oublié de mentionner, j'ai brièvement regardé enumeratum mais je préférerais ne pas inclure de bibliothèques supplémentaires à moins de vraiment comprendre les avantages et les inconvénients (et en ce moment je ne suis pas sûr de la façon dont enumeratum se compare à l'approche ADT, si vous pensez que c'est la meilleure façon et pouvez me guider vers une telle discussion, ce serait formidable)

Merci !

2voto

Une idée qui me vient à l'esprit est de créer une tâche SBT SourceGenerator tâche.
Qui lira un fichier d'entrée JSON, CSV, XML ou autre, qui fait partie de votre projet et créera un fichier scala.

// ----- Fichier : project/VendorsGenerator.scala -----
import sbt.Keys._
import sbt._

/**
 * Une tâche SBT qui génère un fichier source géré avec toutes les inspections Scalastyle.
 */
objet VendorsGenerator {
  // Pour la démonstration, j'utiliserai cette simple List[String] pour générer le code,
  // vous pouvez changer le code pour lire un fichier à la place.
  // Ou peut-être que cela suffira.
  final val vendors: List[String] =
    List(
      "Chrysler",
      "Toyota",
      ...
      "Inconnu"
    )

  val generatorTask = Def.task {
    // Faites la liste 'tipos', qui contient tous les vendeurs.
    val tipos =
      vendors
        .map(nomVendeur => s"${nomVendeur}Vendor")
        .mkString("val tipos: List[Vendeur] = List(", ",", ")")

    // Faites un objet de cas pour chaque vendeur.
    val vendorObjects = vendors.map { nomVendeur =>
      s"""objet cas ${nomVendeur}Vendor étend Vendeur { override val value final : String = "${nomVendeur}" }"""
    }

    // Remplissez le modèle de code.
    val code =
      List(
        List(
          "forfait vendeurs",
          "trait scellé Vendeur étend Produit avec Serializable {",
          "déf value: String",
          "override final def toString: String = value",
          "}",
          "objet Vendeurs étend (String => Option[Vendeur]) {"
        ),
        vendorObjects,
        List(
          tipos,
          "valeur finale privée de la carte fromStringMap: Map[String, Vendeur] = tipos.map(v => v.toString -> v).toMap",
          "remplacer la méthode appliquer(clé: String): Option[Vendeur] = fromStringMap.get(key.toLowerCase)",
          "}"
        )
      ).flatten

    // Enregistrez le nouveau fichier dans le répertoire des sources gérées.
    val vendorsFile = (sourceManaged dans Compile).valeur / "vendeurs.scala"
    IO.writeLines(vendorsFile, code)
    Seq(vendorsFile)
  }
}

Maintenant, vous pouvez activer votre générateur de sources.
Cette tâche sera exécutée à chaque fois, avant l'étape de compilation.

// ----- Fichier : build.sbt -----
sourceGenerators dans Compile += VendorsGenerator.generatorTask.taskValue

Veuillez noter que je suggère ceci, car je l'ai déjà fait et parce que je n'ai aucune expérience en macros ou en méta-programmation.
Aussi, notez que cet exemple repose beaucoup sur les Strings, ce qui rend le code un peu difficile à comprendre et à maintenir.

Au fait, je n'ai pas utilisé enumeratum, mais en y jetant un coup d'œil rapide, cela semble être la meilleure solution à ce problème

Éditer

J'ai mon code prêt à lire un fichier HOCON et générer le code correspondant. Ma question maintenant est où placer le fichier scala dans le répertoire du projet et où les fichiers seront-ils générés. Je suis un peu confus car il semble qu'il y ait plusieurs étapes 1) compiler mon générateur scala, 2) exécuter le générateur et 3) compiler et construire le projet. Est-ce correct ?

Votre générateur ne fait pas partie du code de votre projet, mais plutôt de votre méta-projet (Je sais que cela semble confus, vous pouvez lire ceci pour comprendre) - en tant que tel, vous placez le générateur à l'intérieur du dossier project au niveau de la racine (le même dossier où se trouve le fichier build.properties pour spécifier la version sbt).
Si votre générateur a besoin de dépendances (je suis sûr que c'est le cas pour lire le HOCON) vous les placez dans un fichier build.sbt à l'intérieur de ce dossier project.
Si vous prévoyez d'ajouter des tests unitaires au générateur, vous pouvez créer un projet scala entier à l'intérieur du méta-projet (vous pouvez jeter un œil au dossier project d'un projet open source (Oui, oui, je sais, encore une fois la confusion) sur lequel je travaille pour référence) - Ma suggestion personnelle est qu'au lieu de tester le générateur lui-même, vous devriez tester le fichier généré à la place, ou les deux, encore mieux.

Le fichier généré sera automatiquement placé dans le dossier src_managed (qui se trouve à l'intérieur de target et est donc ignoré par votre gestion de version du code source).
Le chemin à l'intérieur de celui-ci est juste par ordre, car tout à l'intérieur du dossier src_managed est inclus par défaut lors de la compilation.

val vendorsFile = (sourceManaged dans Compile).valeur / "vendeurs.scala" // Chemin vers le fichier à écrire.`

Pour accéder aux valeurs définies dans le fichier généré dans votre code source, il vous suffit d'ajouter un package au fichier généré et d'importer les valeurs de ce package dans votre code (comme avec n'importe quel fichier normal).

Vous n'avez pas besoin de vous soucier de quoi que ce soit en rapport avec l'ordre de compilation, si vous incluez votre générateur de source dans votre fichier build.sbt, SBT s'occupera automatiquement de tout.

sourceGenerators in Compile += VendorsGenerator.generatorTask.taskValue // Activer le générateur de sources.

SBT relancera votre générateur à chaque fois qu'il aura besoin de compiler.

"Au fait, je reçois "non trouvé : objet sbt" sur les imports".

Si le projet est à l'intérieur de l'espace méta-projet, il trouvera le package sbt par défaut, ne vous inquiétez pas à ce sujet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X