Différence entre méthode et fonction dans Scala


254

J'ai lu les fonctions Scala (partie d'une autre tournée de Scala ). Dans ce poste, il a déclaré:

Les méthodes et les fonctions ne sont pas la même chose

Mais il n'a rien expliqué à ce sujet. Qu'essayait-il de dire?



3
Je pense que vous pouvez obtenir quelque chose de Quelle est la différence entre une méthode et une fonction
jinglining

Une question de suivi avec de bonnes réponses: fonctions vs méthodes dans Scala
Josiah Yoder

Réponses:


238

Jim a couvert cela à peu près dans son article de blog , mais je poste ici un briefing pour référence.

Voyons d'abord ce que la spécification Scala nous dit. Le chapitre 3 (types) nous parle des types de fonctions (3.2.9) et des types de méthodes (3.3.1). Le chapitre 4 (déclarations de base) parle de déclaration et définitions de valeur (4.1), de déclaration et définitions de variable (4.2) et de déclarations et définitions de fonctions (4.6). Le chapitre 6 (expressions) parle des fonctions anonymes (6.23) et des valeurs de méthode (6.7). Curieusement, les valeurs de fonction sont parlées une seule fois sur 3.2.9, et nulle part ailleurs.

Un type de fonction est (à peu près) un type de la forme (T1, ..., Tn) => U , qui est un raccourci pour le trait FunctionNdans la bibliothèque standard. Les fonctions anonymes et les valeurs de méthode ont des types de fonction, et les types de fonction peuvent être utilisés dans le cadre des déclarations et définitions de valeurs, de variables et de fonctions. En fait, elle peut faire partie d'un type de méthode.

Un type de méthode est un type sans valeur . Cela signifie qu'il n'y a pas de valeur - pas d'objet, pas d'instance - avec un type de méthode. Comme mentionné ci-dessus, une valeur de méthode a en fait un type de fonction . Un type de méthode est une defdéclaration - tout ce qui concerne a defsauf son corps.

Valeur Déclarations et définitions et déclarations variables et définitions sont valet vardéclarations, y compris les types et la valeur - qui peut être, respectivement, le type de fonction et fonctions anonymes ou valeurs Méthode . Notez que, sur la JVM, ces (valeurs de méthode) sont implémentées avec ce que Java appelle des "méthodes".

Une déclaration de fonction est une defdéclaration, y compris le type et le corps . La partie type est le type de méthode et le corps est une expression ou un bloc . Ceci est également implémenté sur la JVM avec ce que Java appelle des "méthodes".

Enfin, une fonction anonyme est une instance d'un type de fonction (c'est-à-dire une instance du trait FunctionN), et une valeur de méthode est la même chose! La distinction est qu'une valeur de méthode est créée à partir de méthodes, soit en post-fixant un trait de soulignement ( m _est une valeur de méthode correspondant à la "déclaration de fonction" ( def) m), soit par un processus appelé eta-expansion , qui est comme une conversion automatique de la méthode Pour fonctionner.

C'est ce que disent les spécifications, alors permettez-moi de dire ceci: nous n'utilisons pas cette terminologie! Cela conduit à trop de confusion entre ce que l'on appelle la "déclaration de fonction" , qui fait partie du programme (chapitre 4 - déclarations de base) et la "fonction anonyme" , qui est une expression, et le "type de fonction" , qui est, bien un type - un trait.

La terminologie ci-dessous, et utilisée par les programmeurs Scala expérimentés, fait un changement par rapport à la terminologie de la spécification: au lieu de dire la déclaration de fonction , nous disons méthode . Ou même la déclaration de méthode. De plus, nous notons que les déclarations de valeurs et les déclarations de variables sont également des méthodes à des fins pratiques.

Donc, étant donné le changement de terminologie ci-dessus, voici une explication pratique de la distinction.

Une fonction est un objet qui comprend l' un des FunctionXtraits, tels que Function0, Function1, Function2, etc. Il peut être compris PartialFunctionaussi bien, qui se prolonge en fait Function1.

Voyons la signature de type pour l'un de ces traits:

trait Function2[-T1, -T2, +R] extends AnyRef

Ce trait a une méthode abstraite (il a aussi quelques méthodes concrètes):

def apply(v1: T1, v2: T2): R

