Здравствуйте, 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 получился только конструктор копирования.