Ссылки и указатели вне параметров процедур
Кроме того, что они запутывают код, они приводят по крайней мере к двум проблемам:
1. Затрудняют сериализацию объектов.
2. Приводят к потребности в хитром сборщике мусора.
Вопрос: почему от них до сих пор не отказались? Что в них хорошего?
Я уж три примера привёл:
1. Один (или несколько) объект хранит в себе ссылку на другой, который создал не он. В результате, у нас неопределенное время на один объект ссылается несколько, что создаёт лишние связи, т.е. спагетти. Если бы ссылка на второй объект передавалась только в момент использования (то есть как параметр процедуры), то лишних связей было бы меньше, и структура данных была бы менее монолитной.
Понятно, что объекты, ссылающиеся на другие объекты сложнее сериализовать. Понятно, что такие связи требуют автоматической сборки мусора.
2. Если встроенная функция имеет доступ к данным внешней, то опять получается монолит двух функций и запутанные связи с данными. Ещё запутаннее может быть, если мы вернём внутреннюю функцию. Ну и понятно, что тут нужен сборщик мусора.
3. Возвращаемая ссылка создаёт дополнительную связь, если она ссылается на объект-параметр процедуры. Если же она ссылается на новый объект, созданный в теле процедуры, то это терпимо, если не учитывать потребность в сборщике мусора.
Если бы не было таких возможностей, то появилась бы потребность четче определять кто чьим родителем является, что повысило бы чёткость структуры программы.
Если бы не было таких "фич", то объекты могли бы удаляться родителями, их было бы проще сохранять или передавать их копию (с конкретным состоянием) в другой процесс.
Бывает. Кстати, поэтому почти нет смысла приводить код - почти все языки пропитаны идеологией которую я критикую. Можно лишь картинки рисовать.
1. Один (или несколько) объект хранит в себе ссылку на другой, который создал не он. В результате, у нас неопределенное время на один объект ссылается несколько, что создаёт лишние связи, т.е. спагетти.
Что в этом плохого?
Сложнее чем что? Вообще, при чем тут сериализация?
3. Возвращаемая ссылка создаёт дополнительную связь, если она ссылается на объект-параметр процедуры. Если же она ссылается на новый объект, созданный в теле процедуры, то это терпимо, если не учитывать потребность в сборщике мусора.
Как по твоему нужно реализовывать замыкания?
В одном знакомом мне языке есть структура данных "список":
Этот код эквивалентен следующему:
Кто здесь родитель?
У этого списка можно легко заменить голову:
{
| head :: tail => 10 :: tail
| [] => 10 :: [];
}
tail - это объект созданый с помощью list.Cons(2, ...). Экономия памяти на лицо.
Не все структуры данных обладают связью родитель-дитя. Взгляни на любую рекурсивную структуру данных из функционального мира.
В том, что мы не сможем выделить часть. В том, что при рефакторинге мы не сможем проследить все связи и у нас останется больше неиспользуемых кусков, чем это было бы без таких связей.
Если объект простой (то есть не содержит ссылки на другие объекты), то все его поля можно сохранить просто. Если он ссылается на другие объекты, то по хорошему надо сохранять и их.
Никак. Либо передавать нужные параметры в процедуру, либо использовать объекты-генераторы.
Я не утверждаю, что языки с замыканиями и сборщиками мусора совсем не нужны, но мне не ясно, почему же популярна лишь такая идеология.
Этот код эквивалентен следующему:
Кто здесь родитель?
Если я правильно понимаю, то тут у тебя фабрика. В труъ-императивном языке объект бы создался конструктором, а фабрика бы изменила этот объект в соответствии со своими параметрами.
Но в любом случае - родитель тут не конструктор списка и не фабрика, а процедура, в которой создался этот объект, либо другой объект, в конструкторе которого был создан этот.
{
| head :: tail => 10 :: tail
| [] => 10 :: [];
}
tail - это объект созданый с помощью list.Cons(2, ...). Экономия памяти на лицо.
Зачем ты это привел мне не понятно.
Именно по этому я в первом сообщении и упомянул, что речь идёт об императивных языках. И там все вызовы процедуры, использующую рекурсию, могут обращаться к одним и тем же данным, переданным как параметры.
А нет, похоже неправильно понимаю. Это у вас конструктор такой: Cons? Мда...
Часть чего? В настоящий момент я рефакторю тонны говнокода с дичайшей степенью связности (из инструментов лишь анализатор ILSpy) и как-то вполне все связи прослеживаются.
В нормальных сериализаторах объекты, на которые ссылается текущий объект также сериализуются. Из сложностей лишь разруливание циклических ссылок (реализуется банальным словарем).
Я не утверждаю, что языки с замыканиями и сборщиками мусора совсем не нужны, но мне не ясно, почему же популярна лишь такая идеология.
Потому что другой идеологии не существует.
Но в любом случае - родитель тут не конструктор списка и не фабрика, а процедура, в которой создался этот объект, либо другой объект, в конструкторе которого был создан этот.
list - это вариантный тип данных. Cons - это один из конструкторов.
Именно по этому я в первом сообщении и упомянул, что речь идёт об императивных языках. И там все вызовы процедуры, использующую рекурсию, могут обращаться к одним и тем же данным, переданным как параметры.
"Императивность" - это способ записи алгоритмов. Структуры данных во всех известных мне языках описываются декларативно.
Часть - один из объектов. Чем больше ниточек его соединяют с другими тем сложнее его выделить.
Я думаю, что ты и с GOTO неплохо бы справился.
Это нормально, когда мы сохраняем состояние программы на жесткий диск. Но это не нормально, когда мы хотим передать копию объекта в другой процесс. Неэффективно же.
Я уж понял. Но это не имеет значения.
Не совсем так. Это ещё и способ работы с данными. Императивность подразумевает изменчивость данных. Кстати, именно поэтому чистый императивный язык эффективнее по использованию памяти, чем чистый функциональный (в котором нет даже скрытой императивности).
Это нормально, когда мы сохраняем состояние программы на жесткий диск. Но это не нормально, когда мы хотим передать копию объекта в другой процесс. Неэффективно же.
Можно передавать не копию, а ссылку на объект (в клиентском процессе рантайм сделает прокси).
Это назывется мутабельность структур данных. Она напрямую с "функциональностью" не связана. Для справки: функциональный стиль программирования требует, чтобы результат функции был детерминирован и зависел лишь от входных параметров.
Пожалуй соглашусь, чистый функциональный язык - это язык свободный от побочных эффектов, но такие языки совершенно бесполезны.
Ну а прокси то как работает? Разве он не гоняет туда-сюда состояние объекта?
В функциональном стиле я не очень разбираюсь, но про императивный читал, что одна из его характерных черт - наличие переменных с операцией "разрушающего присвоения". И там, где такое есть, рекурсивная процедура может изменять одни и те же данные по кругу. И тут родителем этих данных будет одна из внешних процедур.
Я намеренно использую термин "процедура", чтобы показать, что она не возвращает значений в отличие от функций.
Нет. Вспомни тот же DCOM.
Тогда возможно стоит поднять вопрос о необходимости императивного подхода?
Кстати, этот вопрос эквивалентен такому: для чего в современных языках используют динамическую память (т.е. кучу)?.
И что? Этот твой DCOM не использует маршаллинг? Или использует особый магический?
Если говорить на рабоче-крестьянском, то для передачи в другой процесс объект должен преобразоваться в поток байт, а затем из потока байт обратно восстановиться. Прокси же должен имитировать то, что у нас один объект. Всё остальное - магия. Или я не понимаю чего-то?
Я не хочу спорить на тему "императивный подход vs какой-то другой". Меня интересует целесообразность использования упомянутых ссылок.
Нет. Это касается только конкретных языков, типа C/C++. Но даже на C++ это не полностью распространяется, так как он может использовать кучу неявно через контейнеры STL, не используя ссылки или указатели.
В других языках (например, Python) пользователь может вообще не догадываться, какую память он использует.
Не всю агрегацию. Композицию я одобряю, то есть случай, когда часть и целое живут одной жизнью и уничтожаются вместе.
Но агрегацию, в которой после уничтожения целого часть продолжает жить - я ставлю под сомнение.
Поясню. При правильном проектировании, родитель - это объект (или функция), которому дочерний объект нужен больше, чем другим. Родитель ответственен за дочерние объекты. Другие объекты используют дочерние лишь частично (относительно небольшие промежутки времени), поэтому им можно передавать ссылки на эти объекты как параметры методов.
Если же другим объектам передавать ссылку на дочерний объект стороннего родителя (например, в конструкторе), то эти другие будут, образно говоря, "няньками". И пока не будут уничтожены все "няньки", дочерний объект будет жить. При том далеко не факт, что какая-либо "нянька" будет использовать дочерний объект после уничтожения родителя. И при том, у каждой "няньки" от этого усложнится набор полей, "няньку" будет сложно использовать без родительского объекта.
Если совсем образно, то "у семи нянек дитя без глазу".
Композицию я не ставлю под сомнение.
А разве в Питоне списки не также устроены?
Когром, в прошлой своей теме про публичность всего и вся тебе многие неоднократно говорили, что таким образом ты создаёшь предпосылки для возможного лишнего связывания разных частей кода. А тут ты вдруг ратуешь за минимум связности. Осознал, в чём тогда была одна из ошибок?
Ой, недоброе он замышляет! Может, забаним, пока не поздно? :)
Ой, недоброе он замышляет! Может, забаним, пока не поздно? :)
Забанить нельзя, он же модер. Терпите, кексы. :)
Как определить "нужен больше"?
Если же другим объектам передавать ссылку на дочерний объект стороннего родителя (например, в конструкторе), то эти другие будут, образно говоря, "няньками". И пока не будут уничтожены все "няньки", дочерний объект будет жить. При том далеко не факт, что какая-либо "нянька" будет использовать дочерний объект после уничтожения родителя. И при том, у каждой "няньки" от этого усложнится набор полей, "няньку" будет сложно использовать без родительского объекта.
Если совсем образно, то "у семи нянек дитя без глазу".
В системах без сборки мусора эта проблема решается подсчетом ссылок (со всеми вытекающими), либо копированием. Но вообще сборка мусора - это наиболее абстрактная схема управления памятью, так как отлично ложится на концепцию "объект существует пока он нужен". Критерий "нужности" - достижимость объекта из корней сборщика мусора (точек в памяти, откуда GC начинает обход графа объектов). Т.е. этот подход - обобщение твоего предложения.
Там без Cons наружу. Есть __init__, но он только внутри класса.
Там я говорил про функции, а тут я говорю про данные. Есть разница.
И да, я не говорил про публичность всего и вся, а лишь про правильную организацию инкапсуляции.
Через списки, контейнеры, словари и т.д. Пусть у них внутри будут указатели и прочие низкоуровневые детали - пользователю не обязательно об этом знать.
Инкапсуляцию я уважаю, ссылки тоже. Но всё должно быть на своих местах.
С помощью проектирования. То есть, использовав шаблон Creator из GRASP.
Это далеко не идеальный критерий. Его достоинство в том, что он позволяет создавать системы разгильдяйским способом. Это нормально для языков программирования, которые подразумевают создание прототипов программ, ибо прототип можно написать и выкинуть. Но при разработке предсказуемого ПО с ясным кодом такой подход теряет свою привлекательность.
Через списки, контейнеры, словари и т.д. Пусть у них внутри будут указатели и прочие низкоуровневые детали - пользователю не обязательно об этом знать.
Отлично. Я пользователь, и хочу создать структуру данных "разряженный массив". Как быть?
С помощью проектирования. То есть, использовав шаблон Creator из GRASP.
Чего? В каждом классе на ровном месте наворачивать "шаблоны проектирования"?
Практика показывает обратное. Утечек памяти в рантаймах со сборкой мусора значительно меньше, чем в системах с ручным управлением памятью.
Чем не устраивает словарь?
Если требуется аккуратная работа, то да. Для крупных систем это даст выигрыш в ясности кода.
А кто говорит про системы с ручным управлением памятью?
Тем что это словарь. Не надо уходить от темы. Я пользователь и хочу реализовать собственную структуру данных. Как мне быть?
Все вокруг так и делают что пишут крупные системы. Язык должен быть масштабируем - небольшие программы должно быть писать легко. Большие - тоже легко. Выигрышь в ясности кода дают соглашения об именовании и форматировании + регулярные ревью.
Ты предлагаешь уйти от обобщенной модели сборки мусора (которая умеет работать с графами) и от ручной, которая еще более гибкая, к иерархической модели основаной на модели "родитель-дитя". А что если мне нужно держать в памяти не дерево, но граф?
Ты спрашивал про разряжённый массив. Насколько я понимаю, это разновидность словаря. Заключи словарь в классовую обёртку и выведи наружу требуемые методы.
Если требуется что-то нестандартное, то можно воспользоваться модулем, написанным на низкоуровневом языке. Все высокоуровневые языки к такому прибегают.
Кому должен? Вон баш плохо масштабируем и ничего, пользуется популярностью.
Зачем тогда нужны языки кроме ассемблера?
1. Подключай нужные звенья динамически. Зачем нужно, чтобы они были постоянно переплетены?
2. Если нужна статическая структура, то можно использовать словари.
3. Если первые два пункта не подошли, то у тебя какая-то специфическая задача и можно воспользоваться другим языком. Но за решение этой специфической задачи мы платим бардаком в коде, в котором она не нужна.
Словарь можно реализовать минимум 3 способами (какой из них имеешь в виду ты?). А меня не устраивает ни один из них для реализации разреженного массива.
Чтобы реализовать разреженный массив на C# или Nemerle мне не то что не придется писать на C, мне даже указатели не потребуются.
На баше не создают, с одной стороны, программки с одной кнопкой для мобильника и, с другой, - корпоративные системы документооборота. А вот Java или C# вполне годятся (хотя им до идеала как до Альфацентавара на гужевой повозке).
Толсто.
Не делай в программе ошибки. Зачем делать их в программах?
Только массовые словари спасут демократию в этой стране. :)
Для лаконичного и наглядного решения крайне специфических задач (описание парсера к примеру, введение новой контейнерной структуры данных - не тот случай) нужно иметь возможность расширять базовый язык. Впрочим тут уже какойто оффтоп.
1. Подключай нужные звенья динамически. Зачем нужно, чтобы они были постоянно переплетены?
2. Если нужна статическая структура, то можно использовать словари.
3. Если первые два пункта не подошли, то у тебя какая-то специфическая задача и можно воспользоваться другим языком. Но за решение этой специфической задачи мы платим бардаком в коде, в котором она не нужна.
Я так и не понял, как реализовать граф, в узлах которого помимо некоторых специфичных данных можно будет хранить список входящих и исходящих ребер?
var a : A;
begin
a := A.Create();
x.Add(a);
y.Add(a);
end;
Разряжённый массив - это ассоциативный массив. Синонимом ассоциативного массива является словарь.
Говори, что тебе надо конкретно, с подробностями.
Уж не ответил ли ты тут сам на свой вопрос про создание разряженного массива? Можешь показать реализацию на C#?
Программа с одной кнопкой для мобильника может быть сколько угодно большой программой.
Кроме того, баш + dialog - даст приложение с одной кнопкой наверняка компактнее, чем в C# или Java.
Я другими словами выразил твой вопрос. Значит и у тебя было толсто.
Не обязательно. Просто это решение наиболее простое решение, которое можно привести в пример.
А можно просто создать DSL.
В виде схемы же, которой он и является. Схему можно реализовать на словарях. Не нравится словарь - можно даже и на списках. Да можно и в виде строки. В чём проблема?
var a : A;
begin
a := A.Create();
x.Add(a);
y.Add(a);
end;
Процедура F является родителем объекта a. Копии этого объекта передаются в списки x, y. Объект a уничтожается с завершением процедуры. Копии объекта a уничтожатся при уничтожении x, y.
Идем дальше. Объект A занимает в памтяти, скажем, 200 КБ (допустим изображение у него там в полях, или строка текста большая).
Т.е. перед строкой end; у нас в памяти ТРИ объекта A размером в 200 КБ?
Т.е. перед строкой end; у нас в памяти ТРИ объекта A размером в 200 КБ?
Ты так рассуждаешь, потому что к этому тебя приучили современные ЯП. Если у нас там изображение или большие строки, то мы используем один объект (или массив) с картинками и передаем его во временное пользование другим объектам через параметры процедур.
Говори, что тебе надо конкретно, с подробностями.
Допустим нужно быстро вставлять и доставать тысячи последовательных элементов, но в диапазоне индексов от [0 .. 1 000 000 000). Формулировка, конечно, высосана из пальца (точнее, гдето в вопросах на RSDNе проскакивала), но словари тут не помощники.
Кроме того, баш + dialog - даст приложение с одной кнопкой наверняка компактнее, чем в C# или Java.
Критерий компактности в студию.
Вообще все ООП и даже больше можно на словарях организовать (см JavaScript), только из этого ничего хорошего почему-то не выходит. Еще раз повторяю: не все объекты находятся в зависимости "родитель-дитя". Пример тому - граф.
А как тогда такие большие объекты складывать в коллекции? У тебя же там копирование на каждом шагу?
Может и так. Но решение наверняка будет реализовываться на основе стандартных контейнеров. Или нет?
Да я тебе лучше пример приведу:
DIALOG=${DIALOG=dialog}
$DIALOG --title " Мой первый диалог" --clear \
--yesno "Привет! Перед вами пример программы,\nиспользующей (X)dialog" 10 40
case $? in
0)
echo "Выбрано 'Да'.";;
1)
echo "Выбрано 'Нет'.";;
255)
echo "Нажата клавиша ESC.";;
esac
За корректность не отвечаю - скопипастил. Но как-то так должно быть. Кратко и наглядно.
Конечно не все объекты находятся в такой зависимости. Но ничто им не мешает подключаться на определённые промежутки времени в соответствии с определённой схемой. Более того, при моем подходе объекты могут существовать только в моменты, когда связь понадобится.
Элементарно. Коллекция создает в себе новый объект. Меняем состояние этого объекта, если требуется.
Что происходит с большим содержимым объекта A из примера?
Смотри псевдокод:
Добавить к коллекции новый объект типа А в котором создастся картинка из файла Б.
Объект А удалится вместе со всей коллекцией или по специальной команде типа erase.
Да. Если считать одномерные массивы стандартными контейнерами. На их основе реализованы все остальные коллекции (за исключением, конечно, деревьев и связных списков).
DIALOG=${DIALOG=dialog}
$DIALOG --title " Мой первый диалог" --clear \
--yesno "Привет! Перед вами пример программы,\nиспользующей (X)dialog" 10 40
case $? in
0)
echo "Выбрано 'Да'.";;
1)
echo "Выбрано 'Нет'.";;
255)
echo "Нажата клавиша ESC.";;
esac
За корректность не отвечаю - скопипастил. Но как-то так должно быть. Кратко и наглядно.
Такую фигню даже на VB можно сделать с неменьшей наглядностью и без всяких магических чисел
using System.Windows.Forms;
match(MessageBox.Show(
text = "Привет! Перед вами пример программы,\nиспользующей (X)dialog",
caption = "Мой первый диалог",
buttons = MessageBoxButtons.YesNoCancel))
{
| DialogResult.Yes => WriteLine("Выбрано 'Да'")
| DialogResult.No => WriteLine("Выбрано 'Нет'")
| _ => WriteLine("Диалог отменен")
};
И как определять когда же связь нужна?