Et cela nous dit tout ce qu'il y a à savoir à ce sujet. Une fonction a une applyméthode qui reçoit N paramètres de types T1 , T2 , ..., TN et renvoie quelque chose de type R. Il est contra-variant sur les paramètres qu'il reçoit, et co-variant sur le résultat.

Cette variance signifie que a Function1[Seq[T], String]est un sous-type de Function1[List[T], AnyRef]. Être un sous-type signifie qu'il peut être utilisé à sa place . On peut facilement voir que si je vais appeler f(List(1, 2, 3))et m'attendre à un AnyRefretour, l'un des deux types ci-dessus fonctionnerait.

Maintenant, quelle est la similitude d'une méthode et d'une fonction? Eh bien, si fest une fonction et mest une méthode locale à la portée, les deux peuvent être appelées comme ceci:

val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))

Ces appels sont en fait différents, car le premier n'est qu'un sucre syntaxique. Scala l'étend à:

val o1 = f.apply(List(1, 2, 3))

Ce qui, bien sûr, est un appel de méthode sur un objet f. Les fonctions ont également d'autres sucres syntaxiques à son avantage: les littéraux de fonction (deux d'entre eux, en fait) et (T1, T2) => Rles signatures de type. Par exemple:

val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
  case i: Int => "Int"
  case d: Double => "Double"
  case o => "Other"
}

Une autre similitude entre une méthode et une fonction est que la première peut être facilement convertie en la seconde:

val f = m _

Scala développera cela , en supposant que le mtype est (List[Int])AnyRefdans (Scala 2.7):

val f = new AnyRef with Function1[List[Int], AnyRef] {
  def apply(x$1: List[Int]) = this.m(x$1)
}

Sur Scala 2.8, il utilise en fait une AbstractFunction1classe pour réduire la taille des classes.

Notez que l'on ne peut pas convertir l'inverse - d'une fonction à une méthode.

Les méthodes ont cependant un gros avantage (enfin, deux - elles peuvent être légèrement plus rapides): elles peuvent recevoir des paramètres de type . Par exemple, alors que fci-dessus peut nécessairement spécifier le type de Listréception ( List[Int]dans l'exemple), il mpeut le paramétrer:

def m[T](l: List[T]): String = l mkString ""

Je pense que cela couvre à peu près tout, mais je serai heureux de le compléter avec des réponses à toutes les questions qui pourraient rester.


26
Cette explication est très claire. Bien joué. Malheureusement, le livre Odersky / Venners / Spoon et la spécification Scala utilisent les mots "fonction" et "méthode" de façon quelque peu interchangeable. (Ils sont plus susceptibles de dire "fonction" où "méthode" serait plus clair, mais parfois cela se produit également dans l'autre sens, par exemple, la section 6.7 de la spécification, qui couvre la conversion des méthodes en fonctions, est nommée "Valeurs de méthode". Ugh .) Je pense que l'utilisation lâche de ces mots a créé beaucoup de confusion lorsque les gens essaient d'apprendre la langue.
Seth Tisue

4
@Seth, je sais, je sais - PinS était le livre qui m'a appris Scala. J'ai mieux appris à la dure, c'est-à-dire que Paul m'a remis les choses au clair.
Daniel C.Sobral

4
Grande explication! J'ai une chose à ajouter: lorsque vous citez l'expansion de val f = mpar le compilateur, val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }vous devez souligner que l' thisintérieur de la applyméthode ne fait pas référence à l' AnyRefobjet, mais à l'objet dans la méthode duquel val f = m _est évalué (l' extérieur this , pour ainsi dire ), car thisfait partie des valeurs capturées par la fermeture (comme par exemple, returncomme indiqué ci-dessous).
Holger Peine

1
@ DanielC.Sobral, quel est le livre PinS que vous avez mentionné? Je suis également intéressé à apprendre Scala, et je n'ai pas trouvé de livre avec ce nom,
tldr

