2 votes

Scala : définition d'une fonction avec paramètres et appelable

Je me demandais comment cela fonctionne si je veux définir une fonction qui prend un ou plusieurs paramètres et un appelable (une fonction), et pourquoi l'annotation est comme ceci.

Je vais prendre le code de ceci réponse à titre d'exemple :

// Returning T, throwing the exception on failure
  @annotation.tailrec
  final def retry[T](n: Int)(fn: => T): T = {
    util.Try { fn } match {
      case util.Success(x) => x
      case _ if n > 1 => retry(n - 1)(fn)
      case util.Failure(e) => throw e
    }
  }

Dans cette fonction, il y a quelques éléments intéressants :

  1. L'annotation tailrec.
  2. Fonction de type générique retry[T]
  3. Paramètre int
  4. Appelable fn

Ma question porte sur le point 4. Pourquoi et comment la définition de cette fonction prend two parenthèses rondes. Si l'on veut passer une fonction appelable à une autre fonction, faut-il toujours utiliser des crochets à côté de la "liste" des paramètres facultatifs ? Pourquoi ne pas les mettre ensemble avec les paramètres ?

Merci d'avance

1voto

maddening Points 942

Pour être honnête, vous n'avez pas besoin d'utiliser listes de paramètres multiples afin de passer la fonction comme argument. Par exemple

def pass(string: String, fn: String => Unit): Unit = fn(string)

pourrait tout à fait fonctionner. Cependant, comment l'appellerais-tu ?

pass("test", s => println(s))

Certains trouveraient cela maladroit. Vous voudriez plutôt le passer comme :

pass("test")(s => println(s))

ou même

pass("test") { s =>
  println(s)
}

pour faire croire que la fonction est un bloc annexé à la pass appelé avec un paramètre.

Avec une liste à paramètre unique, vous serez obligé de l'écrire comme suit :

pass("test", println)
pass("test", s => println(s))
pass("test", { s => println(s) })

Avec le dernier paramètre curé, la syntaxe devient plus confortable. Dans des langages comme Haskell, où la curation se fait automatiquement, vous n'avez pas à vous soucier de décisions de conception syntaxique comme celle-ci. Malheureusement, Scala exige que vous preniez ces décisions de manière explicite.

1voto

Dima Points 7274

Vous pouvez avoir plusieurs listes de paramètres dans la déclaration d'une fonction. C'est en grande partie la même chose que de fusionner tous les paramètres en une seule liste ( def foo(a: Int)(b: Int) est plus ou moins équivalent à def foo(a: Int, b: Int) ) avec quelques différences :

  • Vous pouvez faire référence aux paramètres de la ou des listes précédentes dans la déclaration : def foo(a : Int, b: Int = a + 1) ne fonctionne pas, mais def foo(a: Int)(b: Int = a +1) fait.

  • Les paramètres de type peuvent être déduits entre les listes de paramètres : def foo[T](x: T, f: T => String): String ; foo(1, _.toString) ne fonctionne pas (il faudrait écrire foo[Int](1, _.toString) mais def foo[T](x: T)(f: T => String); foo(1)(_.toString) fait.

  • Vous pouvez seulement déclarer la liste entière comme implicite, donc, les listes multiples sont utiles quand vous avez besoin que certains paramètres soient implicites, et pas les autres : def foo(a: Int)(implicit b: Configuration)

Ensuite, il y a quelques avantages syntaxiques - des choses que vous pourriez faire avec la liste unique, mais qui seraient plus laides :

  • Le curry :

    def foo(a: Int)(b: Int): String
    val bar: Int => String = foo(1)

    Vous pourriez aussi écrire ceci avec la liste simple, mais ce ne serait pas aussi joli :

     def foo(a: Int, b: Int): String
     val bar: Int => String = foo(1, _)

Enfin, pour répondre à votre question :

def retry[T](n: Int)(f: => T) 

est agréable, car les parenthèses sont facultatives autour des listes à un seul argument. Cela vous permet donc d'écrire des choses comme

retry(3) { 
  val c = createConnection
  doStuff(c)
  closeConnection(c)
}

ce qui serait beaucoup plus moche si f a été fusionné dans la même liste. De plus, le curage est utile :

val transformer = retry[Int](3)

Seq(1,2,3).map { n => transformer(n + 1) }
Seq(4,5,6).map { n => transformer(n * 2) }

0voto

Yuval Itzchakov Points 13820

Si vous voulez passer une fonction appelable à n'importe quelle fonction, vous devez toujours utiliser des crochets à côté de la "liste" des paramètres optionnels ? Pourquoi ne pas les mettre ensemble avec les paramètres ?

Il n'y a pas d'obligation de ce type, il s'agit (principalement) d'une question d'équité. style . IMO, cela conduit également à une syntaxe plus propre. Avec deux listes de paramètres, dont la seconde est la fonction produisant le résultat, nous pouvons appeler la fonction retry méthode :

val res: Try[Int] retry(3) {
  42
}

Au lieu de cela, si nous utilisions une liste de paramètres unique, notre appel de méthode ressemblerait à ceci :

val res: Try[Int] = retry(3, () => {
  42
})

Je trouve la première syntaxe plus propre, qui vous permet aussi d'utiliser retry comme une méthode curée lorsque l'on ne fournit que la première liste de paramètres

Cependant, si nous pensons à un cas d'utilisation plus avancé, l'inférence de type Scala fonctionne entre les listes de paramètres et non à l'intérieur. Cela signifie que si nous avons une méthode :

def mapFun[T, U](xs: List[T])(f: T => U): List[U] = ???

Nous serions en mesure d'appeler notre méthode sans spécifier le type de T ou U à l'emplacement de l'appel :

val res: List[Int] = mapFun(List.empty[Int])(i => i + 1)

Mais si nous utilisions une seule liste de paramètres,

def mapFun2[T, U](xs: List[T], f: T => U): List[U] = ???

Ça ne compile pas :

val res2 = mapFun2(List.empty[Int], i => i + 1)

Au lieu de cela, nous devrions écrire :

val res2 = mapFun2[Int, Int](List.empty[Int], i => i + 1)

Pour aider le compilateur à choisir les bons types.

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