Это костыли а не подход.
В одних случаях указатель автоматически разъименовывается, в других нет, в третьих возникает конфликт и для получения указателя надо использовать @@ вместо @ и все это зависит от параметров компиляции и версии компилятора.
break'а в switch'е — искусственное ограничение, вызванное реализацией таблицы переходов
если бы в switch не было возможности перехода из одного case в следующий, то чтобы съемулировать такую возможность пришлось бы использовать goto или разделение на функции
За 6 лет, что я пишу на delphi по работе, единственное ограничение, которое изредка досаждает — отсутствие полноценных шаблонов. И то, частично это реализовано.
Шаблоны в pascal ещё 25 лет назад эмулировались с помощью include.
А вот что меня сейчас досаждает — то что generic-и в java никогда не будут иметь возможностей шаблонов C++, поэтому приходится заменять статическую типизацию на динамическую выдумывая как бы при этом сохранить типобезопасность.
Ничто не мешает писать структурировано и читабельно на языке в котором много плюшек.
Не хотите — ну не пользуйтесь всеми плюшками. Но не говорите за всех.
То же, что и с Си — мол, много способов «выстрелить в ногу». Хочешь — стреляешь, не хочешь — делаешь нормально.
Я имел в виду синтаксические костыли.
Ну вот и ответ, в принципе: с классами это было людям нужно — добавили способ forward описания. С функциями — не было нужно.
Это называется — отсутствие общего подхода. Если бы язык был как-то стандартизирован, такой фигни бы не было. А так — язык одного актёра разработчика.
С секцией var проблем не вижу: мне нравится, когда код структурирован. Тут — объявления, тут — код.
Вам ничто не мешает писать так и в C++ и игнорировать удобство RAII. Но это же не значит что надо превозносить такое искусственное ограничение языка.
попробуй отладь потом такой код
ограничения отладчика конечно важны, но тоже не являются непреодолимым барьером.
Например я лет 10 назад перешёл с idea на eclipse потому что у тогдашней idea были проблемы с отладкой анонимных классов. В случае же delphi не получится сменить ide или компилятор, поскольку, увы, альтернатив нет.
допустимость обработки указателей на тип до определения самого типа
Это нормально. Указатель приводится к чему угодно.
Это ужасающе в плане придумывания костылей.
Почему forward-определения указателя недостаточно для класса?
Потому что класс и так передаётся только как ссылка.
Поэтому мы сделаем новый тип forward определений для класса.
И для интерфейсов тоже.
Но функция тоже ссылочный тип. А что с функциями?
Да и фиг с ними — добавлять еще одни костыли глупо, а сделать по-нормальному — сложно.
отделение интерфейсов от реализации
Это тоже замечательно — мне нравится такое разделение. Напротив, мешанина — нечитабельна.
Да уже не осталось языков которые требуют такого отделения. Это всё анахронизмы, как и секция var.
Конечно. 99% случаев покрывается, а 1%м вроде синтетического примера выше можно поступиться.
Если какая-то простая конструкция из-за какой-то несусветной причины не может быть описана на языке — то это очень говорит о продуманности такого языка, тем более языка, претендующего на академичность.
Без констант будет не эквивалентный код так как дефолтные значения буду размазаны по всем классам.
Тогда и без сгенерированных геттеров, сравним — мы же сравниваем только именованные параметры vs builder?:
1: public BaseClass(int first = -1...
2: First =
3: first;
против
1: public int first = -1
2: protected int first;
3: first =
4: src.first;
builder проигрывает на одно упоминание в базовом классе
1: public ChildClass(int first = -1...
2: base(first...
против
0:
в производном builder выигрывает на два упоминания
итого builder выигрывает на одно упоминание даже у кода с размазанными по всем классам константами
Это всё равно что оптимизировать в 10 раз скорость выполнения кода, который отнимает меньше 1% времени.
Повторюсь — поэтому надо самому определять что удобнее в каждой ситуации.
А наследование в общем-то для того и нужно, чтобы не дублировать код — странно было бы не пользоваться им при необходимости.
Да, из-за однопроходности компилятора необходима специальная синтаксическая возможность для обхода ограничений однопроходности. Это forward реализации функций, допустимость обработки указателей на тип до определения самого типа, отделение интерфейсов от реализации и вот такое специальное опережающее описание класса. Ну и стоит ли овчинка однопроходности выделки такой ценой?
Вы будете ради двух-трёх аргументов заводить отдельный класс ради использования один раз?
Это усложняет восприятие кода.
Нет конечно.
Но когда когда количество аргументов функции увеличится вместе с увеличением числа пробросов этих аргументов то для упрощения кода я скорее всего объединю их в класс.
Всё зависит от того что в итоге получается удобнее — пробрасывать каждый раз все параметры но увеличить количество классов, или каждый раз пробрасывать все параметры не увеличивая количество классов.
Откройте файл, нажмите Ctrl+F, введите «first» и посчитайте. Вот это всё — дублирование.
Только мы обсуждаем сейчас не автоматическую генерацию геттеров, а именованые параметры vs объекты.
Вообще-то дублирование параметров — неизбежное зло. Вам никогда не приходилось прокидывать аргументы через несколько вызовов по цепочке?
Нет никакого неизбежного зла — при необходимости часть или весь список параметров превращается в один объект.
Зависит от того насколько то или иное удобно.
Вы так говорите про дублирование, как будто слово «first» повторяется в джавовом коде реже, чем в шарповом.
Слово first встречается только при певром упоминании — в первом объекте и билдере.
Хоть как-то приближается к избавлению от дублирования только планируемый синтаксис C# 7
Этот сахар опять только для первого объявления а не для всех последующих.
Для меня 30–50% — это очень даже ощутимое число. И, как я уже сказал, различия в количестве классов даже более важны, чем слоки, байты и непробельные символы.
Там не было 50% — максимум 40%. Количество классов само по себе ничему не вредит — например мы пользуемся в больших количествах анонимными классами просто потому что так удобнее.
Что будет в случае следующих рефакторингов?
Ну, будет работа не на часик, а на два. Вроде, даже решарпер не очень поможет, потому что полноценная обработка цепочек конструкторов не реализована. Фиче-реквест был, но, вроде, до него ещё не добрались.
В случае выделенного объекта билдер это простые контролируемые компилятором операции.
Я ж уже говорил: это весьма эфемерный бонус. Тип и имя поля-то вы измените, даже сэкономите 50% времени относительно грязной версии с копипастой, но ведь всё равно основное время будет убито на поиск и исправление всех использований во всём коде. Все перечисленные вами изменения ломают обратную совместимость, не получится изменить только одно место.
Т.е. вы отвечаете примерно так: «Да, рефакторинга никакого быть не может. Значит и вам он не нужен.» или что «раз придётся копировать код, то пусть так и будет».
Я всё же не столь категоричен, т.к. выбор зависит от ситуации — надо только описать как преимущества так и недостатки обоих подходов.
Это скомпилируется
Но если вы попытаетесь определить такую функцию:
function functionChain(const value: String): PFunctionChain;
begin
writeLn(value);
result:= @functionChain;
end;
то в lazarus это скорее всего вообще не соберется
а в delphi если и соберется то может свалится при выполнении
да и код использования будет какой-то не такой:
functionChain('1')^('2')^('3');
чтобы сделать код использования таким:
functionChain('1')('2')('3');
можно попытаться объявить функцию как возвращающую TFunctionChain а не PFunctionChain
function functionChain(const value: String): TFunctionChain;
но тогда может не скомпилироваться код
functionChain('1')('2')('3')
или
functionChain('1')^('2')^('3');
но при этом возможно сможет скомпилироваться код:
functionChain('1')^('2')('3');
На некоторых версиях это может работать, даже на совсем древних.
Конструкция конечно вложенная но инициализации тут одноуровневые.
Вот например сначала отдельно инициализируется класс, который потом используется при инициализации Foo
new Foo {
Foo = new Foo {...}
}
А это уже похоже:
new Foo { Foo = { Foo =… } }
А как инициализировать List<List> или Dictionary<Foo, List>?
Выводы: В худшем синтетическом случае кода больше на 40% и то это сокращение в основном из-за getter-ов. В реальном коде увеличение кода даже в таком минимальном варианте (только два класса) не будет более чем на треть.
Как я уже сказал, этот код вручную писать необязательно. «Написание» конструктора для класса-потомка у меня свелось к аккорду
Обычно надо не только сгенерить код но и поддерживать его.
Что будет в случае следующих рефакторингов?:
Изменение типа поля.
Изменение названия поля.
Изменение значения по умолчанию.
Перенос поля из одного класса в другой.
В случае выделенного объекта билдер это простые контролируемые компилятором операции.
А как с параметрами?
Когда я оптимизировал работу с BigData мне приходилось отказываться не только от LinkedList, но даже от HashMap и ArrayList и работать тупо с массивами.
Интересно. ArrayList же просто обёртка над массивом. Что в нём было неэффективно и возможна ли другая более эффективная обёртка над массивом?
И чем вы заменили HashMap? Параллельными сортированными массивами? И возможна ли эффективная обёртка?
ДВС + подруливать электромоторами: http://www.youtube.com/watch?v=osGtTZKcY_0
другие варианты с ДВС пока не летают: http://www.youtube.com/channel/UCDl88VBM_bCazsQHv7vq4Pw/videos
http://www.youtube.com/channel/UCA4v1RYZx5Ew4i9Ko8fiLMw/videos
http://www.youtube.com/watch?v=Vkg23t3YaDQ
В одних случаях указатель автоматически разъименовывается, в других нет, в третьих возникает конфликт и для получения указателя надо использовать @@ вместо @ и все это зависит от параметров компиляции и версии компилятора.
если бы в switch не было возможности перехода из одного case в следующий, то чтобы съемулировать такую возможность пришлось бы использовать goto или разделение на функции
Шаблоны в pascal ещё 25 лет назад эмулировались с помощью include.
А вот что меня сейчас досаждает — то что generic-и в java никогда не будут иметь возможностей шаблонов C++, поэтому приходится заменять статическую типизацию на динамическую выдумывая как бы при этом сохранить типобезопасность.
Ничто не мешает писать структурировано и читабельно на языке в котором много плюшек.
Не хотите — ну не пользуйтесь всеми плюшками. Но не говорите за всех.
Это называется — отсутствие общего подхода. Если бы язык был как-то стандартизирован, такой фигни бы не было. А так — язык одного
актёраразработчика.Вам ничто не мешает писать так и в C++ и игнорировать удобство RAII. Но это же не значит что надо превозносить такое искусственное ограничение языка.
ограничения отладчика конечно важны, но тоже не являются непреодолимым барьером.
Например я лет 10 назад перешёл с idea на eclipse потому что у тогдашней idea были проблемы с отладкой анонимных классов. В случае же delphi не получится сменить ide или компилятор, поскольку, увы, альтернатив нет.
Почему forward-определения указателя недостаточно для класса?
Потому что класс и так передаётся только как ссылка.
Поэтому мы сделаем новый тип forward определений для класса.
И для интерфейсов тоже.
Но функция тоже ссылочный тип. А что с функциями?
Да и фиг с ними — добавлять еще одни костыли глупо, а сделать по-нормальному — сложно.
Да уже не осталось языков которые требуют такого отделения. Это всё анахронизмы, как и секция var.
Если какая-то простая конструкция из-за какой-то несусветной причины не может быть описана на языке — то это очень говорит о продуманности такого языка, тем более языка, претендующего на академичность.
Тогда и без сгенерированных геттеров, сравним — мы же сравниваем только именованные параметры vs builder?:
против
builder проигрывает на одно упоминание в базовом классе
против
в производном builder выигрывает на два упоминания
итого builder выигрывает на одно упоминание даже у кода с размазанными по всем классам константами
Повторюсь — поэтому надо самому определять что удобнее в каждой ситуации.
А наследование в общем-то для того и нужно, чтобы не дублировать код — странно было бы не пользоваться им при необходимости.
это синтетический пример для демонстрации проблем однопроходного компилятора
Я предполагал что это могло бы выглядеть наподобие:
Нет конечно.
Но когда когда количество аргументов функции увеличится вместе с увеличением числа пробросов этих аргументов то для упрощения кода я скорее всего объединю их в класс.
Всё зависит от того что в итоге получается удобнее — пробрасывать каждый раз все параметры но увеличить количество классов, или каждый раз пробрасывать все параметры не увеличивая количество классов.
Только мы обсуждаем сейчас не автоматическую генерацию геттеров, а именованые параметры vs объекты.
Нет никакого неизбежного зла — при необходимости часть или весь список параметров превращается в один объект.
Зависит от того насколько то или иное удобно.
Слово first встречается только при певром упоминании — в первом объекте и билдере.
Этот сахар опять только для первого объявления а не для всех последующих.
Там не было 50% — максимум 40%. Количество классов само по себе ничему не вредит — например мы пользуемся в больших количествах анонимными классами просто потому что так удобнее.
Т.е. вы отвечаете примерно так: «Да, рефакторинга никакого быть не может. Значит и вам он не нужен.» или что «раз придётся копировать код, то пусть так и будет».
Я всё же не столь категоричен, т.к. выбор зависит от ситуации — надо только описать как преимущества так и недостатки обоих подходов.
Но если вы попытаетесь определить такую функцию:
то в lazarus это скорее всего вообще не соберется
а в delphi если и соберется то может свалится при выполнении
да и код использования будет какой-то не такой:
чтобы сделать код использования таким:
можно попытаться объявить функцию как возвращающую TFunctionChain а не PFunctionChain
но тогда может не скомпилироваться код
или
но при этом возможно сможет скомпилироваться код:
На некоторых версиях это может работать, даже на совсем древних.
Вот например сначала отдельно инициализируется класс, который потом используется при инициализации Foo
new Foo {
Foo = new Foo {...}
}
А это уже похоже:
new Foo { Foo = { Foo =… } }
А как инициализировать List<List> или Dictionary<Foo, List>?
Создание новой функции приведёт к дублированию всего описания параметров. А в случае билдера — нет.
Сделал одинаковое форматирование и добавил на github: github.com/speaking-fish/java-sf-builder-simple-example/tree/master/comparison
Получилось следующее:
Полный код:
C#: 41 lines (28 sloc) 1.217 kB
Java: 51 lines (35 sloc) 1.616 kB
Java +24% lines +25% sloc +32% байт
Без toString и main:
C#: 30 lines (21 sloc) 0.813 kB
Java: 42 lines (29 sloc) 1.13 kB
Java +40% lines +38% sloc +39% байт
Без полей и getter-ов — рассматриваем только разницу именованные параметры / билдер:
C#: 24 lines (17 sloc) 0.674 kB
Java: 32 lines (22 sloc) 0.875 kB
Java +33% lines +29% sloc +30% байт
Выводы: В худшем синтетическом случае кода больше на 40% и то это сокращение в основном из-за getter-ов. В реальном коде увеличение кода даже в таком минимальном варианте (только два класса) не будет более чем на треть.
Обычно надо не только сгенерить код но и поддерживать его.
Что будет в случае следующих рефакторингов?:
Изменение типа поля.
Изменение названия поля.
Изменение значения по умолчанию.
Перенос поля из одного класса в другой.
В случае выделенного объекта билдер это простые контролируемые компилятором операции.
А как с параметрами?
Интересно. ArrayList же просто обёртка над массивом. Что в нём было неэффективно и возможна ли другая более эффективная обёртка над массивом?
И чем вы заменили HashMap? Параллельными сортированными массивами? И возможна ли эффективная обёртка?
другие варианты с ДВС пока не летают: http://www.youtube.com/channel/UCDl88VBM_bCazsQHv7vq4Pw/videos
http://www.youtube.com/channel/UCA4v1RYZx5Ew4i9Ko8fiLMw/videos
http://www.youtube.com/watch?v=Vkg23t3YaDQ