Pregunta

De vez en cuando, cuando los programadores se quejan de errores nulos / excepciones alguien le pregunta lo que hacemos sin nulo.

tengo una idea básica de la frescura de los tipos de opción, pero no tengo la habilidad del conocimiento o los idiomas que mejor lo expresan. ¿Qué es un gran explicación de la siguiente escrito en un accesible camino hacia el programador medio que podríamos apuntar a esa persona hacia?

  • La inconveniencia de tener referencias / punteros ser anulable por defecto
  • ¿Cómo funcionan los tipos de opciones que incluye estrategias para facilitar la comprobación de los casos nulos tales como
    • coincidencia de patrones y
    • comprensiones monádicos
  • solución alternativa tal como el mensaje de comer nil
  • (otros aspectos que se perdió)
¿Fue útil?

Solución

Creo que el breve resumen de por qué nula es indeseable es que estados sin sentido no debe ser representable .

Supongamos que estoy modelando una puerta. Puede estar en uno de tres estados: abierto, cerrado, pero desbloqueadas, y cerrada con llave. Ahora podría modelar a lo largo de las líneas de

class Door
    private bool isShut
    private bool isLocked

y está claro cómo asignar mis tres estados en estas dos variables booleanas. Pero esto deja un cuarto estado no deseado disponibles: isShut==false && isLocked==true. Debido a que los tipos que he elegido como mi representación admiten este estado, hay que gastar esfuerzo mental para asegurar que la clase nunca se mete en este estado (tal vez mediante la codificación de forma explícita un invariante). Por el contrario, si estuviera usando un lenguaje con tipos de datos algebraica o enumeraciones comprobado que me permite definir

type DoorState =
    | Open | ShutAndUnlocked | ShutAndLocked

entonces podría definir

class Door
    private DoorState state

y no hay más preocupaciones. El sistema de tipos se asegurará de que sólo hay tres estados posibles para una instancia de class Door estar en Esto es lo que los sistemas de tipos son buenos -.. Explícitamente descartando toda una clase de errores en tiempo de compilación

El problema con null es que cada tipo de referencia se pone este estado extra en su espacio que normalmente es indeseado. Una variable string podría ser cualquier secuencia de caracteres, o podría ser este valor adicional null loca que no se asigna en mi dominio del problema. Un objeto Triangle tiene tres Points, que ellos mismos tienen valores X y Y, pero desafortunadamente los Points o el propio Triangle podría ser este valor nulo loco que no tiene sentido para el dominio de la representación gráfica que estoy trabajando. Etc.

Cuando tienen la intención de modelar un valor posiblemente-inexistente, entonces usted debe optar en forma explícita. Si la forma en que la intención de la gente modelo es que cada Person tiene un FirstName y una LastName, pero sólo algunas personas tienen MiddleNames, entonces me gustaría decir algo como

class Person
    private string FirstName
    private Option<string> MiddleName
    private string LastName

donde se supone string aquí para ser un tipo no anulable. Entonces no hay invariantes difíciles de establecer y no hay NullReferenceExceptions inesperados cuando se trata de calcular la longitud del nombre de alguien. El sistema de tipos asegura que cualquier trato con el código MiddleName cuentas de la posibilidad de que sea None, mientras que cualquier trato con el código FirstName puede asumir con seguridad que hay un valor allí.

Así, por ejemplo, utilizando el tipo anterior, podríamos autor tonta esta función:

let TotalNumCharsInPersonsName(p:Person) =
    let middleLen = match p.MiddleName with
                    | None -> 0
                    | Some(s) -> s.Length
    p.FirstName.Length + middleLen + p.LastName.Length

sin preocupaciones. Por el contrario, en un lenguaje con referencias anulables para tipos como cadena, asumiendo

class Person
    private string FirstName
    private string MiddleName
    private string LastName

se termina la autoría de cosas por el estilo

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length + p.MiddleName.Length + p.LastName.Length

, que explota si el objeto Persona de entrada no tiene el invariante de que todo es no nulo, o

let TotalNumCharsInPersonsName(p:Person) =
    (if p.FirstName=null then 0 else p.FirstName.Length)
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + (if p.LastName=null then 0 else p.LastName.Length)

o tal vez

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + p.LastName.Length

