212

Каждый раз, когда программисты жалуются на нулевые ошибки/исключения, кто-то спрашивает, что мы делаем без нулевого.Лучшее объяснение для языков без пустых

У меня есть базовое представление об холодности типов опций, но у меня нет навыков знания или языков, чтобы наилучшим образом выразить это. Что такое отличное Объяснение следующего написанного способом, доступным среднему программисту, на который мы могли бы указать этого человека?

  • Нежелательность наличия ссылка/указатели будут обнуляемыми по умолчанию
  • Как типов параметров работа, включая стратегии для облегчения проверки неопределенных случаев, таких как
    • сопоставления с образцом и
    • монадических постижениями
  • Альтернативное решение, такое как сообщение, содержащее ноль
  • (другие аспекты, которые я пропустил)
+11

Если добавить теги к этому вопросу для функционально-программирования или F # вы обязаны получить некоторые фантастические ответы. –

+0

Я добавил функциональный тег программирования, так как тип опции действительно поступал из мира ml. Я бы предпочел не отмечать его F # (слишком специфичным). Кстати, кому-то с таксономическими полномочиями необходимо добавить теги типа «тип-тип» или «тип-тип». –

+4

, я подозреваю, что таких особых тегов мало. Теги, в основном, позволяют людям находить соответствующие вопросы (например, «вопросы, которые я знаю много, и я смогу ответить», и «функциональное программирование» здесь очень полезно. Но что-то вроде «null» или «null», option-type "гораздо менее полезны. Немногие люди, вероятно, будут отслеживать тег типа« option-type », который ищет ответы на вопросы, которые они могут ответить.;) – jalf

ответ

414

Я думаю, что краткое изложение того, почему null является нежелательным, заключается в том, что бессмысленные состояния не должны быть представлены.

Предположим, что я моделирую дверь. Он может находиться в одном из трех состояний: открыт, закрыт, но разблокирован, закрыт и заблокирован. Теперь я мог моделировать его вдоль линий

class Door 
    private bool isShut 
    private bool isLocked 

и ясно, как отобразить мои три состояния в этих двух логических переменных. Но это оставляет четвертое, нежелательное состояние: isShut==false && isLocked==true. Поскольку типы, которые я выбрал, так как мое представление допускает это состояние, я должен приложить умственные усилия, чтобы гарантировать, что класс никогда не попадает в это состояние (возможно, явно кодируя инвариант). В отличие от этого, если бы я использовал язык с алгебраическими типами данных или проверяемых перечислений, что позволяет мне определить

type DoorState = 
    | Open | ShutAndUnlocked | ShutAndLocked 

тогда я мог бы определить

class Door 
    private DoorState state 

и нет больше забот. Система типов гарантирует, что существует только три возможных состояния для экземпляра class Door. Это то, к чему подходят системы типов - явно исключая целый класс ошибок во время компиляции.

Проблема с null заключается в том, что каждый ссылочный тип получает это дополнительное состояние в своем пространстве, которое обычно нежелательно. А переменной string может быть любая последовательность символов, или это может быть это сумасшедшее дополнительное значение null, которое не отображается в моей проблемной области. Triangle объект имеет три Point с, которые сами по себе имеют X и Y значения, но, к сожалению, Point s или сам Triangle может быть это сумасшедшее нулевое значение, что не имеет смысла в области построения графиков я работаю в п.

Когда вы намереваетесь моделировать возможное несуществующее значение, вы должны выбрать его явно. Если так, как я намерен моделировать людей в том, что каждый Person имеет FirstName и LastName, но только у некоторых людей есть MiddleName с, то я хотел бы сказать что-то вроде

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

где string здесь предполагается быть непустой тип. Тогда нет никаких сложных инвариантов для установления и отсутствия неожиданных NullReferenceException s при попытке вычислить длину чьего-либо имени. Система типов гарантирует, что любой код, относящийся к MiddleName, учитывает возможность его None, тогда как любой код, относящийся к FirstName, может с уверенностью предположить, что там есть значение.

Так, например, с использованием типа выше, мы могли бы автору эту глупую функцию:

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

без забот.В отличие от этого, в языке с обнуляемого ссылки на типы, такие как строки, то при условии,

class Person 
    private string FirstName 
    private string MiddleName 
    private string LastName 

вы в конечном итоге авторинга такие вещи, как

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

, который взрывается, если входящий объект Person не имеет инвариант все время ненулевым, или

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) 