5
@tldr Programming in Scala , par Odersky et all. C'est l'abréviation courante pour cela (ils m'ont dit qu'ils n'aimaient pas vraiment PiS pour une raison quelconque! :)
Daniel C. Sobral

67

Une grande différence pratique entre une méthode et une fonction est ce que returnsignifie. returnne revient que d'une méthode. Par exemple:

scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
       val f = () => { return "test" }
                       ^

Le retour d'une fonction définie dans une méthode fait un retour non local:

scala> def f: String = {                 
     |    val g = () => { return "test" }
     | g()                               
     | "not this"
     | }
f: String

scala> f
res4: String = test

Alors que le retour d'une méthode locale ne revient que de cette méthode.

scala> def f2: String = {         
     | def g(): String = { return "test" }
     | g()
     | "is this"
     | }
f2: String

scala> f2
res5: String = is this

9
C'est parce que le retour est capturé par la fermeture.
Daniel C.Sobral

4
Je ne peux pas penser à une seule fois où je voudrais «revenir» d'une fonction à une portée non locale. En fait, je peux voir cela comme un grave problème de sécurité si une fonction peut simplement décider qu'elle souhaite remonter la pile. Se sent un peu comme longjmp, seul moyen plus facile de se tromper accidentellement. J'ai remarqué que scalac ne me laissait pas revenir des fonctions, cependant. Est-ce à dire que cette abomination a été rayée de la langue?
rooter le

2
@root - qu'en est-il du retour de l'intérieur d'un for (a <- List(1, 2, 3)) { return ... }? Cela devient sans sucre jusqu'à la fermeture.
Ben Lings

Hmm ... Eh bien, c'est un cas d'utilisation raisonnable. A toujours le potentiel de conduire à des problèmes horribles difficiles à déboguer, mais cela le place dans un contexte plus sensible.
root

1
Honnêtement, j'utiliserais une syntaxe différente. avoir returnrenvoyé une valeur de la fonction, et une certaine forme de escapeou breakou continuepour retourner des méthodes.
Ryan The Leach

38

fonction Une fonction peut être invoquée avec une liste d'arguments pour produire un résultat. Une fonction a une liste de paramètres, un corps et un type de résultat. Les fonctions qui sont membres d'une classe, d'un trait ou d'un objet singleton sont appelées méthodes . Les fonctions définies à l'intérieur d'autres fonctions sont appelées fonctions locales. Les fonctions avec le type de résultat Unit sont appelées procédures. Les fonctions anonymes dans le code source sont appelées des littéraux de fonction. Au moment de l'exécution, les littéraux de fonction sont instanciés en objets appelés valeurs de fonction.

Programmation dans Scala Second Edition. Martin Odersky - Lex Spoon - Bill Venners


1
Une fonction peut appartenir à une classe en tant que def ou en tant que val / var. Seuls les def sont des méthodes.
Josiah Yoder

29

Disons que vous avez une liste

scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

Définir une méthode

scala> def m1(i:Int)=i+2
m1: (i: Int)Int

Définir une fonction

scala> (i:Int)=>i+2
res0: Int => Int = <function1>

scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)

Méthode acceptant l'argument

scala> m1(2)
res3: Int = 4

Définition d'une fonction avec val

scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>

L'argument pour fonctionner est facultatif

 scala> p(2)
    res4: Int = 4

scala> p
res5: Int => Int = <function1>

L'argument de la méthode est obligatoire

scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function

Consultez le didacticiel suivant qui explique comment passer d'autres différences avec des exemples comme un autre exemple de diff avec la fonction Method Vs, utiliser la fonction comme variables, créer une fonction qui a renvoyé la fonction


13

Les fonctions ne prennent pas en charge les valeurs par défaut des paramètres. Les méthodes le font. La conversion d'une méthode en fonction perd les valeurs par défaut des paramètres. (Scala 2.8.1)


5
Y a-t-il une raison à cela?
corazza

7

Il y a un bel article ici dont la plupart de mes descriptions sont tirées. Juste une courte comparaison des fonctions et méthodes concernant ma compréhension. J'espère que ça aide:

Fonctions : Ils sont fondamentalement un objet. Plus précisément, les fonctions sont des objets avec une méthode apply; Par conséquent, elles sont un peu plus lentes que les méthodes en raison de leur surcharge. Elle est similaire aux méthodes statiques dans le sens où elles sont indépendantes d'un objet à invoquer. Un exemple simple de fonction est comme ci-dessous:

val f1 = (x: Int) => x + x
f1(2)  // 4

La ligne ci-dessus n'est rien sauf l'affectation d'un objet à un autre comme object1 = object2. En fait, object2 dans notre exemple est une fonction anonyme et le côté gauche obtient le type d'un objet à cause de cela. Par conséquent, maintenant f1 est un objet (Fonction). La fonction anonyme est en fait une instance de Function1 [Int, Int] qui signifie une fonction avec 1 paramètre de type Int et une valeur de retour de type Int. L'appel de f1 sans les arguments nous donnera la signature de la fonction anonyme (Int => Int =)

