Статьи

Случай для монады Couldbe (или почему значение null не определено)

Несколько новых языков программирования поставляются с типом Maybe (он же Optional или Option ). В этой статье я хотел бы выступить за расширенную монаду Maybe, которую я назову монадой Couldbe и которая может иметь три различных состояния: Не определено , Нет и Некоторые . Различие между значением Undefined и None было бы очень полезно в тех случаях, когда null является вполне приемлемым значением и где мы хотим знать, является ли значение свойства фактически нулевым или неизвестным .

Я не знаю ни о каком обсуждении или реализации такой монады Couldbe (или каково бы ни было ее название), и я, конечно, был бы очень заинтересован в таком материале, если кто-то может дать мне ссылку.

Swift Дополнительно

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

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

enum Optional {
    case None
    case Some(T)
}

Noneпредставляет nullзначение, в то время как Someнесет фактическое значение типа T. Язык Swift делает легко объявить, испытание и UnWrap опций с помощью объекта ?и !операторов. Например, следующий фрагмент кода объявляет три необязательных свойства, проверяет имя лучшего друга на недействительность и обращается к его развернутому значению, если оно есть:

class Person {
    var name:String?
    var bestFriend:Person?
    var age:Int?
}
var person = Person()
// ...
if person.bestFriend?.name != nil {
    println(person.bestFriend!.name!)
}
// or:
if let name = person.bestFriend?.name as String! {
    println(name)
}

Хотя было бы здорово иметь эту встроенную поддержку опций на языке Swift, я бы хотел иметь расширенную версию этого типа опций со следующим определением:

enum Optional {
    case Undefined
    case None
    case Some(T)
}

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

if john.bestFriend == nil {
    if john.bestFriend == undefined {
      // we don't know if John has or not a best friend
    }
    else {
      // John doesn't have a best friend
    }
}

Но почему мы хотели бы иметь такую ​​функцию?

Где JavaScript прав

Язык JavaScript различает undefinedи, nullи это различие велико и значимо. Для данного объекта он позволяет нам проверить, существует ли свойство или имеет нулевое значение:

var person = {
    name: "John",
    bestFriend: null
}

console.log(person.name)        // "John"
console.log(person.bestFriend)  // null
console.log(person.age)         // undefined

В то время как свойство bestFriend имеет нулевое значение (здесь null — это совершенно определенное и допустимое значение, которое означает: «У Джона нет лучшего друга»), свойство age просто не определено. Это не обязательно означает, что Джон не имеет возраста или что наша модель данных не знает о таком свойстве: это может означать, что возраст Джона в настоящее время неизвестен (или не определен ), потому что, например, наш объект был извлекается через удаленный сервис, который не предоставляет эту информацию.

Теперь мы все знаем, что анонимные объекты JavaScript — это не что иное, как карты, и, соответственно, проверка существования свойства age у нашего человека — это почти то же самое, что вызов containsKey("age")метода на карте Java. Однако существует большая разница между объектами JavaScript и картами Java: если вы получаете нулевое значение из карты Java, то нет никакого способа — из самого возвращенного значения — выяснить, является ли оно нулевым, поскольку карта на самом деле содержит нулевое значение или потому что карта не имеет никакого сопоставления для ключа «возраст». С другой стороны, вы можете выполнить следующий тест с JavaScript:

var age = person.age // person["age"]

if (age == null)
    console.log("age is null or undefined")
if (age === null)
    console.log("age is null")
if (age === undefined)
    console.log("age is undefined")

Что приводит к следующему выводу:

"age is null or undefined"
"age is undefined"

Если мы рассмотрим квалифицированный объект Person в Java или в Swift, у нас нет простого способа узнать, является ли значение свойства неопределенным (т. Е. Неизвестным, неинициализированным, еще не установленным и т. Д.) Или нулевым. Это не теоретический вопрос, и мы легко можем найти применение этой концепции в контексте, например, сущностей JPA.

JPA неинициализированные ассоциации

Давайте рассмотрим следующую сущность JPA:

@Entity
public class Person {

    private String name;

    @ManyToOne(fetch=FetchType.LAZY)
    private Person bestFriend;

    private int age;

    // getters and setters
}

Когда мы выбираем одного из этих людей, свойство bestFriend может иметь три различных типа значений: без значения (без лучшего друга), неинициализированное значение (не была выбрана ассоциация с лучшим другом) или инициализированное значение (лучший друг). CouldbeМонада было бы полезно здесь и может даже обеспечить стандартизированный способ представления неинициализированным ассоциаций (например, Hibernate использует динамические прокси в то время как EclipseLink просто использует нуль в таких случаях).

Частичная сериализация

Монада Couldbe также была бы чрезвычайно полезна в контексте частичной сериализации. В настоящее время я работаю над протоколом сериализации Spearal (вы можете прочитать эту статью DZone по этому вопросу), и одна из ключевых особенностей этого проекта — разрешить частичную сериализацию сложных графов данных объектов: в основном, идея заключается в том, что если клиентское приложение извлекает список лиц и отображает только их имена, нет оснований для сериализации всех свойств этих лиц. Это также работает по-другому: если вы отправляете человека на свой сервер для обновления и изменилось только свойство name, нет никаких причин для сериализации всего объекта person.

With Spearal, JPA uninitialized associations are treated as just a particular case of undefined properties. In a JavaScript client, undefined properties are (un)represented by missing keys, while null properties are actually null. It works fine with JavaScript, at least with anonymous objects, but such a possibility is completely missing in Java or Swift: we can’t delete a property of a qualified object and, even if there was a way do it, it wouldn’t be a clean way of dealing with missing properties.

Generally speaking, we need a way to set a value to an undefined (or unknown, or not yet known) state, and the value itself must be able to carry this information. In Java, we can certainly use dynamic proxies to implement this kind of feature. In Swift, a self-observing object that is KVO-compliant can also hold the extra information needed to know if a property is undefined or not. That’s exactly what we are using in Spearal currently but I think that a Couldbe monad would be a much cleaner solution.

Does anybody aware of such extension? Do you have any better idea than this Couldbe monad that could serve the same purpose?