Quel est le point de l'option de classe[T]?


Je ne suis pas capable de comprendre le point de Option[T] classe dans Scala. Je veux dire, je ne peux pas voir d'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 getPerson1 retourne null, alors l'appel de display sur la première ligne de main est voué à l'échec avec NPE. De même, si getPerson2 renvoie None, l'appel display échouera à nouveau avec une erreur similaire.

Si oui, alors pourquoi Scala complique-t-elle les choses en introduisant un nouveau value wrapper (Option[T]) au lieu de suivre une approche simple utilisée en Java?

Mise à JOUR:

J'ai édité mon code selon la suggestion de @Mitch. Je ne suis toujours pas en mesure de voir un avantage particulier de Option[T]. Je dois tester l'exceptionnel null ou None dans les deux cas. :(

Si j'ai bien compris à partir de la réponse de @Michael, le seul avantage de Option[T] est qu'il indique explicitement au programmeur que cette méthode pourrait retourner Aucun? Est-ce la seule raison derrière ce choix de conception?

Author: Community, 2010-01-17

18 answers

, Vous aurez le point de Option mieux si vous vous forcez à ne jamais, jamais, get. C'est parce que get est l'équivalent de "ok, renvoyez-moi à null-land".

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 ne 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 prod vous, mais si vous voulez retomber à aucun filet de sécurité, c'est votre choix.


Vous l'avez cloué ici:

Est le seul avantage de l'option[T] est qu'il indique explicitement à l' programmeur que cette méthode pourrait retour Aucun?

Sauf pour le "seulement". Mais permettez-moi de réaffirmer cela d'une autre manière: l'avantage principal de Option[T] sur T est la sécurité de type. Cela garantit que vous n'enverrez pas de méthode T à un objet qui peut ne pas exister, car le compilateur ne vous laisse pas.

Vous avez dit que vous deviez tester la nullabilité dans les deux cas, mais si vous oubliez or ou ne savez pas you vous devez vérifier null, le compilateur vous le dira-t-il? Ou vos utilisateurs?

Bien sûr, en raison de son interopérabilité avec Java, Scala autorise les null tout comme Java. Donc, si vous utilisez des bibliothèques Java, si vous utilisez des bibliothèques Scala mal écrites, ou si vous utilisez des bibliothèques Scala mal écrites personal, vous devrez toujours gérer null pointeur.

Deux Autres avantages importants de Option je peux penser sont:

  • Documentation: une signature de type de méthode vous dira si un objet est toujours retourné ou non.

  • Composabilité monadique.

Ce dernier prend beaucoup plus de temps à apprécier pleinement, et il n'est pas bien adapté aux exemples simples, car il ne montre sa force que sur du code complexe. Donc, je vais donner un exemple ci-dessous, mais je suis bien conscient que cela ne signifiera guère tout sauf pour les gens qui l'ont déjà.

for {
  person <- getUsers
  email <- person.getEmail // Assuming getEmail returns Option[String]
} yield (person, email)
 71
Author: Daniel C. Sobral, 2015-03-03 15:28:53

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 la fonction map, nous permet d'enchaîner les opérations 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 voulons 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 voudrions-nous 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 cela jette un peu de lumière sur la façon dont les options peuvent rendre la vie un peu plus facile.

 31
Author: Synesso, 2010-01-17 16:47:58

La différence est subtile. Gardez à l'esprit que pour être vraiment une fonction, il doit renvoyer une valeur - null n'est pas vraiment considéré comme une "valeur de retour normale" en ce sens, plus un type inférieur/rien.

, Mais, dans un sens pratique, lorsque vous appelez une fonction qui retourne éventuellement quelque chose, vous feriez:

getPerson2 match {
   case Some(person) => //handle a person
   case None => //handle nothing 
}

Accordé, vous pouvez faire quelque chose de similaire avec null-mais cela rend la sémantique de l'appel getPerson2 évidente en vertu du fait qu'elle renvoie Option[Person] (a bonne chose pratique, autre que de compter sur quelqu'un qui lit le document et obtient un NPE parce qu'il ne lit pas le document).

Je vais essayer de trouver un programmeur fonctionnel qui peut donner une réponse plus stricte que moi.

 21
Author: Michael Neale, 2014-09-01 16:18:01

Pour moi, les options sont vraiment intéressantes lorsqu'elles sont gérées avec la syntaxe for comprehension. En 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'assignation sont None, le fathersMothersSister sera None, mais pas de NullPointerException sera soulevée. Vous pouvez alors passer en toute sécurité fathersMothersSisterà une fonction en prenant des paramètres d'option sans vous inquiéter. donc, vous ne vérifiez pas null et vous ne vous souciez pas des exceptions. Comparez cela à la version java présentée dans l'exemplesynesso .

 15
Author: paradigmatic, 2010-01-17 21:27:05

Vous avez des capacités de composition assez puissantes avec l'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") )
 9
Author: Viktor Klang, 2010-01-18 09:35:46

Peut-être que quelqu'un d'autre l'a souligné, mais je ne l'ai pas vu:

Un avantage de la correspondance de modèle avec l'Option[T] contre la vérification null est que l'Option est une classe scellée, donc le compilateur Scala émettra un avertissement si vous négligez de coder le cas Some ou le cas None. Il existe un indicateur de compilateur pour le compilateur qui transformera les avertissements en erreurs. Il est donc possible d'empêcher l'échec de gérer le cas "n'existe pas" au moment de la compilation plutôt qu'au moment de l'exécution. C'est une énorme avantage par rapport à l'utilisation de la valeur null.

 8
Author: Paul Snively, 2010-08-01 23:37:18

Ce n'est pas là pour aider à éviter une vérification nulle, c'est là pour forcer une vérification nulle. Le point devient clair lorsque votre classe a 10 champs, dont deux peuvent être nuls. Et votre système a 50 autres classes similaires. Dans le monde Java, vous essayez d'empêcher les NPE sur ces champs en utilisant une combinaison de puissance mentale, de convention de nommage ou peut-être même d'annotations. Et chaque développement Java échoue à un degré significatif. La classe d'options rend non seulement les valeurs" nullables " visuellement claires pour tout les développeurs essayant de comprendre le code, mais permet au compilateur d'appliquer ce contrat précédemment tacite.

 7
Author: Adam Rabung, 2010-01-17 15:02:49

[ copié à partir de ce commentaire par Daniel Spiewak ]

Si la seule façon d'utiliser Option était pour correspondre au modèle afin d'obtenir valeurs, alors oui, je suis d'accord qu'il ne s'améliore pas du tout sur null. Cependant, il vous manque une classe* énorme * de ses fonctionnalités. La seule raison impérieuse d'utiliser Option est si vous utilisez son ordre supérieur les fonctions de l'utilitaire. Effectivement, vous besoin d'utiliser sa nature monadique. Par exemple (en supposant un certain montant de la taille de l'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

Là, n'était-ce pas chouette? Nous pouvons effectivement faire 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 ne sommes *jamais* vérifier explicitement null, None ou l'un de ses semblables. Le point de l'ensemble de L'Option est d'éviter de tout ça vérification. Vous ne faites que des calculs de chaîne le long et descendre la ligne jusqu'à ce que vous *vraiment* besoin d'obtenir une valeur. À ce point, vous pouvez décider si ou pas que vous voulez faire vérification explicite (ce que vous devriez ne jamais avoir à faire), fournir une valeur par défaut, lancer un exception, etc.

Je n'ai jamais, jamais fait de correspondance explicite contre Option, et je connais beaucoup de autres développeurs Scala qui sont dans le même bateau. David Pollak a mentionné à moi l'autre jour qu'il utilise une telle correspondance explicite sur Option (ou Box, dans le cas de l'Ascenseur) comme un signe que le développeur qui a écrit le code ne comprend pas complètement la langue et son bibliothèque standard.

Je ne veux pas être un marteau de troll, mais vous avez vraiment besoin de regarder comment les fonctionnalités linguistiques sont * réellement * utilisées en pratique avant de les bash comme inutile. Je suis absolument d'accord que Option est tout à fait décomposable comme * vous* utilisé, mais vous ne l'utilisez pas l' façon dont il a été conçu.

 6
Author: missingfaktor, 2017-05-23 10:31:31

Un point que personne d'autre ici ne semble avoir soulevé est que même si vous pouvez avoir une référence nulle, il y a une distinction introduite par Option.

, ce Qui est, vous pouvez avoir Option[Option[A]], ce qui serait habité par des None, Some(None) et Some(Some(a))a est l'un des habitants habituels de A. Cela signifie que si vous avez une sorte de conteneur et que vous voulez pouvoir y stocker des pointeurs nuls et les sortir, vous devez renvoyer une valeur booléenne supplémentaire pour savoir si vous avez réellement un de la valeur. Des verrues comme celle-ci abondent dans les API des conteneurs java et certaines variantes sans verrouillage ne peuvent même pas les fournir.

null est une construction unique, elle ne compose pas avec elle-même, elle n'est disponible que pour les types de référence, et elle vous oblige à raisonner de manière non totale.

Par exemple, lorsque vous cochez

if (x == null) ...
else x.foo()

Vous devez porter dans votre tête tout au long de la branche else que x != null et que cela a déjà été vérifié. Cependant, lors de l'utilisation de quelque chose comme option

x match {
case None => ...
case Some(y) => y.foo
}

Vous savez y n'est pas None par construction -- et vous sauriez que ce n'était pas nullnon plus, sans l'erreur de Hoare d'un milliard de dollars .

 6
Author: Edward KMETT, 2011-03-26 01:10:47

Option [T] est une monade, ce qui est vraiment utile lorsque vous utilisez des fonctions d'ordre élevé pour manipuler des valeurs.

Je vous suggère de lire les articles énumérés ci-dessous, ce sont de très bons articles qui vous montrent pourquoi l'option[T] est utile et comment peut-elle être utilisée de manière fonctionnelle.

 3
Author: Brian Hsu, 2010-11-26 23:34:17

En ajoutant au teaser de Randall d'une réponse , comprendre pourquoi l'absence potentielle d'une valeur est représentée par Option nécessite de comprendre ce que Option partage avec de nombreux autres types de Scala-en particulier, les types modélisant les monades. Si l'on représente l'absence d'une valeur avec null, cette distinction absence-présence ne peut pas participer aux contrats partagés par les autres types monadiques.

Si vous ne savez pas ce que sont les monades, ou si vous ne remarquez pas comment elles sont représentées dans La bibliothèque de Scala, vous ne verrez pas ce que Option joue avec, et vous ne pouvez pas voir ce que vous manquez. Il y a de nombreux avantages à utiliser Option au lieu de null qui seraient remarquables même en l'absence de tout concept de monade (j'en discute certains dans le "Coût de l'option / Certains vs null" scala-user thread de liste de diffusion ici), mais en parler l'isolement est un peu comme parler du type, tout en manquant l'interface plus générale conteneur/itérateur/algorithme. Il y a une interface plus large à l'œuvre ici aussi, et Option fournit un modèle de présence et d'absence de cette interface.

 3
