Re[6]: Опциональные типы
От: vdimas Россия  
Дата: 26.02.17 09:38
Оценка: -1 :)
Здравствуйте, VladD2, Вы писали:

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

VD>Value-option не обязан быть вариантом. В немерле он сделан просто структурой.

Обязан.
Структура — это произведение типов, алгебраик — сумма.

Вот так было лучше (из твоей статьи):

Тип «option» – это вариантный тип данных, который объявлен в стандартной библиотеке Nemerle следующим образом:

variant option[T]
{
  | None
  | Some { val : T; }
}


Иначе у тебя получится аналог Nullable<> из System, но в C#/VB хоть компилятор "знает этот тип в лицо", как грится, и по-особенному с ним работает. С другим таким же MyNullable<> аналогичные фокусы не прокатывают.

А у тебя компилятор знает твой переписанный Optional в лицо? ))


VD>https://github.com/rsdn/nemerle/blob/master/lib/option.n#L217


    public Value : T
    {
      get
      {
        if (HasValue) _value 
        else assert(false, "try use Value when it not set");
      }
    }

Кошмар.

В том-то и беда для ООП-языков, что тип вариант нормально не выразить, потому что единственная возможность, когда в ООП можно одновременно разветвиться и изъять "откуда-то" значение, это вот такая конструкция:
MyType extractedValue;
Optional<MyType> optional = getValueFromSomewhere();

if(optional.tryExtract(extractedValue)) {
    // blah-blah-blah
}


Но ведь именно в случае Optional объявление предварительно "пустого" extractedValue убивает всю идею на корню.
Не видишь разве сам?
Тут ведь вся идея в том, что MyType может быть даже таким типом, который принципиально не может быть "пустым", а Optional<MyType> — может.

Поэтому, в ООП стиле "окончательно правильно" можно только так:
Optional<MyType> optional = getValueFromSomewhere();

optional.tryExtract(extractedValue => { /* callback */ });

Что и происходит у неглупых людей в том же JS.

А вот за GetValueOrDefault() — вообще руки оторвать надо. ))

Вот зачем ты сделал копию Nullable<> сугубо из мира ООП, если оно уже есть в системной библиотеке, т.е. от него всё-равно не убежишь?
Потому что ограничение на where T : struct? Дык, а ссылочные типы и так изкаробки обладают этой optional/nullable семантикой.


VD>В Немерле варианты сделаны на базе классов, но это одна из возможных реализаций.


Это отличная реализация, особенно в иммутабельной среде.

Реализаций может быть несколько, просто конкретно в дотнете если уж тип ссылочный — то это, блин, тип из кучи. ))
А не просто тип, который передают и хранят по ссылке.

Для языков навроде Немерле нужен чуть другой рантайм. Дотнетный не тогось — прибит гвоздями к ООП.
Вот, Гугл тут делает VM для веба, её текущие версии уже доступны, я уже посмотрел и вам советую...
Эта VM нейтральна к модели памяти.


V>>Я правильно понимаю, что средствами языка, т.е. через variant, его нельзя было описать как value-тип?

VD>У языка как бы средств больше чем одно. option ни разу не обязан быть ваниантом.

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


V>>Я-то уже 100 лет на Немерле не смотрел, но если я понимаю правильно, то ты лишь удружил через ЧТД.

VD>Ты себе что-то на придумывал и пытаешься это обосновать любыми путями.

Да ладно тебе включать непонимающего.
Всё ты прекрасно понимаешь, просто со своими особенностями — да, ты по своей инициативе взял Немерле под своё крыло, это похвально, конечно, но ты к своему подопечному явно не объективен.

Вот даже взять синтаксис:
public GetValueOrDefault() : T { _value }

А чего вот так не сделали еще?
public GetValueOrDefault() => _value;


Или так:
public GetValueOrDefault() = _value;


И зачем идёт аннотация типа : T в исходном варианте?


V>>А как ты отличаешь value-тип от ссылочного?

VD>Как кота от кошки. Смотрю value-тип — говорю — "value-тип".

Т.е. вне дотнета жизни нет? ))

Единственное отличие в том, что value-тип ХРАНИТСЯ по значению, а ссылочный по ссылке.
Передаваться оба могут как ссылочные и на этом есть куча оптимизирующих трюков именно в ФП.
Например, ссылочный тип мог быть создан по-значению в некоей структуре (в памяти этой структуры), а "все остальные" получают/размножают лишь ссылку на такое значение.

Понятно, что в дотнете в этом плане особо не разбежишься.
Причем, в дотнете есть т.н. "управляемая ссылка" (т.е. можно ссылаться на "середину" тела другого объекта из кучи или стека), но её тип почему-то не совместим с "обычным" ссылочным типом. Бред какой-то...


