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?

Était-ce utile?

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é fathersMothersSisterto 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'utiliser Option 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 sur Option (ou   Box, 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))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 Noneby 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.

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:

  1. langage plus simple - pas de constructions de langage supplémentaire requis
  2. uniforme avec d'autres types monades

L'avantage de Nullable:

  1. syntaxe plus courte dans les cas typiques
  2. 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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top