suponiendo que asegura p primera / última media están ahí, pero puede ser nulo, o tal vez para que efectúe los que arrojan diferentes tipos de excepciones, o quién sabe qué. Todas estas opciones de implementación y locas cosas en que pensar surgen porque no representable este valor estúpida que no desea o necesita.

Null normalmente añade una complejidad innecesaria. La complejidad es el enemigo de todo el software, y usted debe esforzarse por reducir la complejidad cada vez razonable.

(Nota así que hay más complejidad a incluso estos ejemplos sencillos. Incluso si un FirstName no puede ser null, un string puede representar "" (la cadena vacía), que no es, probablemente, también un nombre de persona que pretendemos modelo. Como tal, incluso con cadenas no anulables, aún podría ser el caso que estamos "representan valores sin sentido". Again, se puede optar por luchar contra esto ya sea a través de invariantes y el código en tiempo de ejecución condicional, o utilizando el sistema de tipos (por ejemplo, tener un tipo NonEmptyString). Este último es quizás poco aconsejable ( "buenos" son a menudo "cerrada" sobre un conjunto de operaciones comunes, por ejemplo, y NonEmptyString no se cierra sobre .SubString(0,0)), pero demuestra más puntos en el espacio de diseño. Al final del día, en cualquier sistema de tipo dado, hay una cierta complejidad que será muy bueno para deshacerse de, y otra complejidad que es intrínsecamente más difícil de eliminar. La clave para este tema es que en casi todos sistema de tipos, el cambio de "referencias anulables por defecto" a "referencias no anulable por defecto" es casi siempre un cambio simple que hace que el sistema de tipo de una mucho mejor en la lucha contra la complejidad y descartar ciertos tipos de errores y estados sin sentido. Por lo que es bastante loco que tantos idiomas siguen repitiendo este error una y otra vez.)

Otros consejos

Lo bueno de los tipos de opción no es que son opcionales. Es que todos los otros tipos no son .

A veces , tenemos que ser capaces de representar una especie de estado "nulo". A veces tenemos que representar una opción "sin valor", así como los otros valores posibles de una variable puede tomar. Así, un lenguaje que de plano no permite que esto va a ser un poco lisiado.

Pero a menudo , que no lo necesita, y permitir como un estado de "nulo" sólo conduce a la ambigüedad y la confusión: cada vez que acceder a una variable de tipo de referencia en .NET, tengo que tener en cuenta que podría ser nula .

A menudo, nunca realmente será nula, ya que las estructuras programador del código para que nunca puede suceder. Sin embargo, el compilador no puede verificar que, y cada vez que lo ve, usted tiene que preguntarse "puede esto sea nula? ¿Es necesario para comprobar si hay datos nulos?"

Lo ideal sería que, en los muchos casos en que nulo no tiene sentido, que no se debe permitir .

Esto es difícil de lograr en .NET, donde casi todo puede ser nulo. Usted tiene que confiar en el autor del código que está llamando a ser 100% disciplinado y coherente, y ha documentado claramente lo que puede y no puede ser nulo, o tiene que ser paranoico y verificación todo .

Sin embargo, si los tipos no son anulables por defecto , entonces no es necesario para comprobar si están o no nulo. Usted sabe que nunca puede ser nula, debido a que las hace cumplir corrector compilador / tipo que para usted.

Y a continuación, sólo tenemos una puerta trasera para los raros casos en que DO necesidad de manejar un estado nulo. A continuación, un tipo de "opción" se puede utilizar. Entonces permitimos nulo en los casos en los que hemos hecho una decisión consciente de que tenemos que ser capaces de representar el caso "sin valor", y en cualquier otro caso, sabemos que el valor nunca será nula.

Como otros han mencionado, en C # o Java, por ejemplo, nula puede significar una de dos cosas:

  1. la variable no está inicializada. Esto debería, idealmente, no suceder. Una variable no debe existen a menos que se inicializa.
  2. la variable contiene algunos datos "opcionales": tiene que ser capaz de representar el caso en que no hay datos . Esto a veces es necesario. Tal vez usted está tratando de encontrar un objeto en una lista, y no saber de antemano si es o no es allí. Entonces tenemos que ser capaces de representar que "no hay ningún objeto fue encontrado".