V>>Т.е. вопрос в следующем: а как эти значения этих типов передаются в кач-ве аргументов функций — по ссылке или по-значению?

VD>Не имеет значения. Передать вэлью-значение по ссылке не особо проблематично. Это детали реализации.

Именно. Передать можно. Вопрос — как хранить. Потому что в случае value-типа надо делать копию значения.

По-определению ссылочного типа — это тип-индекс. Значения этого типа хранят индекс (ключ) другого значения. При копировании ссылочного типа копируется индекс, а не значение. Вот и вся семантика ссылочного типа.

А то, что ссылочный тип в дотнете создаётся исключительно на куче — это заморочки сугубо дотнета.


V>>Надеюсь, суть вопроса понятна?

VD>Понятна. Пытаешься придумать обосновании явно неверному утверждению.

Всё никак не научишься спорить? ))
Ты можешь возражать своим аргументом с обоснованием, или ты НЕ можешь возражать.
Третьего не дано.


V>>1. Было сказано "в ФП языках".

VD>Язык не имеет значения.

Назови язык как хочешь, но в нём нужна поддержка алгебраиков + паттерн-матчинга по ним (одно без другого не имеет смысла, ес-но), чтобы осилить именно идиому Optional. Потому что проблему реализации в ООП-style я уже показал. Единственное нормальное решение в ООП — через колбэк. Но, боюсь, индустрия как такому "нормальному решению" может оказаться не готовой, ы-ы-ы.
(и пофик что колбэк якобы функциональный тип в "чужеродном ООП" — замени его на интерфейс-колбэк, ничего не изменится в семантике)


V>>2. Variant не описан средствами VB, а "дан сверху", т.е. является встроенным для него.

VD>Опять же не имеет значения.

Если речь о системе типов — имеет первейшее.


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

V>>Одно плохо — CORBA это тоже не про ФП-языки.
VD>Ты себе придумал какой-то странный ценз "ФП-языки". Он только тебе интересен.

Я уже поправился — алегбраик + паттерн матчинг.
Просто сие пошло от функциональных языков изначально.


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

VD>Ну, да. Как всем же ясно что ты прав и они хотят у гуру поучиться.

Почему бы и нет?


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

VD>Ой-йо-йо. Великое горе! А любой объект в дотнете занимает 24 байта (под x64).

Зато ссылка на него занимает 4 байта в x86.
И вообще, это заморочки конкретно дотнета.


VD>И стало быть перерасход 1-2 интов не такая уж проблема.


Проблема при передаче параметров и хранении.


VD>А там еще локальность памяти подтягивается


Это в дотнете она подтягивается.


VD>затраты на занятие памяти в куче


именно что


VD>и в друг оказывается, что потеря этих байтов просто фигня по сравнению с выигрышем.


От сценариев зависит. Если это значение не надо многократно копировать, то да. Если надо — то упс.


VD>Вот так и с ValueOption оказывается, что потеря 1-4 байта (занимаемого bool-ом в зависимости от выравнивания) — это ни что по сравнению с затратами на ссылочные типы.


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

Как сказал бы WH по этому поводу: "компилятор сам должен первое превращать во второе или второе в первое, глядя на использование".
Теоретически я где-то согласен, а фактически его заносит. ))


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

V>>А тут не верно.
VD>Только по-твоему.

Компилятор не контроллирует ошибки, связанные с null-ами, он просто контролирует РАЗНЫЕ типы.

Я тебе специально привел аналог из С++, но про С++ достоверно известно, что компилятор никак не контролирует конкретно NULL.
Зато контролирует типы. Поэтому, просто выносим non-nullable в ОТДЕЛЬНЫЙ тип. Вот и вся суть "фокуса".


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

VD>Не говори ерунды. Система типов нигде значения не контролирует, так как значения сущность времени исполнения.

В общем, бывают системы типов, которые контролируют именно значения как параметр типа.
ЗТ называются.
Спроси у WH, он любит сверкать изречениями аккурат по этому поводу.


VD>Зато она контролирует отсутствие ошибок с этими значениями.


В моём примере такой контроль запросто получается на прикладном уровне, а не на уровне системы типов.


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

VD>Какие на фиг трюки? Это система типов языка.

Обычная там система типов, выглядит как без ЗТ.

Ссылочный тип сам по себе — это и так (в случае строки) уже будет тип 'string?' — сие дано изкаробки для любого ссылочного типа.

