2
votes

classe de cas avec logique quelle est la manière idiomatique

Quelle est la manière idiomatique FP pour cela: disons que j'ai ceci

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

Maintenant, j'ai quelques fonctions d'aide telles que

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

quel chemin est le plus idiomatique:

  1. Mettez-les dans la classe de cas elle-même, mais cela fait que la classe de cas contient une logique, mais je peux faire quelque chose comme:

    val n = ValidName ("john smith")

    val (premier, dernier) = n.split

  2. Mettez-les dans un objet dédié mais alors la méthode ressemblera à

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

  3. Créez un objet avec une classe implicite qui acceptera Name et appellera les méthodes

Que pensez-vous?


2 commentaires

Notez que l'utilisation de Name (...) dans la méthode apply de l'objet appellera Name.apply (...) (s'appelle lui-même) et déborder de la pile (je pense que je ne l'ai pas testé) ... vous devriez probablement le remplacer par new Name (...) pour vous assurer qu'il appelle directement le constructeur de classe


@AlvaroCarrasco bon point, j'ai trop simplifié l'exemple. J'ai édité la question pour éviter la situation comme vous l'avez dit


3 Réponses :


2
votes

Il n'y a pas de problème majeur avec l'ajout de logique à une classe de cas , en particulier lorsqu'il s'agit simplement d'extraire les données dans un format différent. (Cela devient problématique lorsque vous ajoutez des membres de données à une classe de cas ).

Je ferais donc l'option 1 sans m'en soucier!


En réponse aux commentaires, classe de cas n'est en fait qu'un raccourci pour créer une classe avec tout un tas de méthodes pré-implémentées utiles. En particulier, la méthode unapply permet d'utiliser une classe de cas dans la mise en correspondance de modèles, et est égal à effectue une comparaison élément par élément des champs de deux instances.

Et il existe un tas d'autres méthodes pour extraire les données de la classe de cas de différentes manières. Les plus évidents sont toString et copy , mais il y en a d'autres comme hashCode et tout ce qui est hérité de Product , comme productIterator .

Puisqu'une classe de cas a déjà des méthodes pour extraire les données de manière utile, je ne vois aucune objection à ajouter votre méthode split comme une autre façon d'extraire des données de < code> classe de cas .


4 commentaires

Les classes de cas ne sont pas supposées contenir la fonction pour les données, elles sont utilisées pour définir la structure algébrique et principalement utilisées pour la correspondance de modèles.


en fait, tout ce tas de méthodes pré-implémentées est hérité par produit de trait, il ne les a pas. Et c’est ce que j’ai dit que la fonction sur les données devrait rester à l’extérieur dans une autre entité comme un trait ou un objet, etc.


Merci Tim, je suis en quelque sorte entre votre suggestion et mon opinion qui correspond à ce que @RamanMishra a dit concernant la classe de cas en tant que type de données algébrique. Je veux généralement laisser la logique biz en dehors de la classe de cas. Je peux bien sûr créer un objet qui contiendra cette logique comme option 3 mais il semble être une surcharge redondante wdyt?


@igx Je suis d'accord que la logique métier ne doit pas être dans une classe de cas , mais je pense que les opérations simples sont OK tant qu'elles se rapportent directement aux valeurs de la classe elle-même. Il est normal pour un ADT d'avoir d'autres méthodes qui les décrochent, ainsi que la correspondance de modèle.



0
votes

Je pense que cela dépend du contexte. Dans ce cas, si la plupart des méthodes que vous utilisez ne sont que de légères modifications apportées aux méthodes String , vous pouvez envisager une quatrième option:

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

Name("Iron Man").split(" ") // Array(Iron, Man)


1 commentaires

Je vois l'avantage de votre suggestion, mais en ce qui concerne la décision de conception: vous ne voulez pas remplir votre code d'implicites. vous souhaitez utiliser implicits (IMHO) où vous souhaitez étendre une bibliothèque externe pour s'adapter à votre dsl ou pratique. ou pour la conversion nécessaire. dans votre exemple, vous convertissez simplement Name en String , ce qui est même dangereux, par exemple, vous pouvez effectuer une opération telle que `` `val name = Name (" John Doe ") name + "quelque chose" `` `c'est un comportement inattendu



2
votes

Plus idiomatique:

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))
    }
  }
}

Ou peut-être ceci:

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))
  }
}

Dans scala, il est plus idiomatique de représenter les cas d'échec en utilisant Soit que par héritage. Si vous avez une instance de N , vous ne pouvez appeler aucune fonction dessus, vous devrez probablement la faire correspondre. Mais un type comme Sither est déjà livré avec des fonctions telles que map , fold , etc. qui facilite le travail.

Avoir un constructeur privé permet de garantir que vous ne pouvez créer qu'un Name valide car le seul moyen d'en créer un est d'utiliser la méthode fromString .

N'utilisez PAS d'implicits pour cela. Il n'y a pas besoin et ne ferait que créer du code déroutant. Pas vraiment à quoi servent les implicits.


1 commentaires

Je vois mais le pattern matching est une solution spécifique. le truc ici est que nous appliquons une certaine logique biz sur l'un des membres de la classe