El segundo significado tiene que ser conservado, pero el primero debe ser eliminado por completo. E incluso el segundo significado no debe ser el valor por defecto. Es algo que puede optar a si y cuando lo necesitamos . Pero cuando no necesitamos algo para ser opcional, queremos que el tipo de corrector a Garantía que nunca va a ser nulo.

Todas las respuestas hasta el momento se centran en qué null es algo malo, y cómo es poco práctico si una lengua puede garantizar que ciertos valores no ser nulo.

A continuación, vaya a sugerir que sería una idea bastante interesante si hace cumplir no nulabilidad para todos valores, que se pueden hacer si se agrega un concepto como Option o Maybe para representar tipos que no siempre puede tener un valor definido. Este es el enfoque adoptado por Haskell.

Es todas las cosas buenas! Pero esto no excluye el uso de tipos explícita anulables / no nulos para lograr el mismo efecto. ¿Por qué, entonces, es la opción sigue siendo una buena cosa? Después de todo, Scala soporta valores con valores nulos (es tiene a, para que pueda trabajar con bibliotecas de Java), pero soportes Options también.

Q. ¿Cuáles son los beneficios más allá de ser capaz de eliminar los nulos de un lenguaje completamente?

A. Composición

Si haces una traducción ingenua de código nulo conscientes

def fullNameLength(p:Person) = {
  val middleLen =
    if (null == p.middleName)
      p.middleName.length
    else
      0
  p.firstName.length + middleLen + p.lastName.length
}

para el código de opción consciente

def fullNameLength(p:Person) = {
  val middleLen = p.middleName match {
    case Some(x) => x.length
    case _ => 0
  }
  p.firstName.length + middleLen + p.lastName.length
}

no hay mucha diferencia! Pero también es un terribles manera de utilizar las opciones de ... Este enfoque es mucho más limpio:

def fullNameLength(p:Person) = {
  val middleLen = p.middleName map {_.length} getOrElse 0
  p.firstName.length + middleLen + p.lastName.length
}

O incluso:

def fullNameLength(p:Person) =       
  p.firstName.length +
  p.middleName.map{length}.getOrElse(0) +
  p.lastName.length

Al iniciar tratar con lista de opciones, se pone aún mejor. Imagine que la lista es en sí misma people opcional:

people flatMap(_ find (_.firstName == "joe")) map (fullNameLength)

¿Cómo funciona?

//convert an Option[List[Person]] to an Option[S]
//where the function f takes a List[Person] and returns an S
people map f

//find a person named "Joe" in a List[Person].
//returns Some[Person], or None if "Joe" isn't in the list
validPeopleList find (_.firstName == "joe")

//returns None if people is None
//Some(None) if people is valid but doesn't contain Joe
//Some[Some[Person]] if Joe is found
people map (_ find (_.firstName == "joe")) 

//flatten it to return None if people is None or Joe isn't found
//Some[Person] if Joe is found
people flatMap (_ find (_.firstName == "joe")) 

//return Some(length) if the list isn't None and Joe is found
//otherwise return None
people flatMap (_ find (_.firstName == "joe")) map (fullNameLength)

El código correspondiente con cheques nulos (o incluso Elvis:? Operadores) serían dolorosamente largo. El verdadero truco aquí es la operación flatMap, que permite la comprensión anidada de Opciones y colecciones de manera que los valores anulables nunca pueden lograr.

Dado que la gente parece faltar es:. null es ambiguo

fecha de nacimiento de Alice es null. ¿Qué significa?

fecha de la muerte de Bob es null. ¿Qué significa eso?

Una interpretación "razonable" podría ser que existe la fecha de nacimiento de Alice, pero se desconoce, mientras que la muerte la fecha-of-Bob no existe (Bob aún está vivo). Pero ¿por qué hemos llegado a diferentes respuestas?


Otro problema:. null es un caso borde

  • ¿Es null = null?
  • ¿Es nan = nan?
  • ¿Es inf = inf?
  • ¿Es +0 = -0?
  • ¿Es +0/0 = -0/0?

Las respuestas son general "sí", "no", "sí", "sí", "no", "sí", respectivamente. Locos "matemáticos" "nulidad" llamada Nan y dicen que compara igual a sí mismo. nulos trata de SQL como no es igual a nada (por lo que se comportan como NaNs). Uno se pregunta qué sucede cuando se intenta almacenar ± 8, ± 0 y NaNs en la misma columna de la base de datos (hay 2 53 NaNs, la mitad de los cuales son "negativa").