или, может быть

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

Предполагая, что p обеспечивает первое/последнее, но среднее может быть нулевым, или, может быть, вы делаете проверки, которые генерируют разные типы исключений или кто знает что. Все эти сумасшедшие варианты реализации и вещи, о которых нужно подумать, возникают из-за того, что есть эта глупая представляемая ценность, которую вы не хотите или не хотите.

Null обычно добавляет ненужную сложность. Сложность является врагом всего программного обеспечения, и вы должны стремиться к уменьшению сложности всякий раз, когда это разумно.

(Обратите внимание также, что есть более сложность для даже этих простых примеров. Даже если FirstName не может быть null, string может представлять "" (пустая строка), который, вероятно, также не имя человек, который мы намерены смоделировать . Таким образом, даже с не-нулевыми строками, все же может быть, что мы «представляем бессмысленные значения». Опять же, вы можете выбрать сражение с этим либо с помощью инвариантов и условного кода во время выполнения, либо с помощью системы типов (например, иметь тип NonEmptyString). Последнее, возможно, не рекомендуется («хорошие» типы часто «закрыты» по совокупности общих операций, и, например, NonEmptyString не закрыт над .SubString(0,0)), но он демонстрирует больше точек в дизайне В конце дня, в любая система данного типа, есть некоторая сложность, от которой будет очень хорошо избавиться, и другую сложность, из-за которой сложнее всего избавиться. Ключом к этой теме является то, что в почти каждой системе изменение от «нулевых ссылок по умолчанию» к «непустым ссылкам по умолчанию» почти всегда является простым изменением, что делает систему типов намного лучше при сражении сложность и исключение определенных типов ошибок и бессмысленных состояний. Так что это довольно сумасшествие, так что многие языки повторяют эту ошибку снова и снова.)

+1

+1, но обратите внимание, что не все люди имеют фамилии («имя» и «фамилия» кажутся более точными в любом случае); Я слышал о некоторых людях, у которых есть только одно имя (это означает, что они не вписываются в большинство моделей данных). Арабские имена могут быть особенно сложными. Не все кредитные карты имеют 16 цифр. –

+30

Re: names - Действительно.И, может быть, вам действительно нужно моделировать дверь, которая висит на открытом воздухе, но с торможением блокировки, предотвращающим закрытие двери. В мире много сложностей. Ключ состоит в том, чтобы не добавлять сложность _more_ при реализации сопоставления между «состояниями мира» и «состояниями программ» в вашем программном обеспечении. – Brian

+2

Кстати, для хорошего чтения на тему представления в программном обеспечении я предлагаю напечатанную «Абстракцию и спецификацию в разработке программ» (Лисков, используя язык CLU). – Brian

16

Нежелательность наличия указателей/указателей по умолчанию не допускает значения.

Я не думаю, что это главная проблема с нулями, основная проблемой с нулями является то, что они могут означать две вещи:

  1. Ссылка/указатель UNINITIALIZED: проблема здесь то же как изменчивость вообще. Во-первых, это затрудняет анализ вашего кода.
  2. Переменная, являющаяся нулевым, фактически означает что-то: это тот случай, когда типы опций фактически формализуются.

Языки, которые поддерживают типы опционов, как правило, также запрещают или препятствуют использованию неинициализированных переменных.

Как работают типы параметров, включая стратегии, облегчающие проверку нулевых случаев, таких как сопоставление образцов.

Для того, чтобы быть эффективными, типы параметров должны поддерживаться непосредственно на языке. В противном случае для имитации требуется много кода котельной. Совместимость шаблонов и тип-вывод - это две языковые функции, которые упрощают работу с типами параметров. Например:

В 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;") 

Однако в таком языке, как Java без прямой поддержки типов опционов, мы бы что-то вроде:

//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()); 

Альтернативного решения, такие как сообщение есть nil

