Здравствуйте, 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 в С++ мой вариант был бы фактически не нужен и местами даже вреден с т.з. эффективности.