0
votes

Surcharge de méthode de paramètre de type uniquement dans scala

Puis-je obtenir en quelque sorte un comportement comme celui-ci dans scala (funcs a le même nom et les mêmes paramètres mais ne diffère que par le type de retour et le paramètre de type)?

def a(i:Int): Int //1
def a[T](i: Int): Double //2

a(1) //call 1
a[Any](1) //call 2

Motivation UPD: strong>

J'écris la directive akka-http auth , qui vérifie la session et l'autorisation de la demande de l'utilisateur en fonction des paramètres d'autorisation.

  1. Lorsqu'il n'obtient qu'une autorisation, il fournit uniquement un utilisateur authentifié:

def auth (perm: String): Directive1 [Utilisateur]

  1. Lorsqu'il obtient un Tuple d'autorisation avec UUID , il obtient (en quelque sorte) l'objet requis par cet UUID , vérifie l'autorisation appliquée à requis objet et fournit cet objet ainsi que User :

def auth [Entity] (tup: (String, UUID)): Directive1 [(User, Entity)]

  1. Et quand il obtient un même Tuple mais sans paramètre de type, il doit vérifier l'autorisation appliquée à l'objet requis mais fournir uniquement un utilisateur authentifié :

def auth (tup: (String, UUID)): Directive1 [User]

L'idée est d'avoir le même nom de directive pour tous les cas mais il semble qu'il n'est pas possible de faire la distinction entre a [T] (i: Int): Int et a (i: Int): Double au niveau de la langue (en raison du type effacement).

Peut-être y a-t-il quelques astuces utilisant des implicits qui aident à y parvenir?


8 commentaires

Quel est le cas d'utilisation? Quel est l'intérêt de T s'il ne représente rien.


@ LuisMiguelMejíaSuárez veuillez vérifier la question, j'ai mis à jour la motivation


La motivation semble être "d'avoir le même nom directif pour tous les cas", mais pourquoi est-il important qu'ils portent le même nom lorsqu'ils font des choses différentes et sont appelés de différentes manières?


@Tim ils font tous l'authentification et l'autorisation, la seule différence est que l'un d'eux fournit Entity


Celui qui renvoie une entité fait quelque chose de différent de celui qui ne fait que l'authentification, ce qui est une très bonne raison pour lui donner un nom différent. Pourquoi est-il nécessaire qu'ils portent le même nom? Cela commence à ressembler à un problème XY


@Tim Coz, toutes les tâches de sécurité peuvent être effectuées en un seul appel et ce n'est pas important pour l'appelant qu'il charge ou non de la base de données. Le sujet de la question est "comment surcharger les méthodes juste par type dans scala?", Pas sur la qualité de l'architecture


Comment la méthode sait-elle renvoyer une valeur de Entity ? Je m'attendrais à ce que cette méthode reçoive un paramètre implicite lié à Entity et cela supprimerait l'ambiguïté. Il semble que cette méthode fasse de la magie noire à l'intérieur.


La question est de savoir pourquoi vous ne pouvez pas donner le même nom à trois méthodes différentes, donc je pense qu'il est raisonnable de demander pourquoi vous ne pouvez pas leur donner des noms différents.


4 Réponses :


0
votes

En raison de l'effacement de type, ces 2 méthodes auraient la même signature.

@ class Test {
  def a(i:Int): Int = 1
  def a()(i: Int): Double = 2.0
  }
defined class Test

@ new Test().a(1)
res26: Int = 1

@ new Test().a()(1)
res27: Double = 2.0

Si vous changiez quoi que ce soit, par exemple comment vous appliquez les arguments, cela devrait être faisable:

@ class Test {
  def a(i:Int): Int = 1
  def a[T](i: Int): Double = 2.0
  }
defined class Test

@ new Test()
res24: Test = ammonite.$sess.cmd23$Test@7441edeb

@ new Test().a(1)
cmd25.sc:1: ambiguous reference to overloaded definition,
both method a in class Test of type [T](i: Int)Double
and  method a in class Test of type (i: Int)Int
match argument types (Int)
val res25 = new Test().a(1)
                       ^
