J'ai regardé F#, faire des faible niveau de tutoriels, donc mes connaissances sont très limitées. Cependant, il était évident pour moi que son style a été essentiellement fonctionnelle, avec OO être plus comme un add-on, beaucoup plus d'un ADT + module de système que le vrai OO. Le sentiment que j'ai peut être mieux décrit comme si toutes les méthodes sont statiques (comme en Java statique).
Voir, par exemple, le code en utilisant le tuyau de l'opérateur (|>
). Prendre cet extrait de l' article de wikipédia sur F#:
[1 .. 10]
|> List.map fib
(* equivalent without the pipe operator *)
List.map fib [1 .. 10]
La fonction map
n'est pas une méthode de la liste de l'instance. Au lieu de cela, il fonctionne comme une méthode statique sur un List
module qui prend une liste d'instance que l'un de ses paramètres.
Scala, d'autre part, est entièrement OO. Commençons, tout d'abord, avec la Scala équivalent de ce code:
List(1 to 10) map fib
// Without operator notation or implicits:
List.apply(Predef.intWrapper(1).to(10)).map(fib)
Ici, map
est une méthode sur l'exemple de l' List
. Statique méthodes, telles que l' intWrapper
sur Predef
ou apply
sur List
, sont beaucoup plus rares. Ensuite, il y a des fonctions, telles que l' fib
- dessus. Ici, fib
n'est pas une méthode sur int
, mais ce n'est pas une méthode statique. Au lieu de cela, il est un objet -- la deuxième principale différence que je vois entre F# et Scala.
Considérons le F# la mise en œuvre de la Wikipedia, et un équivalent de la Scala de mise en œuvre:
// F#, from the wiki
let rec fib n =
match n with
| 0 | 1 -> n
| _ -> fib (n - 1) + fib (n - 2)
// Scala equivalent
def fib(n: Int): Int = n match {
case 0 | 1 => n
case _ => fib(n - 1) + fib(n - 2)
}
Au-dessus de la Scala de mise en œuvre est une méthode, mais Scala convertit en une fonction pour être en mesure de passer à l' map
. Je vais le modifier en dessous de sorte qu'il devient une méthode qui retourne une fonction au lieu de cela, pour montrer comment les fonctions de travail en Scala.
// F#, returning a lambda, as suggested in the comments
let rec fib = function
| 0 | 1 as n -> n
| n -> fib (n - 1) + fib (n - 2)
// Scala method returning a function
def fib: Int => Int = {
case n @ (0 | 1) => n
case n => fib(n - 1) + fib(n - 2)
}
// Same thing without syntactic sugar:
def fib = new Function1[Int, Int] {
def apply(param0: Int): Int = param0 match {
case n @ (0 | 1) => n
case n => fib.apply(n - 1) + fib.apply(n - 2)
}
}
Donc, en Scala, toutes les fonctions sont des objets de mise en œuvre de ce trait FunctionX
, ce qui définit une méthode appelée apply
. Comme indiqué ici et dans la création d'une liste ci-dessus, .apply
peut être omis, ce qui rend les appels de fonction ressemble juste à des appels de méthode.
À la fin, tout en Scala est un objet -- et de l'instance d'une classe, et chaque objet appartient à une classe, et de tous les codes appartiennent à une méthode, qui est exécuté en quelque sorte. Même match
dans l'exemple ci-dessus est utilisé pour être une méthode, mais a été convertie en un mot-clé pour éviter des problèmes tout à fait il ya un moment.
Alors, comment sur la partie fonctionnelle de celui-ci? F# appartient à l'une des plus traditionnelles familles de langages fonctionnels. Alors qu'il n'a pas certaines fonctionnalités, certaines personnes sont importantes pour les langages fonctionnels, le fait est que le F# est la fonction par défaut, pour ainsi dire.
Scala, d'autre part, a été créé avec l'intention d' unifier fonctionnelle et OO modèles, au lieu de la simple fourniture d'eux en tant que parties séparées de la langue. Dans la mesure où il a été couronné de succès dépend de ce que vous estimez être de la programmation fonctionnelle. Voici certaines des choses qui ont été porté par Martin Odersky:
Les fonctions sont des valeurs. Ils sont aussi des objets -- parce que toutes les valeurs sont des objets Scala-mais l'idée qu'une fonction est une valeur qui peut être manipulé est une question importante, avec ses racines tout le chemin du retour à l'origine Lisp mise en œuvre.
-
Un soutien fort pour immuable types de données. La programmation fonctionnelle a toujours été préoccupé par la diminution des effets secondaires sur un programme, que les fonctions peuvent être analysés comme de véritables fonctions mathématiques. Scala fait, il est facile de faire des choses immuables, mais il n'a pas faire deux choses qui FP puristes critiquent pour:
- Il ne fait pas de la mutabilité plus difficile.
- Il ne fournit pas un effet de système, par lequel la mutabilité peut être statique d'un suivi.
-
Support pour les Types de Données Algébriques. Types de données algébriques (appelé ADT, qui de prêter à confusion, est également un Type Abstrait de Données, une chose différente) sont très fréquents dans la programmation fonctionnelle, et sont plus utiles dans les situations où l'on utilisent généralement le modèle visiteur dans les langages à objets.
Comme tout autre chose, ADTs en Scala sont mis en œuvre sous forme de classes et de méthodes, avec quelques sucres syntaxiques pour les rendre facile à utiliser. Cependant, la Scala est beaucoup plus bavarde que F# (ou autres langages fonctionnels, d'ailleurs) qui les soutiennent. Par exemple, au lieu de F#'s |
pour les états, il utilise case
.
-
Le soutien à la rigueur. Non-rigueur signifie seulement le calcul des trucs sur demande. C'est un aspect essentiel de Haskell, où il est étroitement intégré avec l'effet secondaire du système. En Scala, cependant, la non-rigueur soutien est très timide et débutante. Il est disponible et utilisé, mais dans une manière restreinte.
Par exemple, la Scala, à la non-stricte de la liste, l' Stream
, n'est pas en faveur d'une véritable non-stricte foldRight
, comme Haskell n'. En outre, certains avantages de la non-rigueur sont acquis seulement lorsque c'est la valeur par défaut dans la langue, au lieu d'une option.
-
Soutien pour la compréhension de liste. En fait, Scala appelle pour-de la compréhension, que la façon dont il est mis en œuvre est complètement dissociée des listes. Dans ses termes les plus simples, interprétations de la liste peut être considéré comme l' map
fonction/méthode indiquée dans l'exemple, bien que l'imbrication de la carte des états (compatible avec flatMap
en Scala) ainsi que le filtrage (filter
ou withFilter
en Scala, selon la rigueur des exigences) sont généralement prévu.
C'est une opération très courante dans les langages fonctionnels, et souvent de la lumière dans la syntaxe -- comme en Python in
de l'opérateur. Encore une fois, la Scala est un peu plus bavard que d'habitude.
À mon avis, la Scala est inégalé dans la combinaison de FP et OO. Il s'agit de l'OO côté du spectre vers la FP côté, ce qui est inhabituel. Surtout, je vois FP langues avec OO attaquer sur elle -- et il se sent attaquer sur-le-moi. Je suppose que FP sur Scala probablement se sent de la même façon pour les langages fonctionnels programmeurs.
MODIFIER
La lecture de certains autres réponses que j'ai réalisé qu'il y avait un autre sujet important: l'inférence de type. Lisp est un typées dynamiquement de la langue, et c'est à peu près défini les attentes pour les langages fonctionnels. Le moderne typé statiquement les langages fonctionnels ont tous une forte inférence de type de systèmes, le plus souvent, le Hindley-Milner1 de l'algorithme, ce qui rend les déclarations de type essentiellement en option.
Scala ne pouvez pas utiliser la Hindley-Milner algorithme en raison de la Scala de soutien pour l'héritage2. Scala a à adopter une beaucoup moins puissant algorithme d'inférence de types, en fait, l'inférence de type en Scala est intentionnellement pas défini dans le cahier des charges, et sous réserve de l'amélioration continue (perfectionnement est l'une des plus grandes caractéristiques de la prochaine version 2.8 de la Scala, par exemple).
En fin de compte, cependant, Scala exige que tous les paramètres pour avoir leurs types déclaré lors de la définition des méthodes. Dans certaines situations, comme la récursivité, types de retour pour les méthodes doivent également être déclarés.
Fonctions Scala ont souvent leurs types inférés au lieu d'déclaré, cependant. Par exemple, pas de déclaration de type est nécessaire ici: List(1, 2, 3) reduceLeft (_ + _)
où _ + _
est en fait une fonction anonyme de type Function2[Int, Int, Int]
.
De même, la déclaration de type de variables est souvent inutile, mais l'héritage peuvent l'exiger. Par exemple, Some(2)
et None
ont une commune de la superclasse Option
, mais en réalité, appartiennent à différents subclases. Donc, en général, déclarer var o: Option[Int] = None
à assurez-vous que le type est affecté.
Cette forme limitée de l'inférence de type est beaucoup mieux que les langages à objets à typage statique offrent habituellement, qui donne à la Scala un sentiment de légèreté, et bien pire que statiquement typé FP langues offrent habituellement, qui donne à la Scala un sentiment de heavyness. :-)
Notes:
En fait, l'algorithme est originaire de Damas et Milner, qui l'a appelé "Algorithme W", selon wikipédia.
-
Martin Odersky mentionné dans un commentaire ici que:
La raison de la Scala n'a pas de Hindley/Milner, l'inférence de type est
qu'il est très difficile de combiner avec des fonctionnalités telles que
la surcharge (ad-hoc variante, pas de classes de type), record
la sélection, et le sous-typage
Il poursuit en affirmant qu'il ne peut pas être impossible, et il est descendu à un compromis. S'il vous plaît ne allez à ce lien pour plus d'informations, et si vous venez avec une meilleure instruction ou, mieux encore, certains le papier d'une manière ou d'une autre, je lui en serais reconnaissant pour la référence.
Permettez-moi de remercier Jon Harrop pour regarder cela, comme je l'ai été en supposant que c'était impossible. Eh bien, peut-être que c'est, et je ne pouvais pas trouver un lien. Notez, cependant, qu'il n'est pas d'héritage à lui seul à l'origine du problème.