Para empeorar las cosas, las bases de datos difieren en la forma en que NULL tratamiento, y la mayoría de ellos no son consistentes (véase Manejo de NULL en SQLite para una visión general). Es bastante horrible.


Y ahora la historia obligatoria:

Recientemente he diseñado un (sqlite3) tabla de base de datos con cinco columnas a NOT NULL, b, id_a, id_b NOT NULL, timestamp. Debido a que es un esquema genérico diseñado para resolver un problema genérico para aplicaciones bastante arbitrarias, hay dos restricciones únicas:

UNIQUE(a, b, id_a)
UNIQUE(a, b, id_b)

id_a sólo existe para la compatibilidad con un diseño de aplicación existente (en parte porque no he llegado con una solución mejor), y no se utiliza en la nueva aplicación. Debido a la forma NULL funciona en SQL, puedo insertar (1, 2, NULL, 3, t) y (1, 2, NULL, 4, t) y no viola la primera restricción de unicidad (porque (1, 2, NULL) != (1, 2, NULL)).

Estos trabajos específicamente como consecuencia de la forma en NULL trabaja en una restricción de unicidad en la mayoría de bases de datos (presumiblemente, por lo que es más fácil de modelar situaciones del "mundo real", por ejemplo, hay dos personas pueden tener el mismo número de seguro social, pero no todas las personas tienen una ).


Fwiw, sin primero invocación indefinido comportamiento, C ++ referencias no pueden "punto a" nulo, y no es posible construir una clase con variables miembro de referencia sin inicializar (si se produce una excepción, la construcción no).

Nota al margen: En ocasiones es posible que desee punteros mutuamente excluyentes (es decir, sólo uno de ellos puede ser no nulo), por ejemplo, en un hipotético type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed IOS. En lugar de ello, me veo obligado a hacer cosas como assert((bool)actionSheet + (bool)alertView == 1).

La inconveniencia de tener que tienen referencias / punteros ser anulable por defecto.

No creo que este es el principal problema con valores nulos, el principal problema con nulos es que pueden significar dos cosas:

  1. La referencia / puntero no está inicializado: el problema aquí es la misma que la mutabilidad en general. Por un lado, hace que sea más difícil de analizar su código.
  2. La variable siendo nula en realidad significa algo: este es el caso de los tipos de opciones realmente formalizar
  3. .

Idiomas que tipos opción de soporte típicamente también prohíben o desalentar el uso de variables no inicializadas también.

¿Cómo funcionan los tipos de opciones que incluye estrategias para facilitar la comprobación de los casos nulos tales como la coincidencia de patrones.

Con el fin de ser eficaces, los tipos de opción necesita ser apoyado directamente en el lenguaje. De lo contrario, se necesita una gran cantidad de código de la caldera de la placa para simular ellos. Reconocimiento de patrones y el tipo de inferencia son dos características claves del lenguaje que hacen los tipos de opciones fáciles de trabajar. Por ejemplo:

En F #:

//first we create the option list, and then filter out all None Option types and 
//map all Some Option types to their values.  See how type-inference shines.
let optionList = [Some(1); Some(2); None; Some(3); None]
optionList |> List.choose id //evaluates to [1;2;3]

//here is a simple pattern-matching example
//which prints "1;2;None;3;None;".
//notice how value is extracted from op during the match
optionList 
|> List.iter (function Some(value) -> printf "%i;" value | None -> printf "None;")

Sin embargo, en un lenguaje como Java, sin apoyo directo para este tipo de opción, tendríamos algo como:

//here we perform the same filter/map operation as in the F# example.
List<Option<Integer>> optionList = Arrays.asList(new Some<Integer>(1),new Some<Integer>(2),new None<Integer>(),new Some<Integer>(3),new None<Integer>());
List<Integer> filteredList = new ArrayList<Integer>();
for(Option<Integer> op : list)
    if(op instanceof Some)
        filteredList.add(((Some<Integer>)op).getValue());

solución alternativa tal como el mensaje de comer nil

