Re[4]: Опциональные типы
От: vdimas Россия  
Дата: 25.02.17 17:17
Оценка: :)
Здравствуйте, VladD2, Вы писали:

V>>Например, в ФП языках некий Т был value-тип (ну вот так компилятор решил), а после заворачивания F(T) всегда получаем ссылочный тип. Итого, храним лишнюю ссылку вместо значения.

VD>Ерунду полнейшую говоришь. Опшон без пооблем может быть как сылочным, так и нет.

Т.е. если в Nemerle опишу некий свой variant MyOptional, это будет value-тип?


VD>Я в библиотеку немерла специально добавил вэлью–опшон, чтобы обекты в хипе не создавать.


"Специально"?
Я правильно понимаю, что средствами языка, т.е. через variant, его нельзя было описать как value-тип?
Я-то уже 100 лет на Немерле не смотрел, но если я понимаю правильно, то ты лишь удружил через ЧТД.


VD>В Окамле не рекурсивные юнионы являются вэлью–типом и содержат доп поле для различия подтипов.


А как ты отличаешь value-тип от ссылочного?
Т.е. вопрос в следующем: а как эти значения этих типов передаются в кач-ве аргументов функций — по ссылке или по-значению?
Надеюсь, суть вопроса понятна?
Ниже подробней распишу в чем тут тонкость.


VD>Ну и, естественно, вспоминаем классический VB–шный Variant, который был валью–типом описанным на С с применением тег–поля и сишных юнионов.


1. Было сказано "в ФП языках".
2. Variant не описан средствами VB, а "дан сверху", т.е. является встроенным для него.

Я тебе больше скажу. Генератор CORBA IDL или COM IDL генерит размеченные объединения как value-типы для С++.
Одно плохо — CORBA это тоже не про ФП-языки.

В общем, в двух словах. Не столько для тебя, сколько для читателей.

Основная фишка тут в том, что реализация объединения через условный value-тип будет занимать в памяти такой же размер, какой нужен для представления самого большого из вариантов + дискриминатор. К тому же, данные за дискриминатором в любом случае выравниваются, т.е. под дискриминатор изволь выделить машинное слово к этому самому большому размеру.

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

А самое главное, в отличие от C#, "ссылочный тип" не означает размещение в куче. "Ссылочный тип" означает передачу этого типа по ссылке. Поэтому, оптимизатор запросто может выделить под конкретное значение алгебраика место на стеке и передать затем его в ф-ию по ссылке, если "увидит", что ф-ия лишь читает это значение, но нигде не запоминает ссылку на него.

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


V>>Только какие проблемы повторить тоже самое с ссылочными (nullable) типами в том же дотнете или C++?

VD>Ты путаешь абстракцию типа и реализацию. Нулевая ссылка конечно же может интерпретироваться программистом как отсутствие значения, но система типов языка о такой интерпретации ничего не знает, а стало быть не может уберечь от ошибок.

Верно.


VD>Тот же Котлин, хотя и использует нулевую ссылку каксэ отсутствующее значение, интерпретирует, например, string и string? как разные типы. Это позволяет компилятору контролировать ошибки связанные с null–ами.


А тут не верно.
Озвученное тобой говорит о том, что система типов Котлина НЕ контроллирует значение ссылочного типа, поэтому просто добавила ДРУГОЙ тип для non-nullable-ссылок.

То, что в синтаксисе языка наоборот — т.е. как бэ nullable-тип видится как "другой" — это ж просто трюк такой, верно?

А так-то похожий трюк с введением ДРУГОГО типа я тоже в С++ тоже периодически делаю.
Вот из рабочего проекта (надеюсь не наругают бо это примитивщина):
  NotNull
/* ------------------------------------------------------ */
/// NotNull pointer idiom
template<class T>
class NotNull
{
public:
    explicit NotNull(T * ptr)
        : ptr_(ptr) {
        assert(ptr);

        if(!ptr)
            throw NullReferenceException();
    }

    template<class T2>
    NotNull(const NotNull<T2> & ptr)
        : ptr_(ptr.ptr()) {}

    T * get() const BOOST_NOEXCEPT {
        T * tmp = ptr_;
        assume(tmp);
        return tmp;
    }

    T * operator->() const BOOST_NOEXCEPT {
        return get();
    }

    T * ptr() const {
        return ptr_;
    }
private:
    T * ptr_;
};

/* ------------------------------------------------------ */
/// NotNull specialization for intrusive_ptr
template<class T>
class NotNull< boost::intrusive_ptr<T> >
{
public:
    typedef boost::intrusive_ptr<T> SmartPtr;

    explicit NotNull(T * ptr)
        : ptr_(ptr) {
        assert(ptr);

        if(!ptr)
            throw NullReferenceException();
    }

    template<class T2>
    NotNull(const NotNull<T2> & ptr)
        : ptr_(ptr.ptr()) {}

    template<class T2>
    explicit NotNull(const boost::intrusive_ptr<T2> & ptr)
        : ptr_(ptr) {
        assert(ptr);

        if(!ptr)
            throw NullReferenceException();
    }

    T * get() const BOOST_NOEXCEPT {
        T * tmp = ptr_.get();
        assume(tmp);
        return tmp;
    }

    T * operator->() const BOOST_NOEXCEPT {
        return get();
    }

    operator SmartPtr() const {
        return ptr_;
    }

    const SmartPtr & ptr() const {
        return ptr_;
    }
private:
    SmartPtr ptr_;
};

/* ------------------------------------------------------ */
/// NotNull specialization for shared_ptr
template<class T>
class NotNull< boost::shared_ptr<T> >
{
public:
    typedef boost::shared_ptr<T> SmartPtr;

    explicit NotNull(T * ptr)
        : ptr_(ptr) {
        assert(ptr);

        if(!ptr)
            throw NullReferenceException();
    }

    template<class T2>
    NotNull(const NotNull<T2> & ptr)
        : ptr_(ptr.ptr()) {}

    template<class T2>
    explicit NotNull(const boost::shared_ptr<T2> & ptr)
        : ptr_(ptr) {
        assert(ptr);

        if(!ptr)
            throw NullReferenceException();
    }

    T * get() const BOOST_NOEXCEPT {
        T * tmp = ptr_.get();
        assume(tmp);
        return tmp;
    }

    T * operator->() const BOOST_NOEXCEPT {
        return get();
    }

    operator SmartPtr() const {
        return ptr_;
    }

    const SmartPtr & ptr() const {
        return ptr_;
    }

private:
    SmartPtr ptr_;
};

/* ------------------------------------------------------ */
template<class T>
bool operator==(const NotNull<T> & l, const NotNull<T> & r)
{
    return l.get() == r.get();
}

template<class T>
bool operator!=(const NotNull<T> & l, const NotNull<T> & r)
{
    return l.get() != r.get();
}

template<class T>
bool operator<(const NotNull<T> & l, const NotNull<T> & r)
{
    return l.get() < r.get();
}


Теперь в сигнатурах пишешь:
void foo(NotNull<SomeRefType> arg) {...}
// можно и через typedef NotNull<SomeRefType> SomeRefTypePtr; 
// - тут на вкус и цвет, явные аннотации тоже неплохи, бо читабельность! ))

И в теле ф-ий ничего проверять не надо.

Т.е. один раз сконструировал экземпляр non-nullable из nullable, т.е. всего один раз произошла проверка, а затем гарантии распространяются далее без проверок. Обрати внимание на explicit-конструкторы — помогает в том самом распространении гарантий, потому что implicit получился только конструктор копирования.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.