Объективное сообщение «message eating nil» - это не столько решение, сколько попытка облегчить голова f null проверка. В принципе, вместо того, чтобы бросать исключение во время выполнения при попытке вызвать метод для нулевого объекта, выражение вместо этого вычисляет нуль.Приостановив недоверие, это похоже на то, что метод каждого экземпляра начинается с if (this == null) return null;. Но тогда есть потеря информации: вы не знаете, вернул ли метод null, потому что он является допустимым возвращаемым значением, или потому, что объект фактически является нулевым. Это очень похоже на проглатывание исключений и не делает никакого прогресса, устраняя проблемы с нулем, описанным ранее.

+0

Это домашнее животное, но C# - ha rdly c-подобный язык. –

+4

Я собирался для Java здесь, так как у C#, вероятно, было бы более приятное решение ... но я ценю ваш peeve, что люди действительно имеют в виду, это «язык с синтаксисом c-inspired». Я пошел вперед и заменил оператор «c-like». –

+0

С linq, справа. Я думал о C# и не заметил этого. –

11

Ассамблея принесла нам адреса, также известные как нетипизированные указатели. C отображал их непосредственно как типизированные указатели, но вводил null Algol как уникальное значение указателя, совместимое со всеми типизированными указателями. Большая проблема с null в C состоит в том, что, поскольку каждый указатель может быть нулевым, никогда нельзя использовать указатель без ручной проверки.

В языках высокого уровня, имея нулевой неудобно, так как он действительно передает два различных понятия:

  • указал, что-то неопределенных.
  • Сообщить о том, что что-то есть факультативно.

Наличие неопределенных переменных в значительной степени бесполезно и приводит к неопределенному поведению, когда они происходят. Я полагаю, что все согласятся, что с неопределенными вещами следует избегать любой ценой.

Второй случай является опциональным и лучше всего предоставляется явно, например, с помощью option type.


Предположим, мы находимся в транспортной компании, и нам нужно создать приложение, чтобы помочь создать расписание для наших драйверов. Для каждого водителя мы храним несколько информации, таких как: водительские права, которые у них есть, и номер телефона для вызова в случае чрезвычайной ситуации.

В C мы могли бы:

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; 
}; 

Как вы заметили, в какой-либо обработки над нашим списком водителей мы должны проверить нулевые указатели. Компилятор вам не поможет, безопасность программы зависит от ваших плеч.

В OCaml, тот же код будет выглядеть следующим образом:

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; 
} 

Давайте теперь говорят, что мы хотим напечатать имена всех водителей вместе с их лицензионным грузовиком номеров.

В 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 ? */ 
    } 
} 

В OCaml, который был бы:

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 

Как вы можете видеть в этом тривиальном примере, нет ничего сложного в безопасной версии:

  • Это терьер.
  • Вы получаете гораздо лучшие гарантии, и никакая нулевая проверка не требуется вообще.
  • Компилятор гарантировал, что вы правильно имели дело с опцией

В то время как в C, вы могли бы просто забыли нулевой чек и бум ...

Примечание: эти примеры кода, где не компилируется, но Надеюсь, у вас есть идеи.

+0

Я никогда не пробовал, но http://en.wikipedia.org/wiki/Cyclone_%28programming_language%29 утверждает, что допускает ненулевые указатели для c. –

+1

Я не согласен с вашим утверждением, что никто не заинтересован в первом случае. Многие люди, особенно в сообществах функциональных языков, чрезвычайно заинтересованы в этом и либо препятствуют, либо полностью запрещают использование неинициализированных переменных. –

+0