Compilation Failed

Alors ... en fonction de combien et de ce que vous pouvez changer, oui ou non. En général, c'est une mauvaise idée. Vous avez rarement une bonne raison de surcharger une méthode et la surcharge dans ce cas précis est presque certainement un anti-pattern.


5 commentaires

Je vérifiais aussi deux paramètres mais avec implicite comme: def a [T] (i: Int) (implicit numeric: Numeric [T]): Double = ???


Il aurait une signature différente après l'effacement, donc cela devrait fonctionner. Toujours aucune idée de la raison pour laquelle on essaierait d’y parvenir en premier lieu.


Oui, c'est étrange pourquoi ne pas écrire une fonction générique, ou donner un nom différent))


@MateuszKubuszok pls vérifier la question, j'ai mis à jour la motivation


@Bob belle capture avec paramètre implicite mais cela ne semble pas approprié avec la directive akka-http car le bloc de code avec extractor-func devrait être traité comme un paramètre implicite



0
votes

Essayez peut-être de lever l'ambiguïté avec le type ascription comme ceci

def a(i:Int): Int = 1
def a[T](i: Int): Double = 2

a(1): Int // res0: Int = 1
a[Any](1) // res1: Double = 2.0


3 commentaires

Le premier cas aboutit à une erreur: incompatibilité de type ; trouvé: Double, obligatoire: Int


@mopdobopot; Vous devrez être plus précis. Ce code, soumis par @MarioGalic, passe mes tests sans faute et il répond directement à votre question d'origine (avant les mods).


Désolé les gars, mon erreur, le code @MarioGalic fonctionne très bien mais malheureusement pas adapté à mon cas



0
votes

Ma réponse est une approche un peu générique:

def add(a: Int): Int = a + 1
def add[T](a: T)(implicit num: Numeric[T]): Double = {
  num.toDouble(a) + 2.3
}

println(add(5)) // 6
println(add[Int](6)) // 8.3

L'exemple sur Scastie


1 commentaires

belle idée avec param implicite mais ça ne convient pas dans mon cas, j'ai mis à jour une question, désolé pour ça



0
votes

Je pense que vous avez un problème de séparation des préoccupations ici.

Autorisation d'un utilisateur et l'extraction de données sont des choses différentes, donc je les traiterais comme deux opérations différentes: une directive pour autoriser un utilisateur et une méthode de base de données pour charger des données pour cet utilisateur. Le principal avantage de ceci est que vous pouvez autoriser un utilisateur pour un ensemble de points de terminaison en une seule opération.

Voici un exemple simple de ce qui pourrait fonctionner:

def route =
  auth { authUser =>
    (pathEnd | pathSingleSlash) {
      concat(
        get {
          val data = load(authUser, staticUuid)

          ???
        },
        post {
          decodeRequest {
            entity(as[MyRequestBody]) { myRequest =>
              val data = load(authUser, myRequest.uuid)

              ???
            }
          }
        },
      )
    }
  }

S'il est difficile de passer authUser aux méthodes tout le temps, faites-en un implicite des méthodes d'extraction de données. Et vous pouvez également faire de l'opération de chargement une directive, mais une directive différente de auth.

Cela peut sembler plus verbeux de cette façon, mais la personne qui regarde votre code en deux le temps de l'année vous remerciera d'avoir rendu le code facile à comprendre. Et cette personne sera probablement vous.


2 commentaires

J'ai un authService distinct qui me renvoie l'ensemble des droits et l'objet User (authService stockant les utilisateurs) en une seule réponse. Ensuite, du côté métier, le client spécial vérifie ces droits (peut-être en fonction de l'objet métier requis pour cela). Et ce client charge l'objet de db par lui-même, il sait quel objet requis pour quel droit. La chose que je veux est de fournir cet objet au code suivant sans une autre demande à la base de données


Bon, donc la directive auth renvoie le User de authService, et la méthode load vérifie les droits et charge l'objet à partir de la base de données.