de Objective-C "mensaje de comer nulo" no es tanto una solución como un intento de aligerar el dolor de cabeza de comprobación nula. Básicamente, en lugar de lanzar una excepción de tiempo de ejecución al intentar invocar un método de un objeto nulo, la expresión en lugar evalúa como null en sí. La suspensión de la incredulidad, es como si cada método de instancia comienza con if (this == null) return null;. Pero entonces no hay pérdida de información: no se sabe si el método devuelve nulo, ya que es el valor de retorno válido, o porque el objeto es en realidad nulo. Es muy parecido a tragar la excepción, y no hace ningún progreso frente a los problemas con nula esbozados antes.

Asamblea nos trajo direcciones también conocidos como punteros sin tipo. C les asigna directamente como punteros tecleados pero introdujo nula de Algol como un valor de puntero único, compatible con todos los punteros mecanografiadas. El gran problema con nulo en C es que, dado que cada puntero puede ser nulo, nunca se puede utilizar un puntero de forma segura sin una comprobación manual.

En lenguajes de alto nivel, que tienen nula es incómoda, ya que realmente transmite dos nociones distintas:

  • Decir que algo es Indefinido .
  • Decir que algo es opcional .

Tener variables no definidas es bastante inútil, y los rendimientos a un comportamiento indefinido siempre que se produzcan. Supongo que todo el mundo estará de acuerdo en que tienen las cosas no definidas deben evitarse a toda costa.

El segundo caso es opcionalidad y es mejor proporcionado explícitamente, por ejemplo con un tipo de opción .


Vamos a decir que estamos en una empresa de transporte y tenemos que crear una aplicación para ayudar a crear una programación para nuestros conductores. Para cada controlador, almacenamos un par de informaciones tales como: los permisos de conducir que tienen y el número de teléfono para llamar en caso de emergencia

.

En C que podría tener:

struct PhoneNumber { ... };
struct MotorbikeLicence { ... };
struct CarLicence { ... };
struct TruckLicence { ... };

struct Driver {
  char name[32]; /* Null terminated */
  struct PhoneNumber * emergency_phone_number;
  struct MotorbikeLicence * motorbike_licence;
  struct CarLicence * car_licence;
  struct TruckLicence * truck_licence;
};

Como se observa, en los tratamientos sobre nuestra lista de controladores que tendremos que comprobar si hay punteros nulos. El compilador no le ayudará, la seguridad del programa se basa en sus hombros.

En OCaml, el mismo código se vería así:

type phone_number = { ... }
type motorbike_licence = { ... }
type car_licence = { ... }
type truck_licence = { ... }

type driver = {
  name: string;
  emergency_phone_number: phone_number option;
  motorbike_licence: motorbike_licence option;
  car_licence: car_licence option;
  truck_licence: truck_licence option;
}

Ahora vamos a decir que queremos imprimir los nombres de todos los conductores junto con sus números de licencia de camiones.

En C:

#include <stdio.h>

void print_driver_with_truck_licence_number(struct Driver * driver) {
  /* Check may be redundant but better be safe than sorry */
  if (driver != NULL) {
    printf("driver %s has ", driver->name);
    if (driver->truck_licence != NULL) {
      printf("truck licence %04d-%04d-%08d\n",
        driver->truck_licence->area_code
        driver->truck_licence->year
        driver->truck_licence->num_in_year);
    } else {
      printf("no truck licence\n");
    }
  }
}

void print_drivers_with_truck_licence_numbers(struct Driver ** drivers, int nb) {
  if (drivers != NULL && nb >= 0) {
    int i;
    for (i = 0; i < nb; ++i) {
      struct Driver * driver = drivers[i];
      if (driver) {
        print_driver_with_truck_licence_number(driver);
      } else {
        /* Huh ? We got a null inside the array, meaning it probably got
           corrupt somehow, what do we do ? Ignore ? Assert ? */
      }
    }
  } else {
    /* Caller provided us with erroneous input, what do we do ?
       Ignore ? Assert ? */
  }
}

En OCaml que sería:

open Printf

(* Here we are guaranteed to have a driver instance *)
let print_driver_with_truck_licence_number driver =
  printf "driver %s has " driver.name;
  match driver.truck_licence with
    | None ->
        printf "no truck licence\n"
    | Some licence ->
        (* Here we are guaranteed to have a licence *)
        printf "truck licence %04d-%04d-%08d\n"
          licence.area_code
          licence.year
          licence.num_in_year