Я верю, что 'NULL' как в« ссылке, которая не может указывать на что-либо »была изобретена для некоторого языка алголов (Wikipedia соглашается, см. Http://en.wikipedia.org/wiki/Null_pointer#Null_pointer). Но, конечно, вероятно, что программисты сборки инициализировали свои указатели на недопустимый адрес (читайте: Null = 0). – delnan

62

Приятная вещь о типах опций заключается не в том, что они являются необязательными. Это то, что все остальные типы не являются.

Иногда, мы должны быть в состоянии представить своеобразное «нулевое» состояние. Иногда мы должны представлять опцию «нет значения», а также другие возможные значения, которые может принимать переменная. Таким образом, язык, который категорически запрещает это, будет немного искалечен.

Но часто, нам не нужно, и позволяет такое «нулевое» состояние только приводит к неоднозначности и путанице: каждый раз, когда я получить доступ к переменной ссылочного типа в .NET, я должен считать, что это может быть null.

Часто он никогда не будет Фактически будет нулевым, потому что программист структурирует код так, что он никогда не может произойти. Но компилятор не может проверить это, и каждый раз, когда вы его видите, вы должны спросить себя: «Может ли это быть нулевым?Мне нужно, чтобы проверить нуль здесь ли?»

В идеале, во многих случаях, когда нуль не имеет смысла, оно не должно быть разрешено.

Это сложно достичь в .NET, где почти все может быть нулевым. Вы должны полагаться на автора кода, который вы называете, на 100% дисциплинированным и последовательным и четко документировали, что может и не может быть нулевым, или вам нужно быть параноидальным и проверить все.

Однако, если типы не являются нулевыми по умолчанию, то вы не знаете чтобы проверить, являются ли они нулевыми. Вы знаете, что они никогда не могут быть нулевыми, потому что проверка компилятора/типа обеспечивает это для вас.

И тогда нам просто нужна задняя дверь для редких случаев, где нам нужно обработать нулевое состояние. do. Затем можно использовать тип «option». Затем мы допускаем null в тех случаях, когда мы приняли сознательное решение о том, что нам нужно представить случай «нет значения», и в каждом другом случае мы знаем, что значение никогда не будет равно нулю.

Как уже упоминалось, в C# или Java, например, нуль может означать одно из двух:

  1. переменная не инициализирована. Это должно, в идеале, никогда. Переменная не должна существует, если она не инициализирована.
  2. переменная содержит некоторые «необязательные» данные: она должна быть способна представить случай, когда нет данных. Иногда это необходимо. Возможно, вы пытаетесь найти объект в списке, и заранее не знаете, действительно ли он там. Затем мы должны иметь возможность представить, что «объект не найден».

Второе значение должно быть сохранено, но первое должно быть полностью устранено. И даже второе значение не должно быть значением по умолчанию. Это то, что мы можем выбрать в , если и когда нам это нужно. Но когда нам не нужно что-то быть дополнительным, мы хотим, чтобы контролер типа гарантировал, что он никогда не будет равен нулю.

+0

И во втором значении мы хотим, чтобы компилятор предупреждал (останавливал?) Нас, если мы попытаемся получить доступ к таким переменным, не указав сначала недействительность. Вот отличная статья о предстоящей функции null/non-null C# (наконец!) Https://blogs.msdn.microsoft.com/dotnet/2017/11/15/nullable-reference-types-in-csharp/ –

38

Поскольку людям, кажется, не хватает его: null неоднозначно.

Дата рождения Алисы: null. Что это значит?

Дата смерти Боба: null. Что это значит?

«Разумная» интерпретация может заключаться в том, что дата рождения существует у Алисы, но неизвестна, тогда как дата смерти Боба не существует (Боб все еще жив). Но почему мы получили разные ответы?


Другая проблема: null это крайний случай.

  • null = null?
  • nan = nan?
  • inf = inf?
  • Факс: +0 = -0?
  • +0/0 = -0/0?

Ответы обычно "да", "нет", "да", "да", "нет", "да", соответственно. Сумасшедшие «математики» называют NaN «ничтожеством» и говорят, что он сравнивается с самим собой. SQL обрабатывает значения NULL как не равные чему-либо (поэтому они ведут себя как NaNs). Интересно, что происходит, когда вы пытаетесь сохранить ± ∞, ± 0 и NaN в одну колонку базы данных (есть 2 NaNs, половина из которых являются «отрицательными»).

Хуже того, базы данных отличаются тем, как они обрабатывают NULL, и большинство из них несовместимы (см. Обзор NULL Handling in SQLite). Это довольно ужасно.


И теперь для обязательной истории:

Я недавно проектировал (sqlite3) таблицы базы данных с пятью столбцами a NOT NULL, b, id_a, id_b NOT NULL, timestamp. Потому что это общая схема предназначена для решения общей проблемы для достаточно произвольных приложений, существуют два ограничения уникальности:

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

id_a существует только для совместимости с существующим дизайном приложений (отчасти потому, что я не придумать лучшее решение) и не используется в новом приложении. Из-за того, как NULL работает в SQL, я могу вставить (1, 2, NULL, 3, t) и (1, 2, NULL, 4, t) и не нарушать первое ограничение единственности (потому что (1, 2, NULL) != (1, 2, NULL)).

Это работает специально из-за того, как NULL работает с ограничением уникальности в большинстве баз данных (по-видимому, проще моделировать ситуации «реального мира», например, ни один человек не может иметь одинаковый номер социального страхования, но не все люди имеют один).


FWIW без первого вызова неопределенного поведения, ссылки на C++ не могут «точка» нулевой, и это не представляется возможным построить класс с неинициализированными переменными членами класса (если исключение, строительство не получится).

Sidenote: Иногда вам могут понадобиться взаимоисключающие указатели (т. Е. Только один из них может быть не NULL), например. в гипотетическом iOS type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed. Вместо этого я вынужден делать такие вещи, как assert((bool)actionSheet + (bool)alertView == 1).

+0

Фактический математики не используют понятие «NaN», хотя, будьте уверены. – Noldorin

+0

@Noldorin: Они делают, но используют термин «неопределенная форма». –

+0

@ I.J.Kennedy: Это совсем другой колледж, который я хорошо знаю. Некоторые «NaN» могут представлять неопределенную форму, но поскольку FPA не делает символических рассуждений, приравнивая их к неопределенной форме, это вводит в заблуждение! – Noldorin

0

Векторный язык может иногда уйти с отсутствием нуля.

Пустой вектор служит в этом случае типичным нулем.

+0

Думаю, я понимаю, о чем вы говорите, но можете ли вы привести несколько примеров? Особенно, если применить несколько функций к возможному нулевому значению? –

+0

Хорошо применяя векторное преобразование к пустому вектору, получаем другой пустой вектор. FYI, SQL - это в основном векторный язык. – Joshua

+1

ОК, я лучше это разъясню. SQL - это векторный язык для строк и язык значений для столбцов. – Joshua

1

Я всегда смотрел на Null (или ноль) как отсутствие значения.

Иногда вы этого хотите, иногда вы этого не делаете. Это зависит от домена, с которым вы работаете.Если отсутствие имеет смысл: нет среднего имени, то ваше приложение может действовать соответствующим образом. С другой стороны, если нулевое значение не должно быть там: Первое имя равно null, тогда разработчик получает поговорку 2   a.m. телефонный звонок.

Я также видел, что код перегружен и чрезмерно сложный с проверкой на нуль. Для меня это означает, что одна из двух вещей:
а) ошибка выше в дереве приложений
б) плохой/неполного дизайн

