. Я начал изучать его недавно, и он мне нра В "Дизайне и Эволюции С++" был такой пассажик: Бьерн сомневается, в правильном ли направлении он движется, и один из создателей языка С успокаивает его фразой "С++ — это то, чем бы мы сделали С, если бы могли". Так, вот, при ознакомлении с D у меня временами (далеко не всегда!) возникает впечатление, что D — это то, чем мог бы быть С++.
Так вот — сейчас сижу читаю мануал этого забавного языка. И возникло у меня желание делиться с окружающими интересными фактами. А раз возникло — то этим я в этой ветке и займусь. Цель этого мне видится не в ознакомлении всего РСДНа с новым языком — а в обсуждении новых или не очень концепций D и их реализации. В конце концов, если это никому не интересно — бомбочек навешаете и всех делов
Поехали!
Перегрузка операторов
Хорошая новость: она есть. Но выглядит несколько... вычурно, что ли?
В С++ мы имеем "естественный синтаксис:
class A
{
...
int operator+(int); //оператор сложения A+int
...
}
В принципе, такой синтаксис в некоторой степени самоописателен.
В D та же задача решается следующим способом:
class A
{
...
int opAdd(int); //оператор сложения A+int - функция со "специальным" именем opAdd
...
}
Результат, как бы, тот же. Но выглядит это слегка менее очевидно.
Зачем это сделано?
Во-первых, один из design goals языка D — упрощение (по сравнению с С++) лексического/синтаксического анализатора (поэтому D вообще больше склонен к словам, чем к "значками").
Во-вторых, при этом достигается довольно интересный эффект:
A a;
double d = 1/a; //как написать кастомный оператор для этого случая?
В С++ выходом из этой ситуации стали операторы — свободные функции:
class A
{
...
};
double operator/(int, A&);
А вот в D — "несимметричные операторные функции" ((с) термина мой):
class A
{
double opDiv_r(int, A&); //opDiv + _r (right)
};
Мне, откровенно говоря, такой подход нравится существенно меньше — уж очень это напоминает некоторые "неявные соглашения", которые способствуют упрощению компилятора, но не самодокументированию кода. Впрочем, думаю, привыкнуть можно
А вот — очень интересная фича операторов D: они в явном виде поддерживают "зависимые операторы":
//C++
A a, b;
if(a == b) ... //вызов A.operator==if(a != b) ... //вызов A.operator!=
//D
A a, b;
if(a == b) ... //вызов A.opEqualsif(a != b) ... //вызов !A.opEquals
то есть оператора != не существует по определению.
И даже более того: все четыре сравнения (>, <, >=, <=) выполняются одним оператором opCmp:
//C++
A a, b;
if(a > b) ... //вызов A.operator>if(a < b) ... //вызов A.operator<
//...и т.д.
//D
A a, b;
if(a < b) ... //вызов A.opCmp(b) < 0if(a > b) ... //вызов A.opCmp(b) > 0if(a <= b) ... //вызов A.opCmp(b) <= 0if(a >= b) ... //вызов A.opCmp(b) >= 0
...и вот эта фичка (несмотря на не совсем очевидное действие сравнения результата с нулем) мне кажется весьма достойной. Как и любые другие, позволяющие записывать всеобщие неявные соглашения (например: оператор != эквивалентен отрицанию оператора ==) в явном виде. Выигрыш имеем двойной: а) уверенность, что в чужом коде определены и корректно работают все операторы сравнение + б) отстутсвие необходимости повторять "руками" дурную работу.
Впрочем, не исключаю, что для нетривиального класса определение оператора opCmp (учитывая все варианты возвращаемых значений) будет выглядет довольно нелаконично и даже искусственно.
К слову, эта идея (избавления от "ненужных операторов") не распространена на операторы вида op= (+=, -= и т.д.). Почему? То есть, конечно, логику в этом, видимо, можно найти... Но лень.
Здравствуйте, Зверёк Харьковский, Вы писали:
ЗХ>Мне, откровенно говоря, такой подход нравится существенно меньше — уж очень это напоминает некоторые "неявные соглашения", которые способствуют упрощению компилятора, но не самодокументированию кода. Впрочем, думаю, привыкнуть можно
В общем и целом согласен.
Пара "но" разве что.
1) Не так часто переопределяются операторы.
2) Если нужно "унутре" позвать функцию оператор то явный вызов opShift() выглядит наверное лучше.
Решение этого вопроса довольно изящно: без введения дополнительного ключевого слова и особого синтаксиса:
//Это - С++:class A
{
int val(); //getterint val(int _newval); //setter (собака такая - сеттер)
}
A a;
int i = a.val();
a.val(i);
//==============================
//А вот это - уже D:class A
{
int val(); //getterint val(int _newval); //setter
}
A a;
int i = a.val; //вызывается A.val();
a.val = i; //вызывается A.val(int);
Лихо? Это еще не все. Данные имеют свойства:
int[] arr; //что написано, то и есть - это динамический массив int'овfor(int i = 0; i < arr.length; ++i) //использование свойства массива .length
a.length = 42; //еще одно использование свойства
Зашибись? Но и это еще не все. Типы тоже имеют свойства:
int maxint = int.max; //максимальное значение int'аint sizeint = int.size; //эта фенечка вместо С-шного sizeof(int)float.nan //NaNfloat.infinity //бесканечнасть
(2).max //эквивалентно int.max
2.max //ошибка - у числа 2 нету свойствов
Еще есть такое забавное свойство .init — значение, которым инициализируется переменная:
int val = int.init; //получим 0int a;
val = a.init; //получим 0 - потому что a было проинициализировано нулемint b = 8;
val = b.init; //получим 8typedef int mycoolint = 2;
val = mycoolint.init; //получим 2
mycoolint c;
val = c.init; //получим 2
ЗХ>//C++
ЗХ>A a, b;
ЗХ>if(a > b) ... //вызов A.operator>
ЗХ>if(a < b) ... //вызов A.operator<
ЗХ>//...и т.д.
ЗХ>//D
ЗХ>A a, b;
ЗХ>if(a < b) ... //вызов A.opCmp(b) < 0
ЗХ>if(a > b) ... //вызов A.opCmp(b) > 0
ЗХ>if(a <= b) ... //вызов A.opCmp(b) <= 0
ЗХ>if(a >= b) ... //вызов A.opCmp(b) >= 0
ЗХ>
ЗХ>...и вот эта фичка (несмотря на не совсем очевидное действие сравнения результата с нулем) мне кажется весьма достойной. Как и любые другие, позволяющие записывать всеобщие неявные соглашения (например: оператор != эквивалентен отрицанию оператора ==) в явном виде. Выигрыш имеем двойной: а) уверенность, что в чужом коде определены и корректно работают все операторы сравнение + б) отстутсвие необходимости повторять "руками" дурную работу. ЗХ>Впрочем, не исключаю, что для нетривиального класса определение оператора opCmp (учитывая все варианты возвращаемых значений) будет выглядет довольно нелаконично и даже искусственно.
Вот именно. В C++ мне время от времени приходится использовать в качестве ключей в map объекты, содержащие несколько полей:
Но в C++ я делаю только то, что мне нужно. А вот в D, как я понял, потребуется сделать Compound_Key.opCmp, который должен будет отслеживать не только "строго меньше", но и равенство, и "строго больше".
... << RSDN@Home 1.1.4 beta 6a rev. 436>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Обероны компактнее.
СГ>Если сложить полные описания всех более-менее известных оберонов, то, думается, описание языка D все равно будет больше их в несколько раз
На размер описания языка начхать всем кроме разработчиков компилятора.
А для пользователей языка важна компактность записи тех или иных конструкций в языке. А вот с компактностью записи у оберонов проблемы.
... << RSDN@Home 1.1.4 beta 6a rev. 436>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, Зверёк Харьковский, Вы писали:
ЗХ>Никто ж не спорит! Я что подчеркнул — что в D эта "традиция" присутствует в языке в явном виде, а не отдана воле разработчика.
Ну я еще намекнул на то что ребята както странно подходят к реализации одних операций сравнения через другие.
... << RSDN@Home 1.1.4 beta 6a rev. 436>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Обероны компактнее.
СГ>Если сложить полные описания всех более-менее известных оберонов, то, думается, описание языка D все равно будет больше их в несколько раз
Этот вопрос, IMHO, несколько месяцев назад разобрали достаточно основательно. Считаю, что нет смысла к нему возвращаться. К тому же здесь это оффтоп.
Здравствуйте, WolfHound, Вы писали:
WH>На размер описания языка начхать всем кроме разработчиков компилятора. WH>А для пользователей языка важна компактность записи тех или иных конструкций в языке. А вот с компактностью записи у оберонов проблемы.
Во-первых, не кипятитесь Вы так. Я же пошутил !!!
Во-вторых, хоть это была и шутка, но правда. Сражаться с Оберонами в компактности практически бесполезно. Существование семейства оберонистых языков было открыто способом с помощью которого можно было открыть существование только очень компактных языков (человек взял новый пустой (голый) компьютер и написал для него все от компилятора до операционки с нуля, понятно что сделать это за обозримое время можно только используя очень хороший инструмент, который собственно и был создан параллельно в ходе работы — так было открыто существование оберонов, необходимых и достаточных — минимальных языков программирования общего назначения).
Вот что пишут про одну из новейших оберонистых операционок:
For a native multiprocessor operating system, Aos is small, with a kernel of 7,210 lines of source or about 50KB of object code. For comparison, the 4.4BSD kernel (cf. 8.1.2) consists of 58,289 lines of C code (excluding file systems, network protocols and device drivers, which add another 143,962 lines) [65]. Version 2.4 of the Linux kernel consists of approximately 420,000 lines of C code (excluding drivers, file systems and architecture-specific code, which bring the total to 2.4 million lines) [128], and has a minimum size of around 500KB on Intel processors. Microsoft boasts that Windows 2000 “consists of over 29 million lines of code”, but does not say what is included in this figure, so it is not possible to compare specifics.
Subsystem Lines
Kernel 7210
Service support 2532
File system 4624
User interface 2204
Network 6200
Oberon for Aos 8667
Total 31437
Правда еще есть Lisp, который еще более компактен, но он, согласитесь, уже относится немного к другому классу языков программирования.
Здравствуйте, eao197, Вы писали:
ЗХ>>Впрочем, не исключаю, что для нетривиального класса определение оператора opCmp (учитывая все варианты возвращаемых значений) будет выглядет довольно нелаконично и даже искусственно.
E>Вот именно. В C++ мне время от времени приходится использовать в качестве ключей в map объекты, содержащие несколько полей: E>
E>Так вот в C++ мне достаточно определить только оператор "строго меньше"...
...что, по сути, является определением совсем другого оператора — оператора порядка, а не оператора сравнения. Ты не застрахован от удивления в клиентском коде, когда
Compound_Key a, b;
if(a < b) //так можноif(a > b) //а так почему-то нельзя :(
Я, когда мне нужно использовать в качестве ключей мапы класс, сравнение объектов которого бессмысленно, предпочитаю определять для этого свой функтор-компаратор, который не выглядит как operator<
Здравствуйте, Mamut, Вы писали:
M>[offtop]
M>На смену волне Обероновой пришла волна D
Ну я вроде стался быть конструктивен. Думал, эта информация будет интересна.
Цель этого мне видится не в ознакомлении всего РСДНа с новым языком — а в обсуждении новых или не очень концепций D и их реализации.
(с) я.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Обероны компактнее.
СГ>Если сложить полные описания всех более-менее известных оберонов, то, думается, описание языка D все равно будет больше их в несколько раз
Но является ли это однозначным преимуществом языка (в ущерб, скажем, выразительности) — вопрос глубоко спорный...
Здравствуйте, Зверёк Харьковский, Вы писали:
ЗХ>Здравствуйте, eao197, Вы писали:
ЗХ>>>Впрочем, не исключаю, что для нетривиального класса определение оператора opCmp (учитывая все варианты возвращаемых значений) будет выглядет довольно нелаконично и даже искусственно.
E>>Вот именно. В C++ мне время от времени приходится использовать в качестве ключей в map объекты, содержащие несколько полей: E>>
E>>Так вот в C++ мне достаточно определить только оператор "строго меньше"...
ЗХ>...что, по сути, является определением совсем другого оператора — оператора порядка, а не оператора сравнения. Ты не застрахован от удивления в клиентском коде, когда ЗХ>
ЗХ>Compound_Key a, b;
ЗХ>if(a < b) //так можно
ЗХ>if(a > b) //а так почему-то нельзя :(
ЗХ>
ЗХ>Я, когда мне нужно использовать в качестве ключей мапы класс, сравнение объектов которого бессмысленно, предпочитаю определять для этого свой функтор-компаратор, который не выглядит как operator<
Вообще-то я не думаю, что сравнение объектов, которые используются в качестве ключа map-а бессмыслено. Ведь в std::map ключи элементов как раз-таки сравниваются
А пример мой был к тому, что если в C++ мне потребуется сделать класс, от которого средства языка (std::{multi}map, std::{multi}set, std::find, ...) требуют только наличия operator<(), то я и реализую только это. И если от моего класса начинают требовать чего-то еще, то тут же получают по рукам.
В подходе же с opCmp я уже не могу просто сделать сравнение на "строго меньше". Мне в любом случае придется предоставлять варианты для случаев "равно" и "строго больше" (либо чесно их реализуя, либо используя что-то типа: Re: Снова D: Зверёк читает мануал