А теперь вводим некий ДРУГОЙ тип — аналог показанного мною NotNull<string> и делаем такой фокус — в синтаксисе языка обозначаем его как просто string.


VD>Для этого языка это разные, хотя и связанные типы.


В моём сниппете T* и NotNull<T> — тоже разные типы.
Первый — nullable, второй — никогда.


VD>Система типов языка может отличаться от системы типов базовой платформы.


Может. Только тут не отличается йоту. По крайней мере в плане nullable.
Тут весь трюк заключается сугубо в синтаксисе, где некий встроенный тип NotNull<string> в синтаксисе (и только в синтаксисе) выражается через простое string.


V>>А так-то похожий трюк с введением ДРУГОГО типа я тоже в С++ тоже периодически делаю.

VD>Да это где угодно можно. Вопрос лишь в удобстве использовании и загрузки компилятора ненужной работой.

Вот тут верно. Дело в удобстве.
С другой стороны, когда компилятор знает некоторые типы в лицо — это всегда грязный хак как раз в обход системы типов языка.
Потому что иначе такой хак был бы не нужен, ес-но.


VD>В том же Колине кроме разведения типов еще есть ряд фич упрощающих жизнь с этим. Иначе эта была бы адова боль, а не работа.


Я посмотрел "эти фичи".
Основное отличие в сценариях от моих — это в явном вызове конструктора NotNull<T>(someNullablePtr) у меня, потому что explicit было поставлено специально (думаю, понятно — почему).

Но это вещь одноразовая — только при вызове new объекта.
Но сам понимаешь, в С++ с его шаблонами эта вещь обыгрывается тоже однократно:
typedef NotNull<MyType> MyTypeRef;

void foo(MyTypeRef arg) {}
...
foo(make<MyType>(args));

Последнее не сильно отличается от этого в плане "синтаксического оверхеда":
foo(new MyType(args));

Зато сильно отличается в плане безопасности — в теле foo() и далее по зависимостям не надо делать никаких проверок.

Более того, метод NotNull operator-> проходит через такое:
    T * tmp = ptr_;
    assume(tmp);
    return tmp;

Это подсказка компилятору, что tmp не может быть NULL ни при каких обстоятельствах, что позволяет компилятору делать совсем уж агрессивные оптимизации.

Итого:
— проверку на NULL на каждый чих убрали, контроль возложили на компилятор;
— агрессивную оптимизацию включили.
— все выполнено ср-вами языка.
))


VD>Так у них можно проверить в if-е значение на null и компилятор сам подменит тип внутри true-секции этого if-f. Оберткой это не сделаешь. Или можно?


Оберткой надо будет создать новую "строгую" ссылку в секции true, согласен.


VD>Далее для null-абл значений есть лифтинг. Например, написав optionX?.Foo мы получим значение Foo поднятое в null-абл тип. И это можно проделывать сколько угодно долго.


Дык, это же не система типов, это же просто синтаксический сахар для такого (в терминах Шарпа):
option.HasValue ? new Nullable<ReturnTypeOfFoo>(Foo()) : null;


Думаю, в Немерле что-то типа такого тоже должно быть?

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


VD>Еще они проводят анализ библиотек Явы и вычисляют функции точно не возвращающие нулабл-типы. Всего этого на обертках не добьешся.


На обертке компилятор выкинет повторный if в момент линковки в теле конструктора моего NotNull, если поданное значение будет достоверно не NULL.


VD>В итоге получается разный класс решений. У тебя это примочка вызывающая боль при использовании, а у них стройная система.


Нет никакой боли и не про боль речь, а про систему типов языка.
Моей задачей было лишь ПРОДЕМОНСТРИРОВАТЬ механику такого трюка в языке без ЗТ.

Хотя, вот это:

можно проверить в if-е значение на null и компилятор сам подменит тип внутри true-секции этого if

Это обычно признак языков с ЗТ. В таких языках т.н. "контекст типизации" меняется прямо по ходу вычислений в теле ф-ий, а не только в ветках паттерн-матчинга, как у обычных ФП-языков.

Одно но — в языке со строгой типизацией всё-равно невозможно использование пременной одного типа в виде переменной другого, но возможно после такой проверки произвести, скажем присвоение/инициализацию, которая до проверки вызовет ошибку компиляции.

В случае С++ тоже надо будет в положительной ветке создать переменную NotNull<>, скомпилируется второй if в теле конструктора, который будет выкинут затем оптимизатором.

Да, есть такое. Без вот этого трюка оптимизатора с выкидываением повторного if в С++ мой вариант был бы фактически не нужен и местами даже вреден с т.з. эффективности.
Отредактировано 26.02.2017 9:49 vdimas . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.