На положительной стороне - Null, вероятно, один из наиболее полезных понятий для проверки, что-то отсутствует, а языки без понятия null приводят к чрезмерному усложнению вещей, когда пришло время провести проверку данных. В этом случае, если новая переменная не инициализирована, упомянутые язычники обычно устанавливают переменные в пустую строку, 0 или пустую коллекцию. Однако, если пустая строка или 0 или пустая коллекция действительные значения для вашего приложения - тогда у вас есть проблема.

Иногда это обходит путем изобретения особых/странных значений для полей, представляющих неинициализированное состояние. Но что происходит, когда специальное значение вводится благими намерениями пользователя? И давайте не будем впадать в беспорядок, это будет делать процедуры проверки данных. Если язык поддерживает нулевую концепцию, все проблемы исчезнут.

+0

Привет @Jon, Это немного сложно после вас здесь. Наконец, я понял, что по «специальным/странным» значениям вы, вероятно, имеете в виду что-то вроде «неопределенного» Javascript или «NaN» IEEE. Но кроме того, вы действительно не обращаетесь ни к одному из вопросов, заданных ОП. И утверждение, что «Нуль, вероятно, самое полезное понятие для проверки того, что что-то отсутствует», почти наверняка неверно. Типы вариантов - это хорошо продуманная, безопасная по типу альтернатива null. –

