He estado leyendo sobre la (des) conveniencia de tener en null
lugar de (por ejemplo) Maybe
. Después de leer este artículo , estoy convencido de que sería mucho mejor usarMaybe
(o algo similar). Sin embargo, me sorprende ver que todos los lenguajes de programación imperativos u orientados a objetos "bien conocidos" todavía se usan null
(lo que permite el acceso sin control a tipos que pueden representar un valor de "nada"), y eso Maybe
se usa principalmente en lenguajes de programación funcionales.
Como ejemplo, mira el siguiente código C #:
void doSomething(string username)
{
// Check that username is not null
// Do something
}
Algo huele mal aquí ... ¿Por qué deberíamos verificar si el argumento es nulo? ¿No deberíamos suponer que cada variable contiene una referencia a un objeto? Como puede ver, el problema es que, por definición, casi todas las variables pueden contener una referencia nula. ¿Qué pasaría si pudiéramos decidir qué variables son "anulables" y cuáles no? Eso nos ahorraría mucho esfuerzo al depurar y buscar una "NullReferenceException". Imagine que, por defecto, ningún tipo podría contener una referencia nula . En lugar de eso, declararía explícitamente que una variable puede contener una referencia nula , solo si realmente la necesita. Esa es la idea detrás de Quizás. Si tiene una función que en algunos casos falla (por ejemplo, división por cero), puede devolver unMaybe<int>
, declarando explícitamente que el resultado puede ser un int, ¡pero también nada! Esta es una razón para preferir Quizás en lugar de nulo. Si está interesado en más ejemplos, le sugiero que lea este artículo .
Los hechos son que, a pesar de las desventajas de hacer que la mayoría de los tipos sean anulables por defecto, la mayoría de los lenguajes de programación OO realmente lo hacen. Por eso me pregunto sobre:
- ¿Qué tipo de argumentos tendrías que implementar
null
en tu lenguaje de programación en lugar deMaybe
? ¿Hay alguna razón o es solo "equipaje histórico"?
Asegúrese de comprender la diferencia entre nulo y Quizás antes de responder esta pregunta.
null
existen o no su concepto (IIRC Haskell es un ejemplo de ello).null
s en ellos durante mucho tiempo. No es fácil dejar eso.Respuestas:
Creo que es principalmente equipaje histórico.
El lenguaje más prominente y antiguo con
null
C y C ++. Pero aquí,null
tiene sentido. Los punteros siguen siendo un concepto bastante numérico y de bajo nivel. Y cómo alguien más dijo, en la mentalidad de los programadores de C y C ++, tener que decir explícitamente que el puntero puede sernull
no tiene sentido.En segundo lugar viene Java. Teniendo en cuenta que los desarrolladores de Java estaban tratando de acercarse a C ++, para que pudieran hacer la transición de C ++ a Java más simple, probablemente no querían meterse con ese concepto central del lenguaje. Además, la implementación explícita
null
requeriría mucho más esfuerzo, ya que debe verificar si la referencia no nula se establece correctamente después de la inicialización.Todos los demás lenguajes son iguales a Java. Por lo general, copian la forma en que C ++ o Java lo hacen, y teniendo en cuenta cómo es el concepto central de implícito
null
de los tipos de referencia, se vuelve realmente difícil diseñar un lenguaje que esté usando explícitonull
.fuente
null
no sería un gran cambio, creo.std::string
No puede sernull
.int&
No puede sernull
. Unaint*
lata, y C ++ permite el acceso sin control a ella por dos razones: 1. porque C lo hizo y 2. porque se supone que debes entender lo que estás haciendo cuando usas punteros sin formato en C ++.En realidad,
null
es una gran idea. Dado un puntero, queremos designar que este puntero no hace referencia a un valor válido. Por lo tanto, tomamos una ubicación de memoria, la declaramos inválida y nos apegamos a esa convención (una convención que a veces se aplica con segfaults). Ahora, cada vez que tengo un puntero, puedo verificar si contieneNothing
(ptr == null
) oSome(value)
(ptr != null
,value = *ptr
). Quiero que entiendas que esto es equivalente a unMaybe
tipo.Los problemas con esto son:
En muchos idiomas, el sistema de tipos no ayuda aquí para garantizar una referencia no nula.
Este es un bagaje histórico, ya que muchos lenguajes imperativos convencionales o OOP solo han tenido avances incrementales en sus sistemas de tipo en comparación con sus predecesores. Los pequeños cambios tienen la ventaja de que los nuevos idiomas son más fáciles de aprender. C # es un lenguaje convencional que ha introducido herramientas de nivel de lenguaje para manejar mejor los valores nulos.
Los diseñadores de API pueden regresar
null
en caso de falla, pero no una referencia a lo real en sí mismo en el éxito. A menudo, la cosa (sin una referencia) se devuelve directamente. Este aplanamiento de un nivel de puntero hace que sea imposible usarlonull
como valor.Esto es solo pereza por parte del diseñador y no se puede evitar sin imponer un anidamiento adecuado con un sistema de tipo adecuado. Algunas personas también pueden tratar de justificar esto con consideraciones de rendimiento o con la existencia de comprobaciones opcionales (una colección puede devolver
null
o el artículo en sí, pero también proporcionar uncontains
método).En Haskell hay una vista clara sobre el
Maybe
tipo como una mónada. Esto facilita componer transformaciones en el valor contenido.Por otro lado, los lenguajes de bajo nivel como C apenas tratan las matrices como un tipo separado, por lo que no estoy seguro de lo que esperamos. En los lenguajes OOP con polimorfismo parametrizado, un
Maybe
tipo verificado en tiempo de ejecución es bastante trivial de implementar.fuente
null
menos de lo que hicieron en el pasado?null
s " - Estoy hablando de losNullable
tipos y el??
operador predeterminado. No resuelve el problema en este momento en presencia de código heredado, pero es un paso hacia un futuro mejor.Nullable
.Entiendo que
null
era una construcción necesaria para abstraer los lenguajes de programación fuera del ensamblaje. 1 Los programadores necesitaban la capacidad de indicar que un puntero o valor de registro eranot a valid value
y senull
convirtió en el término común para ese significado.Reforzando el punto que
null
es solo una convención para representar un concepto, el valor real paranull
poder / puede variar según el lenguaje de programación y la plataforma.Si estuviera diseñando un nuevo idioma y quisiera evitarlo,
null
pero en sumaybe
lugar lo usaría , entonces recomendaría un término más descriptivo comonot a valid value
onavv
para indicar la intención. Pero el nombre de ese no-valor es un concepto separado de si debería permitir que existan incluso valores en su idioma.Antes de poder decidir sobre cualquiera de esos dos puntos, debe definir qué significaría el significado de
maybe
para su sistema. Puede encontrar que es solo un cambio de nombre delnull
significado denot a valid value
o puede encontrar que tiene una semántica diferente para su idioma.Del mismo modo, la decisión de verificar el acceso con el
null
acceso o la referencia es otra decisión de diseño de su idioma.Para proporcionar un poco de historia,
C
tenía una suposición implícita de que los programadores entendían lo que intentaban hacer al manipular la memoria. Como era una abstracción superior al ensamblaje y los lenguajes imperativos que lo precedieron, me aventuraría a que no se les ocurriera la idea de proteger al programador de una referencia incorrecta.Creo que algunos compiladores o sus herramientas adicionales pueden proporcionar una medida de verificación contra el acceso de puntero no válido. Entonces, otros han notado este problema potencial y han tomado medidas para protegerse contra él.
Si debe permitirlo o no, depende de lo que desea que logre su idioma y del grado de responsabilidad que desea llevar a los usuarios de su idioma. También depende de su capacidad de crear un compilador para restringir ese tipo de comportamiento.
Entonces para responder a sus preguntas:
"Qué tipo de argumentos ..." - Bueno, depende de lo que quieras que haga el lenguaje. Si desea simular el acceso de metal desnudo, entonces puede permitirlo.
"¿es solo equipaje histórico?" Quizás, quizás no.
null
ciertamente tuvo / tiene significado para varios idiomas y ayuda a impulsar la expresión de esos idiomas. El precedente histórico puede haber afectado los idiomas más recientes y su aceptación,null
pero es un poco demasiado difícil agitar las manos y declarar el concepto de equipaje histórico inútil.1 Vea este artículo de Wikipedia, aunque se da crédito a Hoare por valores nulos y lenguajes orientados a objetos. Creo que los idiomas imperativos progresaron a lo largo de un árbol genealógico diferente al de Algol.
fuente
null
).object o = null; o.ToString();
compila muy bien para mí, sin errores ni advertencias en VS2012. ReSharper se queja de eso, pero ese no es el compilador.Si observa los ejemplos en el artículo que citó, la mayoría de las veces el uso
Maybe
no acorta el código. No obvia la necesidad de verificarNothing
. La única diferencia es que le recuerda hacerlo a través del sistema de tipos.Tenga en cuenta que digo "recordar", no forzar. Los programadores son flojos. Si un programador está convencido de que un valor no puede serlo
Nothing
, desreferenciará elMaybe
sin verificarlo, al igual que desreferencia un puntero nulo ahora. El resultado final es que convierte una excepción de puntero nulo en una excepción de "vacío tal vez desreferenciado".El mismo principio de la naturaleza humana se aplica en otras áreas donde los lenguajes de programación intentan obligar a los programadores a hacer algo. Por ejemplo, los diseñadores de Java intentaron obligar a las personas a manejar la mayoría de las excepciones, lo que resultó en una gran cantidad de repeticiones que ignora silenciosamente o propaga ciegamente las excepciones.
Lo que
Maybe
es bueno es cuando se toman muchas decisiones a través de la coincidencia de patrones y el polimorfismo en lugar de verificaciones explícitas. Por ejemplo, podría crear funciones separadasprocessData(Some<T>)
y con lasprocessData(Nothing<T>)
que no puede hacer nadanull
. Automáticamente mueve su manejo de errores a una función separada, lo cual es muy deseable en la programación funcional donde las funciones se pasan y evalúan de manera perezosa en lugar de llamarse siempre de arriba hacia abajo. En OOP, la forma preferida de desacoplar su código de manejo de errores es con excepciones.fuente
const
, pero hacerlo opcional. Algunos tipos de código de nivel inferior, como las listas vinculadas, por ejemplo, serían muy molestos de implementar con objetos no anulables.map :: Maybe a -> (a -> b)
ybind :: Maybe a -> (a -> Maybe b)
definirse en él, para que pueda continuar y enhebrar más cálculos en su mayor parte con la refundición utilizando una instrucción if else. Y legetValueOrDefault :: Maybe a -> (() -> a) -> a
permite manejar el caso anulable. Es mucho más elegante que la coincidencia de patronesMaybe a
explícitamente.Maybe
es una forma muy funcional de pensar un problema: hay una cosa y puede o no tener un valor definido. Sin embargo, en un sentido orientado a objetos, reemplazamos esa idea de una cosa (no importa si tiene un valor o no) con un objeto. Claramente, un objeto tiene un valor. Si no es así, decimos que el objeto esnull
, pero lo que realmente queremos decir es que no hay ningún objeto en absoluto. La referencia que tenemos al objeto no apunta a nada. La traducciónMaybe
a un concepto OO no tiene nada de novedoso, de hecho, solo genera un código más desordenado. Aún debe tener una referencia nula para el valor deMaybe<T>
. Todavía tiene que hacer verificaciones nulas (de hecho, tiene que hacer muchas más verificaciones nulas, abarrotando su código), incluso si ahora se llaman "tal vez verificaciones". Claro, escribirás un código más robusto como afirma el autor, pero diría que es solo el caso porque has hecho el lenguaje mucho más abstracto y obtuso, lo que requiere un nivel de trabajo que es innecesario en la mayoría de los casos. De buena gana tomaría una NullReferenceException de vez en cuando que lidiar con el código de espagueti haciendo una comprobación Quizás cada vez que accedo a una nueva variable.fuente
Maybe<T>
tiene que permitirnull
como valor, porque el valor puede no existir. Si tengoMaybe<MyClass>
, y no tiene un valor, el campo de valor debe contener una referencia nula. No hay nada más que sea verificablemente seguro.Maybe
podría ser una clase abstracta, con dos clases que heredan de ella:Some
(que tiene un campo para el valor) yNone
(que no tiene ese campo). De esa manera, el valor nunca esnull
.El concepto de
null
puede remontarse fácilmente a C, pero ese no es el problema.Mi lenguaje de elección diario es C # y me quedaría
null
con una diferencia. C # tiene dos tipos de tipos, valores y referencias . Los valores nunca pueden sernull
, pero a veces me gustaría poder expresar que ningún valor está perfectamente bien. Para hacer esto, C # usaNullable
tipos, porint
lo que sería el valor yint?
el valor anulable. Así es como creo que los tipos de referencia también deberían funcionar.Ver también: la referencia nula puede no ser un error :
fuente
Creo que esto se debe a que la programación funcional está muy preocupada por los tipos , especialmente los tipos que combinan otros tipos (tuplas, funciones como tipos de primera clase, mónadas, etc.) que la programación orientada a objetos (o al menos inicialmente lo hizo).
Las versiones modernas de los lenguajes de programación de los que creo que estás hablando (C ++, C #, Java) se basan en lenguajes que no tenían ninguna forma de programación genérica (C, C # 1.0, Java 1). Sin eso, aún puede generar algún tipo de diferencia entre los objetos anulables y no anulables en el lenguaje (como las referencias de C ++, que no pueden ser
null
, pero también son limitadas), pero es mucho menos natural.fuente
Creo que la razón fundamental es que se requieren relativamente pocas verificaciones nulas para que un programa sea "seguro" contra la corrupción de datos. Si un programa intenta usar el contenido de un elemento de matriz u otra ubicación de almacenamiento que se supone que se escribió con una referencia válida pero no lo fue, el mejor resultado es que se produzca una excepción. Idealmente, la excepción indicará exactamente dónde ocurrió el problema, pero lo que importa es que se arroje algún tipo de excepción antes de que la referencia nula se almacene en algún lugar que pueda causar corrupción de datos. A menos que un método almacene un objeto sin intentar usarlo de alguna manera primero, un intento de usar un objeto constituirá, en sí mismo, una especie de "verificación nula".
Si uno quiere asegurarse de que una referencia nula que aparece donde no debería causar una excepción particular que no sea
NullReferenceException
, a menudo será necesario incluir verificaciones nulas en todo el lugar. Por otro lado, el simple hecho de garantizar que se produzca alguna excepción antes de que una referencia nula pueda causar un "daño" más allá de cualquier cosa que ya se haya hecho a menudo requerirá relativamente pocas pruebas; las pruebas generalmente solo se requerirán en los casos en que un objeto almacenaría un referencia sin tratar de usarlo, y la referencia nula sobrescribiría una válida o haría que otro código malinterpretara otros aspectos del estado del programa. Tales situaciones existen, pero no son tan comunes; la mayoría de las referencias nulas accidentales quedarán atrapadas muy rápidamentesi uno los busca o no .fuente
"
Maybe
," como está escrito, es una construcción de nivel más alto que nulo. Usando más palabras para definirlo, Quizás es "un puntero a una cosa o un puntero a nada, pero el compilador aún no ha recibido suficiente información para determinar cuál". Esto lo obliga a verificar explícitamente cada valor constantemente, a menos que construya una especificación del compilador que sea lo suficientemente inteligente como para mantenerse al día con el código que escribe.Puede realizar una implementación de Maybe con un lenguaje que tenga valores nulos fácilmente. C ++ tiene uno en forma de
boost::optional<T>
. Hacer el equivalente de nulo con Quizás es muy difícil. En particular, si tengo unMaybe<Just<T>>
, no puedo asignarlo a nulo (porque dicho concepto no existe), mientras queT**
en un lenguaje con nulo es muy fácil de asignar a nulo. Esto obliga a uno a usarMaybe<Maybe<T>>
, lo cual es totalmente válido, pero lo obligará a hacer muchas más comprobaciones para usar ese objeto.Algunos lenguajes funcionales usan Quizás porque nulo requiere un comportamiento indefinido o manejo de excepciones, ninguno de los cuales es un concepto fácil de mapear en sintaxis de lenguaje funcional. Quizás desempeña el papel mucho mejor en tales situaciones funcionales, pero en lenguajes de procedimiento, nulo es el rey. No se trata de lo correcto y lo incorrecto, sino de lo que hace que sea más fácil decirle a la computadora que haga lo que usted quiere que haga.
fuente