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?
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?
Réponses:
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 FunctionN
dans 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 def
déclaration - tout ce qui concerne a def
sauf son corps.
Valeur Déclarations et définitions et déclarations variables et définitions sont val
et var
dé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 def
dé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 FunctionX
traits, tels que Function0
, Function1
, Function2
, etc. Il peut être compris PartialFunction
aussi 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 apply
mé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 AnyRef
retour, l'un des deux types ci-dessus fonctionnerait.
Maintenant, quelle est la similitude d'une méthode et d'une fonction? Eh bien, si f
est une fonction et m
est 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) => R
les 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 m
type est (List[Int])AnyRef
dans (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 AbstractFunction1
classe 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 f
ci-dessus peut nécessairement spécifier le type de List
réception ( List[Int]
dans l'exemple), il m
peut 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.
val f = m
par 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' this
intérieur de la apply
méthode ne fait pas référence à l' AnyRef
objet, mais à l'objet dans la méthode duquel val f = m _
est évalué (l' extérieur this
, pour ainsi dire ), car this
fait partie des valeurs capturées par la fermeture (comme par exemple, return
comme indiqué ci-dessous).
Une grande différence pratique entre une méthode et une fonction est ce que return
signifie. return
ne 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
for (a <- List(1, 2, 3)) { return ... }
? Cela devient sans sucre jusqu'à la fermeture.
return
renvoyé une valeur de la fonction, et une certaine forme de escape
ou break
ou continue
pour retourner des méthodes.
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
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
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>
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 Function1
instance 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
Dans Scala 2.13, contrairement aux fonctions, les méthodes peuvent prendre / retourner
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
Pratiquement, un programmeur Scala a seulement besoin de connaître les trois règles suivantes pour utiliser correctement les fonctions et les méthodes:
def
et 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.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.