+0

@ Stephen - На самом деле, оглядываясь назад на мое сообщение, я думаю, что вся вторая половина должна быть перенесена на еще не заданный вопрос. Но я все еще говорю, что null очень полезен для проверки того, что что-то отсутствует. – Jon

43

Все ответы до сих пор сосредоточиться на том, почему null это плохо, и как это своего рода удобно, если язык может гарантировать, что некоторые значения будут никогда быть пустым.

Затем они идут на предположить, что это было бы очень аккуратной идея, если вы применять не-допустимость пустой для всех значений, которые можно сделать, если добавить понятие как Option или Maybe для представления типов, которые не может всегда имеют определенное значение. Это подход, примененный Haskell.

Это все хорошо! Но это не исключает возможности использования явно нулевых/ненулевых типов для достижения такого же эффекта. Почему же Опция по-прежнему хорошая? В конце концов, Scala поддерживает значения с нулевым значением (имеет, поэтому он может работать с библиотеками Java), но также поддерживает Options.

Q. Каковы преимущества, выходящие за рамки возможности удалять нули из языка полностью?

А. Состав

Если вы делаете наивный перевод с нулевым знают код

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

для опционного знают код

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

есть не большая разница! Но это также ужасный способ использовать Options ... Такой подход является гораздо чище:

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

Или даже:

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

Когда вы начинаете дело со списком вариантов, она становится еще лучше ,Представьте себе, что список people сам по желанию:

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

Как это работает?

//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) 

Соответствующий код с нулевыми проверками (или даже с elvis?: Операторами) был бы больно длинным. Настоящий трюк здесь - операция flatMap, которая позволяет вложенное понимание опций и коллекций таким образом, что значения, допускающие нулевое значение, никогда не могут быть достигнуты.

+6

+1, это хороший момент, чтобы подчеркнуть. Одно добавление: в Haskell-land, 'flatMap' будет называться' (>> =) ', то есть« bind »для monads. Правильно, Haskellers, как «flatMap», так много, что мы положили его на логотип нашего языка. –

+1

+1 Надеюсь, выражение 'Option ' никогда бы не было нулевым. К сожалению, Scala - это uhh, все еще связанная с Java :-) (С другой стороны, если Scala не играла хорошо с Java, кто бы это использовал? Oo) – 2010-10-30 21:27:16

+0

Прост достаточно: «List (null) .headOption ' , Обратите внимание, что это означает совсем другое, чем возвращаемое значение «None». –

5

Microsoft Research имеет межжала проект под названием

SpeC#

Это C# расширение с не-нуль типа и некоторый механизм проверить объекты против не является нулевым , хотя ИМХО, применяя проект по договору, принцип может быть более подходящим и более полезным для многих неприятных ситуаций, вызванных нулевыми ссылками.

+1

Хмм, интересно, действительно ли это: http://xkcd.com/810/ –

3

Исходя из .NET-фона, я всегда думал, что null имеет смысл, его полезно. Пока я не узнал о структурах и как легко было работать с ними, избегая большого количества шаблонов. Tony Hoare выступает в QCon London в 2009 году, apologized for inventing the null reference. Процитировать его:

Я называю это своей ошибкой в ​​миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я разрабатывал первую всеобъемлющую систему типов для ссылок на объектно-ориентированном языке (ALGOL W). Моя цель состояла в том, чтобы гарантировать, что все использование ссылок должно быть абсолютно безопасным, причем проверка выполняется автоматически с помощью . Но я не мог удержаться от соблазна положить нулевую ссылку , просто потому, что ее было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и сбоям системы, которые , вероятно, вызвали миллиард долларов боли и повреждений в последние сорок лет лет. В последние годы ряд программных анализаторов, таких как PREfix и PREfast в Microsoft, были использованы для проверки ссылок и предупреждения , если есть риск, что они могут быть не пустыми. Более поздние языки программирования, такие как SpeC#, ввели объявления для ненулевые ссылки. Это решение, которое я отверг в 1965 г.

Смотрите этот вопрос слишком at programmers