1

... ou mésaventures d'un programmeur Haskell qui doit coder Scala, partie 5.Définir une instance Semigroup qui dépend lui-même

J'ai la structure suivante à Scala:

case class ResourceTree(
    resources: Map[String, ResourceTree] 
) 

Et, en utilisant Cats, je voudrais définir une instance Semigroup de celui-ci.

object ResourceTreeInstances { 
    implicit val semigroupInstance = new Semigroup[ResourceTree] { 
    override def combine(x: ResourceTree, y: ResourceTree): ResourceTree = { 
     ResourceTree(
     x.resources |+| y.resources 
    ) 
    } 
    } 

Cela se traduira par l'erreur suivante:

value |+| is not a member of Map[String, ResourceTree] 
[error] Note: implicit value semigroupInstance is not applicable here because it comes after the application point and it lacks an explicit result type 
[error]   x.resources |+| y.resource 

Donc, je pense était que, puisque je définir l'instance pour Semigroup le compilateur Scala ne peut pas tirer une instance pour Semigroup de Map[String, ResourceTree]. Cela semble confirmer, puisque l'instance suivante est compile:

implicit val semigroupInstance = new Semigroup[ResourceTree] { 
    override def combine(x: ResourceTree, y: ResourceTree): ResourceTree = { 
    dummyCombine(x, y) 
    } 
} 

// FIXME: see if there's a better way to avoid the "no instance of Semigroup" problem 
def dummyCombine(x: ResourceTree, y: ResourceTree): ResourceTree = { 
    ResourceTree(
    x.resources |+| y.resources 
) 
} 

Je suis vraiment en espérant que je me trompe parce que si cela est la bonne façon de définir une instance pour un Semigroup à Scala Je vais commencer à envisager l'idée d'abandonner la PF dans cette langue.

Y a-t-il un meilleur moyen?

Répondre

4

Ce qui suit devrait fonctionner parfaitement:

import cats.Semigroup 
import cats.instances.map._ 
import cats.syntax.semigroup._ 

case class ResourceTree(resources: Map[String, ResourceTree]) 

implicit val resourceTreeSemigroup: Semigroup[ResourceTree] = 
    new Semigroup[ResourceTree] { 
    def combine(x: ResourceTree, y: ResourceTree): ResourceTree = 
     ResourceTree(
     x.resources |+| y.resources 
    ) 
    } 

La clé est cette partie du message d'erreur: « et il manque un type de résultat explicite ». Les méthodes récursives de Scala doivent avoir des types de retour explicites, et les instances de classe de type dépendantes d'elles-mêmes (directement ou indirectement par quelque chose comme l'instance Map et la syntaxe |+| dans ce cas) en ont également besoin.

En général c'est une bonne idée de mettre des types de retour explicites sur tous les définitions implicites - ne pas le faire peut conduire à un comportement inattendu, dont certains ont un sens si vous y réfléchissez et lisez la spécification (comme dans ce cas)), et certains d'entre eux semblent être buggys dans le compilateur.

+1

J'ai été confondu par le "manque un type de résultat explicite". Je pensais que cela se rapportait à l'opération de la moissonneuse-batteuse, et non à la classe elle-même. Merci! Je ne peux pas m'empêcher de penser que j'écris l'assemblage de FP lors du codage dans Scala ... –