Quel est le point de l'option de classe [T]?
-
21-09-2019 - |
Question
Je ne suis pas en mesure de comprendre le point de classe Option[T]
à Scala. Je veux dire, je ne suis pas en mesure de voir les advanages de None
sur null
.
Par exemple, considérons le code:
object Main{
class Person(name: String, var age: int){
def display = println(name+" "+age)
}
def getPerson1: Person = {
// returns a Person instance or null
}
def getPerson2: Option[Person] = {
// returns either Some[Person] or None
}
def main(argv: Array[String]): Unit = {
val p = getPerson1
if (p!=null) p.display
getPerson2 match{
case Some(person) => person.display
case None => /* Do nothing */
}
}
}
Supposons maintenant, la méthode retourne getPerson1
null
, l'appel fait à display
en première ligne de main
est vouée à l'échec avec NPE
. De même, si getPerson2
retourne None
, l'appel display
à nouveau échouer avec une erreur similaire.
Si oui, alors pourquoi Scala compliquer les choses en introduisant une nouvelle enveloppe de valeur (Option[T]
) au lieu de suivre une approche simple utilisée en Java?
Mise à jour:
J'ai modifié mon code comme par @Mitch suggestion . Je ne suis toujours pas en mesure de voir aucun avantage particulier de Option[T]
. Je dois tester la null
exceptionnelle ou None
dans les deux cas. : (
Si je comprends bien de @ de réponse de Michael, est le seul avantage de Option[T]
est qu'il indique explicitement le programmeur que cette méthode pourrait revenir Aucun ? Est-ce la seule raison derrière ce choix de conception?
La solution
Vous obtenez le point de Option
mieux si vous vous forcez à jamais, jamais, l'utilisation get
. En effet, get
est l'équivalent de « ok, envoyez-moi revenir à nulle terre ».
Alors, prenez cet exemple de la vôtre. Comment appelleriez-vous display
sans utiliser get
? Voici quelques alternatives:
getPerson2 foreach (_.display)
for (person <- getPerson2) person.display
getPerson2 match {
case Some(person) => person.display
case _ =>
}
getPerson2.getOrElse(Person("Unknown", 0)).display
Aucune de ces alternatives vous permettra d'appeler display
sur quelque chose qui n'existe pas.
Quant à savoir pourquoi get
existe, Scala ne vous dit pas comment votre code doit être écrit. Il peut doucement vous pousser, mais si vous voulez revenir à aucun filet de sécurité, il est votre choix.
Vous cloués ici:
est le seul avantage de l'option [T] est qu'il dit explicitement la programmeur que cette méthode pourrait retour Aucun?
A l'exception du « seulement ». Mais permettez-moi de répéter que d'une autre façon: principale avantage de Option[T]
sur T
est la sécurité de type. Il vous assure de ne pas être l'envoi d'une méthode de T
à un objet qui ne peut exister, comme le compilateur ne vous laissera pas.
Vous avez dit que vous devez tester les valeurs NULL dans les deux cas, mais si vous oubliez - ou ne sais pas - vous devez vérifier invalidés, le compilateur vous dire? Ou bien vos utilisateurs?
Bien sûr, en raison de son interopérabilité avec Java, Scala permet nulls comme Java fait. Donc, si vous utilisez des bibliothèques Java, si vous utilisez des bibliothèques Scala mal écrites, ou si vous utilisez mal écrit personnels bibliothèques Scala, vous aurez encore à traiter avec des pointeurs nuls.
D'autres deux avantages importants de Option
je peux penser sont:
-
Documentation: une signature de type de méthode vous dire si un objet est toujours retourné ou non
.
-
Monadique composabilité.
Ce dernier on prend beaucoup plus de temps pour apprécier pleinement, et ce n'est pas bien adapté à des exemples simples, car il ne montre sa force sur le code complexe. Alors, je vais vous donner un exemple ci-dessous, mais je suis bien conscient qu'il ne sera guère dire quoi que ce soit, sauf pour les personnes qui reçoivent déjà.
for {
person <- getUsers
email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)
Autres conseils
Comparer:
val p = getPerson1 // a potentially null Person
val favouriteColour = if (p == null) p.favouriteColour else null
avec:
val p = getPerson2 // an Option[Person]
val favouriteColour = p.map(_.favouriteColour)
La propriété monadique bind , qui apparaît dans Scala comme carte fonction, permet aux opérations de la chaîne sur les objets sans se soucier de savoir si elles sont « nulles » ou non.
Prenez cet exemple simple un peu plus loin. Disons que nous voulions trouver toutes les couleurs préférées d'une liste de personnes.
// list of (potentially null) Persons
for (person <- listOfPeople) yield if (person == null) null else person.favouriteColour
// list of Options[Person]
listOfPeople.map(_.map(_.favouriteColour))
listOfPeople.flatMap(_.map(_.favouriteColour)) // discards all None's
Ou peut-être que nous aimerions trouver le nom de la sœur de la mère du père d'une personne:
// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister
// with options
val fathersMothersSister = getPerson2.flatMap(_.father).flatMap(_.mother).flatMap(_.sister)
J'espère que cela jette une certaine lumière sur la façon dont les options peuvent rendre la vie un peu plus facile.
La différence est subtile. Gardez à l'esprit pour être vraiment une fonction il doit retourner une valeur - nul n'est pas vraiment considéré comme une « valeur de rendement normal » dans ce sens, plus un type de fond / rien.
Mais, dans un sens pratique, lorsque vous appelez une fonction qui renvoie le cas échéant quelque chose, vous feriez:
getPerson2 match {
case Some(person) => //handle a person
case None => //handle nothing
}
D'accord, vous pouvez faire quelque chose de similaire avec nul - mais cela fait la sémantique de l'appel getPerson2
évidente en vertu du fait qu'elle retourne Option[Person]
(une chose pratique agréable, autre que de compter sur une personne lisant la doc et obtenir un NPE parce qu'ils ne pas lire la doc).
Je vais essayer de déterrer un programmeur fonctionnel qui peut donner une réponse plus stricte que possible.
Pour moi, les options sont vraiment intéressantes lorsqu'il est manipulé avec la syntaxe de compréhension. Prenant Synesso exemple précédent:
// with potential nulls
val father = if (person == null) null else person.father
val mother = if (father == null) null else father.mother
val sister = if (mother == null) null else mother.sister
// with options
val fathersMothersSister = for {
father <- person.father
mother <- father.mother
sister <- mother.sister
} yield sister
Si l'un des None
sont assignation, le fathersMothersSister
sera None
mais pas NullPointerException
sera soulevée. Vous pouvez ensuite passer en toute sécurité fathersMothersSister
to une fonction prise de paramètres d'option sans se soucier. de sorte que vous ne cochez pas nulle et vous ne vous souciez pas des exceptions. Comparez cela à la version java présentée dans Synesso par exemple.
Vous avez des capacités de composition assez puissantes avec option:
def getURL : Option[URL]
def getDefaultURL : Option[URL]
val (host,port) = (getURL orElse getDefaultURL).map( url => (url.getHost,url.getPort) ).getOrElse( throw new IllegalStateException("No URL defined") )
Peut-être que quelqu'un d'autre a souligné ceci, mais je ne l'ai pas:
Un avantage de pattern-matching avec l'option [T] par rapport à la vérification nulle est que l'option est une classe scellée, de sorte que le compilateur Scala émettra un avertissement si vous négligez au code soit la partie ou le Aucun cas. Il y a un indicateur de compilateur au compilateur qui transformera les avertissements en erreurs. Il est donc possible d'éviter l'échec de gérer le «n'existe pas » cas au moment de la compilation plutôt que lors de l'exécution. Ceci est un énorme avantage sur l'utilisation de la valeur nulle.
Il est pas là pour aider à éviter un contrôle nul, il est là pour forcer un chèque nul. Le point devient clair lorsque votre classe a 10 champs, dont deux pourraient être nul. Et votre système a 50 autres classes similaires. Dans le monde Java, vous essayez d'éviter NPE sur ces champs en utilisant une combinaison de horesepower mentale, convention de nommage, ou peut-être même des annotations. Et tous les dev Java échoue à ce à un degré significatif. La classe d'option permet non seulement des valeurs « nullable » visuellement clair pour les développeurs essayant de comprendre le code, mais permet au compilateur d'appliquer ce contrat précédemment non-dit.
[copié ce commentaire Daniel Spiewak ]
Si la seule façon d'utiliser
Option
étaient à match de modèle afin d'obtenir valeurs out, alors oui, je suis d'accord que n'améliore pas du tout sur nul. Cependant, vous manquez une * énorme classe * de ses fonctionnalités. Le seul raison impérieuse d'utiliserOption
est si vous utilisez son ordre supérieur fonctions d'utilité. En effet, vous doivent utiliser sa nature monadique. Par exemple (en supposant une certaine quantité de coupe API):val row: Option[Row] = database fetchRowById 42 val key: Option[String] = row flatMap { _ get “port_key” } val value: Option[MyType] = key flatMap (myMap get) val result: MyType = value getOrElse defaultValue
Il était pas chouette? nous pouvons faire en fait beaucoup mieux si nous utilisons
for
-compréhensions:val value = for { row <- database fetchRowById 42 key <- row get "port_key" value <- myMap get key } yield value val result = value getOrElse defaultValue
Vous remarquerez que nous sommes * jamais * vérifier explicitement null, Aucun ou tout de même acabit. Le point entier de Option est d'éviter tout cela vérification. Vous venez de calculs de chaîne le long et déplacer la ligne jusqu'à ce que vous * Vraiment * besoin d'obtenir une valeur sur. À ce moment-là, vous pouvez décider si voulez-vous pas faire la vérification explicite (Que vous devriez jamais ont à faire), fournir une valeur par défaut, lancer une exception, etc.
Je ne jamais faire de correspondance explicite contre
Option
, et je sais que beaucoup de d'autres développeurs Scala qui sont dans la même bateau. David Pollak mentionné à moi l'autre jour qu'il utilise telle correspondance explicite surOption
(ouBox
, dans le cas de levage) en tant que signe que le développeur qui a écrit le code ne comprend pas complètement la langue et sa bibliothèque standard.Je ne veux pas être un marteau troll, mais vous avez vraiment besoin de regarder comment caractéristiques linguistiques sont * * en fait utilisés dans la pratique avant de les bash comme inutile. Je suis absolument d'accord Option est tout à fait uncompelling que * vous * utilisé, mais vous ne l'utilisez pas la comme il a été conçu.
Un point que personne ne semble ici avoir soulevé est que si vous pouvez avoir une référence nulle, il y a une distinction introduite par option.
C'est que vous pouvez avoir Option[Option[A]]
, qui serait habité par None
, Some(None)
et Some(Some(a))
où a
est l'un des habitants habituels de A
. Cela signifie que si vous avez une sorte de conteneur, et que vous voulez être en mesure de stocker des pointeurs nuls dans, et les sortir, vous devez passer une valeur booléenne retour supplémentaire pour savoir si vous avez effectivement une valeur sur. Verrues comme celui-ci abonder dans les API conteneurs java et quelques variantes sans verrouillage ne peut même pas leur fournir.
null
est une construction unique, il ne compose pas avec lui-même, il est uniquement disponible pour les types de référence, et il vous oblige à raisonner de façon non totale.
Par exemple, lors de l'enregistrement
if (x == null) ...
else x.foo()
vous devez transporter dans votre tête tout au long de la branche else
qui x != null
et que cela a déjà été vérifiée. Cependant, lorsque vous utilisez quelque chose comme l'option
x match {
case None => ...
case Some(y) => y.foo
}
sais y est None
by pas la construction - et vous sauriez qu'il n'a pas été null
soit, s'il n'y avait pas de Hoare billion erreur dollar.
Option [T] est une monade, qui est vraiment utile lorsque vous en utilisant des fonctions d'ordre supérieur pour manipuler les valeurs.
Je vous suggère de lire des articles énumérés ci-dessous, ils sont vraiment bons articles que vous montrer pourquoi l'option [T] est utile et comment peut-il être utilisé de manière fonctionnelle.
Ajout à teaser d'une réponse, comprendre pourquoi l'absence potentielle d'une valeur est représenté par Option
exige la compréhension de ce Option
actions avec de nombreux autres types de Scala spécifiquement, les types de modélisation monades. Si l'on représente l'absence d'une valeur nulle avec, cette distinction absence-présence ne peut pas participer aux contrats partagés par les autres types monades.
Si vous ne savez pas ce que monades, ou si vous ne remarquez pas la façon dont ils sont représentés dans la bibliothèque de Scala, vous ne verrez pas ce que Option
joue avec, et vous ne pouvez pas voir ce que vous êtes passer à côté. Il y a de nombreux avantages à l'utilisation Option
au lieu de nul qui serait remarquable même en l'absence de tout concept de monade (je discute certains d'entre eux dans le « coût de l'option / Certains vs null » scala utilisateur mailing fil de la liste ), mais parler de il l'isolement est un peu comme parler d'un type iterator de mise en œuvre de la liste liée notamment, se demandant pourquoi il est nécessaire, tout en manquant sur le récipient plus général / iterator / interface algorithme. Il y a une interface plus large au travail ici aussi, et Option
fournit un modèle présence et absence de cette interface.
Je pense que la clé se trouve dans la réponse de Synesso: Option est pas surtout utile comme un alias lourd pour nulle, mais comme un objet à part entière qui peut vous aider avec votre logique <. / p>
Le problème est que ce nul est le manque d'un objet. Il n'a pas de méthode qui pourrait vous aider à faire face avec elle (même si en tant que designer de langue que vous pouvez ajouter des listes de plus en plus longues de fonctionnalités à votre langue qui émulent un objet si vous vous sentez vraiment comme ça).
Une chose option peut faire, comme vous l'avez démontré, est d'imiter null; vous devez alors tester la valeur extraordinaire « Aucun » au lieu de la valeur extraordinaire « null ». Si vous oubliez, dans les deux cas, les mauvaises choses vont se produire. Option ne le rendre moins susceptible de se produire par hasard, puisque vous devez taper « get » (qui devrait vous rappeler que peut être null, euh, je veux dire aucun), mais c'est un petit avantage en échange d'un objet enveloppe supplémentaire.
Si l'option commence vraiment à montrer sa puissance Contribue vous traitez avec le concept de I-voulait-quelque chose, mais-je-ne-fait-ont-un.
Considérons certaines choses que vous pouvez faire des choses qui pourraient être nulle.
Peut-être que vous voulez définir une valeur par défaut si vous avez une valeur nulle. Comparons Java et Scala:
String s = (input==null) ? "(undefined)" : input;
val s = input getOrElse "(undefined)"
Au lieu d'un peu lourd: construire, nous avons une méthode qui traite de l'idée de « utiliser une valeur par défaut si je suis nul ». Cela nettoie votre code un peu.
Peut-être que vous voulez créer un nouvel objet que si vous avez une valeur réelle. Comparer:
File f = (filename==null) ? null : new File(filename);
val f = filename map (new File(_))
Scala est légèrement plus courte et évite encore les sources d'erreurs. Pensez alors à l'avantage cumulatif lorsque vous devez les choses de la chaîne ainsi que comme le montrent les exemples de Synesso, Daniel, et paradigmatique.
Il est pas vaste amélioration, mais si vous ajoutez le tout, il vaut bien l'enregistrer partout code très haute performance (où vous voulez éviter même la petite tête de la création de la partie (x) objet encapsuleur).
L'utilisation de la correspondance est pas vraiment utile sur son propre sauf en tant que périphérique pour vous alerter sur l'affaire null / Aucune. Quand il est vraiment utile est quand vous commencez enchaînant, par exemple, si vous avez une liste d'options:
val a = List(Some("Hi"),None,Some("Bye"));
a match {
case List(Some(x),_*) => println("We started with " + x)
case _ => println("Nothing to start with.")
}
Maintenant, vous devez plier les cas Aucun et la liste est vide-cas tous ensemble dans une déclaration à portée de main qui tire exactement la valeur que vous voulez.
Les valeurs NULL de retour ne sont présents que pour la compatibilité avec Java. Vous ne devriez pas les utiliser autrement.
Il est vraiment une question de style de programmation. Utilisation de Java fonctionnelle, ou en écrivant vos propres méthodes d'aide, vous pouvez avoir votre fonctionnalité d'option mais pas abandonner le langage Java:
http://functionaljava.org/examples/#Option.bind
Juste parce que Scala inclut par défaut ne permet pas spécial. La plupart des aspects des langages fonctionnels sont disponibles dans cette bibliothèque et peuvent coexister bien avec tout autre code Java. Tout comme vous pouvez choisir de programmer Scala avec nulls vous pouvez choisir de programmer Java sans eux.
Admettre à l'avance qu'il est une réponse facile, l'option est une monade.
En fait, je partage le doute avec vous. A propos de l'option, il me dérange vraiment que 1) il y a une surcharge de performance, car il y a un Lor de « certains » emballages créés everywehre. 2) Je dois utiliser beaucoup de certains et l'option dans mon code.
voir les avantages et les inconvénients de cette décision de conception de la langue que nous devrions prendre en considération les alternatives. Comme Java ignore tout le problème de la nullabilité, ce n'est pas une alternative. L'alternative réelle fournit le langage de programmation Fantom. Il existe plusieurs types nullables et non-nullables là et?. ?: Opérateurs au lieu de carte / flatMap / getOrElse Scala. Je vois les balles suivantes dans la comparaison:
L'avantage de l'option:
- langage plus simple - pas de constructions de langage supplémentaire requis
- uniforme avec d'autres types monades
L'avantage de Nullable:
- syntaxe plus courte dans les cas typiques
- de meilleures performances (comme vous n'avez pas besoin de créer de nouveaux objets et options lambdas pour carte, flatMap)
Donc il n'y a pas de gagnant évident ici. Et encore une note. Il n'y a pas d'avantage syntaxique principal en utilisant l'option. Vous pouvez définir quelque chose comme:
def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)
Ou utiliser des conversions implicites pour obtenir la syntaxe pritty avec des points.
Le véritable avantage d'avoir des types d'options explicites est que vous êtes en mesure de pas les utiliser dans 98% de tous les lieux, et donc empêcher statiquement exceptions nulles. (Et dans l'autre 2%, le système de type vous rappelle de vérifier correctement lorsque vous les accès en fait.)
Une autre situation où l'option fonctionne, est dans des situations où les types ne sont pas en mesure d'avoir une valeur nulle. Il est impossible de stocker null dans un Int, Float, Double, valeur etc., mais avec une option vous pouvez utiliser le Néant.
En Java, vous devez utiliser les versions en boîte (entier, ...) de ces types.