(* Here we are guaranteed to have a valid list of drivers *)
let print_drivers_with_truck_licence_numbers drivers =
  List.iter print_driver_with_truck_licence_number drivers

Como se puede ver en este ejemplo trivial, no hay nada complicado en la versión segura:

  • Es más concisa.
  • Usted consigue mucho mejores garantías y no se requiere ninguna verificación nula en absoluto.
  • El compilador garantiza que usted trató correctamente con la opción

Mientras que en C, sólo podría haber olvidado un cheque nulo y la pluma ...

Nota: estos ejemplos de código en las que no compilan, pero espero que tienes las ideas

.

Microsoft Research tiene un proyecto interesante lo llama

Spec #

Es una extensión C # con el tipo no nulo y algún mecanismo para ver sus objetos contra no ser nulo , aunque, en mi humilde opinión, la aplicación de la diseño por contrato principio puede ser más apropiado y más útil para muchas situaciones molestas causadas por referencias nulas.

Viniendo de fondo .NET, siempre pensé que tenía un punto nulo, su utilidad. Hasta que llegué a conocer de estructuras y lo fácil que estaba trabajando con ellos para evitar una gran cantidad de código repetitivo. de Tony Hoare hablando en QCon Londres en 2009, disculpó por la invención de la referencia nula. Para citar a él:

Me llamo mi mil millones de dólares error. Fue la invención de la nula referencia en el año 1965. En ese momento, estaba diseñando la primera Tipo de sistema integral para las referencias de un objeto orientado idioma (ALGOL W). Mi objetivo era asegurar que todo uso de referencias debe estar absolutamente seguro, con control realizadas automáticamente por el compilador. Pero no pude resistir la tentación de poner en un nulo referencia, simplemente porque era tan fácil de implementar. Esto ha llevado a innumerables errores, vulnerabilidades y fallos del sistema, que tienen probablemente causado mil millones de dólares de dolor y daño en los últimos cuarenta años. En los últimos años, una serie de programas como prefijo y analizadores PREfast en Microsoft se han utilizado para verificar las referencias, y dar advertencias si hay un riesgo que puede ser no nulo. Más reciente lenguajes de programación como Spec # han introducido declaraciones de referencias no nulos. Esta es la solución, que rechacé en 1965.

Vea esta pregunta demasiado a los programadores

Robert Nystrom ofrece un buen artículo aquí:

http://journal.stuffwithstuff.com / 2010/08/23 / nulo-nulo-quizás-y-nada /

describe su proceso de pensamiento cuando se añade soporte para la ausencia y la falta de su Urraca lenguaje de programación.

He siempre miraba Null (o nil) como siendo la ausencia de un valor .

A veces desea que este, a veces no lo hace. Depende del dominio que se está trabajando. Si la ausencia es significativa: sin nombre, a continuación, su aplicación puede actuar en consecuencia. Por otro lado, si el valor nulo no debería estar ahí: El primer nombre es nulo, entonces el desarrollador obtiene el proverbial llamada telefónica 2 a.m..

También he visto código sobrecargado y sobre-complicado con los cheques para nulo. Para mí esto significa una de dos cosas:
a) un error más arriba en el árbol de aplicaciones
b) mala / incompleta diseño

En el lado positivo - Null es probablemente una de las nociones más útiles para comprobar si algo está ausente, y las lenguas sin el concepto de null se endup complicar las cosas cuando es el momento de hacer la validación de datos. En este caso, si una nueva variable no se ha inicializado, dijo languagues por lo general establecer las variables en una cadena vacía, 0, o una colección vacía. Sin embargo, si una cadena vacía o 0 o colección vacía son valores válidos para su aplicación - entonces usted tiene un problema.

A veces esto eludido por inventar / valores extraños especiales para los campos de representar un estado no inicializado. Pero entonces, ¿qué sucede cuando el valor especial es introducido por un usuario bien intencionado? Y no vamos a entrar en el lío que esto hará que las rutinas de validación de datos. Si el idioma apoyó el concepto nula todas las preocupaciones desaparecerían.

Vector idiomas a veces puede salirse con no tener un valor nulo.

El vector vacío sirve como un nulo mecanografiada en este caso.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top