Méthodes : Ce ne sont pas des objets mais assignés à une instance d'une classe, c'est-à-dire un objet. Exactement la même que la méthode en java ou les fonctions membres en c ++ (comme Raffi Khatchadourian l'a souligné dans un commentaire à cette question ) et etc. Un exemple simple de méthode est comme ci-dessous:

def m1(x: Int) = x + x
m1(2)  // 4

La ligne ci-dessus n'est pas une simple attribution de valeur mais une définition d'une méthode. Lorsque vous appelez cette méthode avec la valeur 2 comme la deuxième ligne, le x est remplacé par 2 et le résultat sera calculé et vous obtenez 4 en sortie. Ici, vous obtiendrez une erreur si vous écrivez simplement m1 car c'est une méthode et vous avez besoin de la valeur d'entrée. En utilisant _, vous pouvez affecter une méthode à une fonction comme ci-dessous:

val f2 = m1 _  // Int => Int = <function1>

Que signifie "assigner une méthode à une fonction"? Cela signifie-t-il simplement que vous avez maintenant un objet qui se comporte de la même manière que la méthode?
K.M

@KM: val f2 = m1 _ est équivalent à val f2 = new Function1 [Int, Int] {def m1 (x: Int) = x + x};
sasuke

3

Voici un excellent article de Rob Norris qui explique la différence, voici un TL; DR

Les méthodes de Scala ne sont pas des valeurs, mais les fonctions le sont. Vous pouvez construire une fonction qui délègue à une méthode via η-expansion (déclenchée par le truc de soulignement de fin).

avec la définition suivante:

une méthode est quelque chose de défini avec def et une valeur est quelque chose que vous pouvez assigner à un val

En bref ( extrait du blog ):

Lorsque nous définissons une méthode, nous voyons que nous ne pouvons pas l'affecter à a val.

scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int

scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
       val f = add1

Notez également le type de add1, qui ne semble pas normal; vous ne pouvez pas déclarer une variable de type (n: Int)Int. Les méthodes ne sont pas des valeurs.

Cependant, en ajoutant l'opérateur de suffixe η-expansion (η est prononcé "eta"), nous pouvons transformer la méthode en une valeur de fonction. Notez le type de f.

scala> val f = add1 _
f: Int => Int = <function1>

scala> f(3)
res0: Int = 4

L'effet de _est d'effectuer l'équivalent de ce qui suit: nous construisons une Function1instance qui délègue à notre méthode.

scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>

scala> g(3)
res18: Int = 4

1

Dans Scala 2.13, contrairement aux fonctions, les méthodes peuvent prendre / retourner

  • paramètres de type (méthodes polymorphes)
  • paramètres implicites
  • types dépendants

Cependant, ces restrictions sont levées dans dotty (Scala 3) par les types de fonctions polymorphes # 4672 , par exemple, la version 0.23.0-RC1 de dotty active la syntaxe suivante

Paramètres de type

def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))

Paramètres implicites ( paramètres de contexte )

def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero

Types dépendants

class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet

Pour plus d'exemples, voir tests / run / polymorphic-functions.scala


0

Pratiquement, un programmeur Scala a seulement besoin de connaître les trois règles suivantes pour utiliser correctement les fonctions et les méthodes:

  • Les méthodes définies par defet les littéraux de fonction définis par =>sont des fonctions. Il est défini à la page 143, chapitre 8 du livre de programmation de Scala, 4e édition.
  • Les valeurs de fonction sont des objets qui peuvent être transmis comme n'importe quelle valeur. Les littéraux de fonction et les fonctions partiellement appliquées sont des valeurs de fonction.
  • Vous pouvez laisser le trait de soulignement d'une fonction partiellement appliquée si une valeur de fonction est requise à un point du code. Par exemple:someNumber.foreach(println)

Après quatre éditions de Programming in Scala, il est toujours difficile pour les gens de différencier les deux concepts importants: fonction et valeur de fonction car toutes les éditions ne donnent pas d'explication claire. La spécification du langage est trop compliquée. J'ai trouvé que les règles ci-dessus sont simples et précises.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.