Author: seh, 2017-05-23 10:31:31

Je pense que la clé se trouve dans la réponse de Synesso: L'option est pas principalement utile en tant qu'alias encombrant pour null, mais en tant qu'objet à part entière qui peut ensuite vous aider avec votre logique.

Le problème avec null est que c'est le manque d'un objet. Il n'a pas de méthodes qui pourraient vous aider à y faire face (bien qu'en tant que concepteur de langue, vous pouvez ajouter des listes de plus en plus longues de fonctionnalités à votre langue qui émulent un objet si vous en avez vraiment envie).

Un la chose que l'option peut faire, comme vous l'avez démontré, est d'émuler null; vous devez ensuite tester la valeur extraordinaire "None" au lieu de la valeur extraordinaire "null". Si vous oubliez, dans les deux cas, de mauvaises choses se produiront. L'option le rend moins susceptible de se produire par accident, car vous devez taper "get" (ce qui devrait vous rappeler qu'il pourrait être null, euh, je veux dire Aucun), mais c'est un petit avantage en échange d'un objet wrapper supplémentaire.

Où l'option commence vraiment à montrer son pouvoir vous aide à gérer le concept de Je-voulais-quelque chose-mais-je-n'en-ai-pas-en-fait-un.

Considérons certaines choses que vous pourriez vouloir faire avec des choses qui pourraient être nulles.

Peut-être que vous voulez définir une valeur par défaut si vous avez un null. Comparons Java et Scala:

String s = (input==null) ? "(undefined)" : input;
val s = input getOrElse "(undefined)"

À la place d'un quelque peu encombrant ?: construire nous avons une méthode qui traite de l'idée de "l'utilisation d'une valeur par défaut si je suis nulle". Cela nettoie un peu votre code.

Peut-être que vous vous voulez créer un nouvel objet uniquement 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 court et évite à nouveau les sources d'erreur. Considérez ensuite l'avantage cumulatif lorsque vous devez enchaîner les choses comme le montrent les exemples de Synesso, Daniel et paradigmatic.

Ce n'est pas une amélioration vaste, mais si vous additionnez tout, cela vaut bien la peine d'enregistrer partout du code très performant (où vous voulez éviter même la petite surcharge de création du Certains (x) wrapper objet).

L'utilisation de la correspondance n'est pas vraiment utile en soi, sauf en tant que périphérique pour vous alerter sur le cas null/None. Quand il est vraiment utile, c'est quand vous commencez une chaîne, 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 pouvez plier les cas None et les cas List-is-empty tous ensemble dans une instruction pratique qui tire exactement la valeur que vous voulez.

 2
Author: Rex Kerr, 2010-01-17 16:56:44

C'est vraiment une question de style de programmation. En utilisant Java fonctionnel, ou en écrivant vos propres méthodes d'aide, vous pouvez avoir votre fonctionnalité d'option mais ne pas abandonner le langage Java:

Http://functionaljava.org/examples/#Option.bind

Ce n'est pas parce que Scala l'inclut par défaut que cela le rend spécial. La plupart des aspects des langages fonctionnels sont disponibles dans cette bibliothèque et peuvent coexister bien avec d'autres codes Java. Tout comme vous pouvez choisir de programmer Scala avec nulls vous pouvez choisir de programmer Java sans eux.

 1
Author: Sam Pullara, 2010-07-30 05:11:18

Les valeurs de retour Null ne sont présentes que pour la compatibilité avec Java. Vous ne devriez pas les utiliser autrement.

 1
Author: ryeguy, 2013-05-24 20:31:02

Admettant à l'avance que c'est une réponse glib, Option est une monade.

 0
Author: Randall Schulz, 2010-01-16 23:44:46

En fait, je partage le doute avec vous. A propos de l'option cela me dérange vraiment que 1) il y a une surcharge de performance, car il y a un lor de "Certains" wrappers créés everywehre. 2) Je dois en utiliser beaucoup et Option dans mon code.

Donc, pour voir les avantages et les inconvénients de cette décision de conception linguistique, nous devrions prendre en considération les alternatives. Comme Java ignore simplement le problème de la nullabilité, ce n'est pas une alternative. L'alternative réelle fournit le langage de programmation Fantom. Il existe des types nullable et non nullable là-bas et ?. ?: opérateurs au lieu de la carte/flatMap/getOrElse de Scala. Je vois les puces suivantes dans la comparaison:

Avantage de l'option:

  1. langage plus simple - aucune construction de langage supplémentaire requise
  2. uniforme avec d'autres types monadiques

Avantage nullable:

  1. syntaxe plus courte dans les cas typiques
  2. meilleures performances (car vous n'avez pas besoin de créer de nouveaux objets Option et lambdas pour map, flatMap)

Il n'y a donc pas de gagnant évident ici. Et une remarque plus. Il n'y a pas d'avantage syntaxique principal pour utiliser Option. Vous pouvez définir quelque chose comme:

def nullableMap[T](value: T, f: T => T) = if (value == null) null else f(value)

Ou utilisez des conversions implicites pour obtenir une syntaxe pritty avec des points.

 0
Author: Alexey, 2010-01-17 22:13:04

Le véritable avantage d'avoir des types d'options explicites est que vous pouvez pas les utiliser dans 98% de tous les endroits, et donc exclure statiquement les exceptions null. (Et dans les 2% restants, le système de type vous rappelle de vérifier correctement lorsque vous y accédez réellement.)

 0
Author: Andreas Rossberg, 2012-12-18 19:10:43

Une autre situation où l'option fonctionne, est dans des situations où les types ne peuvent pas avoir une valeur null. Il n'est pas possible de stocker null dans un Int, Float, Double, etc. valeur, mais avec une Option, vous pouvez utiliser le Néant.

En Java, vous devrez utiliser les versions en boîte (Integer, ...) de ces types.

 -3
Author: Arne, 2013-05-24 20:37:33