Oberon space
General Category => Общий раздел => Тема начата: vlad от Февраль 17, 2011, 03:43:16 am
-
Уже полгода на моем рабочем столе лежит презентация The Next Mainstream Programming Language:A Game Developer’s Perspective (http://www.google.ru/url?sa=t&source=web&cd=1&sqi=2&ved=0CBcQFjAA&url=http%3A%2F%2Fwww.cs.princeton.edu%2F~dpw%2Fpopl%2F06%2FTim-POPL.ppt&rct=j&q=%22Game%20Development%3A%20Gears%20of%20War%22&ei=kN1aTYuNPMqBOpLjvZ0L&usg=AFQjCNH1lYwuSG7QJy3ShTC6uRB2biG-ag&sig2=zxt4pID-HdB083eGOMEO2w&cad=rja) - крик души разработчика игр. Лежит как напоминание о том, что хороший структурный редактор должен соответствовать этим ясно выраженным потребностям. Должен признать, что предложенные автором решения мне понравились. А компьютерные игры - это передовой край Computer Science, эталон пригодности инструментального программного обеспечения к решению сложных задач.
Если вкратце, то проблемы (и их решение) следующие:
- ошибочная перезапись в оперативной памяти (решенная проблема)
- утечки памяти (решенная проблема)
- висячие указатели (сборщик мусора обязателен)
- выход индекса за границы массива (нужен статический контроль компилятора)
- разыменование нулевых указателей (нужен статический контроль компилятора)
- доступ к неинициализированным переменным (нужен статический контроль компилятора)
- целочисленное переполнение (нужен целочисленный тип данных переменной длины, причем если длина стандартная, то в ячейке хранится само значение, а если длина больше стандартной - указатель на ячейку со значением)
- не поддерживаются параллельные вычисления (необходимы локальные кучи, отсутствие побочных эффектов)
По поводу нулевых указателей и неинициализированных переменных. Я уже выступал на оригинальном форуме (http://forum.oberoncore.ru (http://forum.oberoncore.ru)) и наверняка есть ЯП в которых эта проблема в каком то виде решена, но если подытожить применительно к оберону и структурному редактору, то основные тезисы такие:
- Неинициализированных переменных нет, потому что объявление переменной совмещено с ее инициализацией синтаксически. A-ля VAR i: INTEGER := 3.
- Указатели по умолчанию ненулевые (обязаны быть проинициализированы реальным объектом или другим ненулевым указателем).
- Можно объявить нулевой указатель. Это другой тип, совместимый с ненулевым указателем в одну сторону: ненулевой указатель может быть приведен к нулевому, но не наоборот. Нулевой указатель не может быть разыменован.
- Для получения ненулевого указателя из нулевого есть специальная синтаксическая конструкция вроде обероновской проверки/приведения типа с помощью WITH. Если проверка сработала, то выполняется ветка кода, где "виден" ненулевой указатель.
-
В том моём сообщении, исковерканном Темиргалеевым (да ещё и с оскорбительной уничижительной пометкой "экспертное" -- именно в кавычках) я по пунктам ответил Сергею Прохоренко, что фактически ничего этого в КП/ББ нет, даже сборщик мусора -- и тот не реалтаймный (GC, замораживающий мир, в интерактивных играх типа Unreal неприемлем).
-
Я вообще говоря долго думал о эффективном и при этом безопасном языке. Безопасный -- я имею ввиду проверка на ошибки на этапе компиляции. Эффективный -- проверка ИСКЛЮЧИТЕЛЬНО на этапе компиляции.
В частности проверка численных переполнений и выход за границы массива.
Таки вот, я пришел к выводу, что сделать подобное для тьюринг-полного языка невозможно (я это даже помнится доказал). Следовательно нужно попробовать построить тьюринг-не полный подъязык. Кроме того, мне интуитивно кажется, что процентов 90 всего кода в современных приложениях может быть писано на таком вот подъязыке. Я пробовал на наколеночном прототипе написать quicksort -- получилось. И оно даже внятней смотрелось, чем вариант даденый Виртом в своей книжке.
Т.е. тут примерно такой же подход как в Haskell'e -- там код делится на код с побочными эффектами (такого кода мало) и код без побочных эффектов (такого большенство). Тут будет деление на код тьюринг-полный, и на не тьюринг-полный код. Причем из первого можно вызывать последний, но из последнего нельзя вызывать первый.
Вот как-то так.
-
Т.е. программа должна получаться верна по построению. Не должно быть зацикливаний, выходов за границы, численных переполнений.
Естественно ошибки алгоритмов (хотели сделать одно, а сделали таки другое) останутся, т.е. когда реализовали по сути другой алгоритм.
-
Кстати, если мне память не изменяет, то языки с зависимыми типами, вроде Agda и т.п., где как раз на этапе компиляции проверяется всё от и до, не являются тьюринг полными (точнее то их подмножество где всё это как раз проверяется и доказывается). Правда они таки функциональные, я мне хочется тут императивный. Ну и хочется без столь страшной доказательной математике (я не верю что масс-программист её осилит, собственно даже хаскеля народ боится).
-
Я пробовал на наколеночном прототипе написать quicksort -- получилось.
И как оно выглядит?
-
Я в конфу кидал исходник. Но тогда вроде бы логи не велись ещё, поэтому оно пока кануло в лету. :-)
Постараюсь на выходных восстановить.
Там небыло while'ов. Там небыло произвольного доступа к индексам массивов. Циклы шли четко по множествам индексов, на каждой итерации цикла выкидывался из множества индексов как минимум один индекс (можно выкинуть больше). Добавить во множество индексов ничего нельзя. Из цикла можно преждевременно выйти (break), потому как этот выход на безопасность алгоритма никак не влияет (с т.з. выходов за границы, целочисленного переполнения и т.п.).
У меня проблемы случились позже, после qsort'a -- я попытался построить алгоритм с экспоненциальной сложностью, и у меня с ходу не получилось это вменяемо сделать. Надо думать дальше.
-
Надо бы выделить эти размышления о таком безопасном языке в отдельную тему...
Проверки на отсутствие зацикливаний -- это уже из области тотального ФП -- там все функции заведомо завершаемые, то есть полностью вычислимые.
А для работы с бесконечными циклами -- а такие тоже иногда нужны -- корекурсия, то есть типа кофункции.
Всё никак не могу заставить себя перевести статью Дэвида Тёрнера про тотальное ФП. Надо сделать -- небольшая она...
-
Ну, мой тезис таков, что это может быть не ФП, а вполне себе ИП, но не тьюринг полное просто. Т.е. это ортогонально ФП и ИП. Кстати, такой мелкий и убогий язычок очень пригодился бы для, например, написания nif'ов в ерланге (где важна как раз надежность, скорость и гарантированное завершение за определеный квант времени).
Кроме всего прочего, я думаю, что алгоритмы на таковом языке можно будет довольно просто (и даже автоматически) проверять на сложность. И уже при компиляции можно будет примерно оценить сколько при каких входных данных оно сожрет памяти и процессорного времени.
Почему я говорю именно про ИЯ? Потому, что для не тьюринг-полного языка не нужна иммутабельность. По кр. мере тотальная иммутабельность. В общем, тут можно и нужно много о чем ещё подумать, поэкспериментировать. Любые коментарии мысли (даже в порядке полного бреда) приветствуются.
PS. И да, именно вот это то и нужно для игр по сути.
-
Потому, что для не тьюринг-полного языка не нужна иммутабельность. По кр. мере тотальная иммутабельность.
Хм...
Вот помню цитату из Лебедя (сам слышал): "у нас в армии на такое говорили - не делайте умное лицо, ведь Вы же Офицер!!!"
В смысле - разжуйте по буквам эту цитату. Честное слово, я быстро учусь :D
А, да... ВСЕМ ПРИВЕТ !!!
-
Т.е. в отличае от современного ФП, где грубо говоря нет присваиваний вообще и нет изменения значений чего-либо, в императивном не тьюринг полном языке, я думаю что можно будет разрешить некоторые типы изменений по крайней мере локальных переменных.
Ну, например та же сортировка массива -- сортировка будет вестись без дополнительного выделения памяти, т.е. прямо в самом массиве. В отличае от ФП, где придется плодить копии массивов, ведь там данные иммутабельны, и изменять тот же массив мы не имеем права (да, я знаю про монаду IO и монаду ST, но это таки отступление некоторое от канонов, и они таки дают меньшую надежность чем таковой не тьюринг-полный императивный язык).
-
Всё же мне кажется, что можно сделать вполне внутри настоящий чистый и тотальный язык, но замаскировать его таким синтаксисом, что будет похоже на ИЯ, но с такими ограничениями, которые дадут ему безопасность.
Под маскировкой я имею в виду, например, библиотеку Language.Basic (http://hackage.haskell.org/packages/archive/BASIC/latest/doc/html/Language-BASIC.html) в Хаскелле, позволяющую писать на Хаскелле такие во программы:
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
{-# LANGUAGE ExtendedDefaultRules, OverloadedStrings #-}
module HiLo where
import Language.BASIC
main :: IO ()
main = runBASIC $ do
10 GOSUB 1000
20 PRINT "* Welcome to HiLo *"
30 GOSUB 1000
100 LET I := INT(100 * RND(X))
-- 110 PRINT I
200 PRINT "Guess my number:"
210 INPUT X
220 LET S := SGN(I-X)
230 IF S <> 0 THEN 300
240 FOR X := 1 TO 5
250 PRINT X*X;" You won!"
260 NEXT X
270 STOP
300 IF S <> 1 THEN 400
310 PRINT "Your guess ";X;" is too low."
320 GOTO 200
400 PRINT "Your guess ";X;" is too high."
410 GOTO 200
1000 PRINT "*******************"
1010 RETURN
9999 END
http://augustss.blogspot.com/2009/02/regression-they-say-that-as-you-get.html
-
Да, я тоже полагаю что на хаскеле можно сделать такой EDSL и с типизацией там будет всё хорошо. Хаскель тем и хорош, что там можно обкатывать новые парадигмы :-) Собственно для того он и делался, делается и будет делаться.
Но в последствии неплохо бы иметь отдельный компилятор для такого язычка. Потому как ghc это явный оверкил :-)
Но вначале надо поэкспериментировать/подумать как он вообще должен выглядеть.
-
Т.е. программа должна получаться верна по построению. Не должно быть зацикливаний, выходов за границы, численных переполнений.
А чем, собственно, зацикливание отличается от просто очень долгого алгоритма? С точки зрения конечного результата в виде "зависания".
-
А чем, собственно, зацикливание отличается от просто очень долгого алгоритма? С точки зрения конечного результата в виде "зависания".
Ну, очень долгий алгоритм в конце концов всё же завершится с каким-то результатом, а зацикливание типа "10 GOTO 10" не завершится никогда.
При определённом подходе к программированию (например тотальное ФП) можно получить гарантию того, что алгоритм рано или поздно завершится, а иначе программа просто не скомпилируется из-за ошибки типизации.
-
Т.е. программа должна получаться верна по построению. Не должно быть зацикливаний, выходов за границы, численных переполнений.
А чем, собственно, зацикливание отличается от просто очень долгого алгоритма? С точки зрения конечного результата в виде "зависания".
Тут есть отличие -- знаем мы что оно зациклилось, или не знаем. Если знаем, тогда ждать смысла не имеет, надо убить (если зациклилось), если мы гарантировано знаем что алгоритм конечный, то можем подождать. Более того, можем как-то проследить процесс исполнения и что-то спрогнозировать. Т.е. процесс получаем управляемый.
-
Тут есть отличие -- знаем мы что оно зациклилось, или не знаем. Если знаем, тогда ждать смысла не имеет, надо убить (если зациклилось), если мы гарантировано знаем что алгоритм конечный, то можем подождать. Более того, можем как-то проследить процесс исполнения и что-то спрогнозировать. Т.е. процесс получаем управляемый.
С точки зрения пользователя программы все равно - зависла она на совсем или сделает свою работу через пару миллиардов лет. И так и так - глючит :)
P.S. Хотя, конечно, иметь гарантии завершимости на этапе компиляции было бы полезно. Сколько-то ошибок/опечаток не пролезло бы.
-
По поводу нулевых указателей и неинициализированных переменных. Я уже выступал на оригинальном форуме (http://forum.oberoncore.ru (http://forum.oberoncore.ru)) и наверняка есть ЯП в которых эта проблема в каком то виде решена, но если подытожить применительно к оберону и структурному редактору, то основные тезисы такие:
- Неинициализированных переменных нет, потому что объявление переменной совмещено с ее инициализацией синтаксически. A-ля VAR i: INTEGER := 3.
- Указатели по умолчанию ненулевые (обязаны быть проинициализированы реальным объектом или другим ненулевым указателем).
- Можно объявить нулевой указатель. Это другой тип, совместимый с ненулевым указателем в одну сторону: ненулевой указатель может быть приведен к нулевому, но не наоборот. Нулевой указатель не может быть разыменован.
- Для получения ненулевого указателя из нулевого есть специальная синтаксическая конструкция вроде обероновской проверки/приведения типа с помощью WITH. Если проверка сработала, то выполняется ветка кода, где "виден" ненулевой указатель.
Я против такой имитации решения проблемы. :-\\
Это похоже на то, как действуют пользователи MS Access. Там есть поля, которые программист делает обязательными для заполнения. Хитро...умные пользователи ставят пробел, и программа считает, что всё OK. Ну и кому от этого польза? Реальная-то информация не введена, а введена галиматья!
Вместо того, чтобы напрячь разработчика компилятора, мы, получается, заставляем прикладного программиста вводить бессмысленные значения, чтобы только компилятор не ругался! На самом деле компилятор должен найти те переменные, которые заведомо никогда не инициализируются. Если же в процессе исполнения программы до инициализации просто не дойдет дело, то тогда должно генерироваться исключение.
-
Вообще, скажем в яве, является распространенной практикой не использовать нулевые указатели вообще.
Делается это так (допустим у нас есть метод который должен возвращать найденую строку, иногда он строку не находит, и что в этом случае делать? а вот что):
class SomeClass {
static nullString = "SomeClass null string";
String findString(int id) {
if (canFind(id)) // получилось найти
return foundString; // возвращаем найденую строку
else
return nullString;
}
}
Т.о. нуль оно не возвращает никогда. Всегда возвращается некое осмысленное значение. Это не панацения конечно, но это сильно помогает отладке и диагностике ошибок. Работа программы становится вроде как более предстказуемой с случае ошибки.
К сожалению Ява, в отличае от например Ады, не позволяет определить тип ненулевой ссылки. А тут бы оно было бы очень полезно. Поэтому среди Адовцев Ява считается языком маленьким и слабеньким :-)
-
Я против такой имитации решения проблемы. :-\\
Это похоже на то, как действуют пользователи MS Access. Там есть поля, которые программист делает обязательными для заполнения. Хитро...умные пользователи ставят пробел, и программа считает, что всё OK. Ну и кому от этого польза? Реальная-то информация не введена, а введена галиматья!
Нет. Язык дает средство сделать код надежнее/понятнее/эффективнее. Если программист не хочет этим воспользоваться - то тут языком дело не исправишь. Ключевое отличие от Access в том, что там есть разделение на программиста, который думает, что поле обязательно и требует ввести туда значение, и пользователя, который знает, что на самом деле оно не обязательно и вбивает туда мусор :) В случае ЯП такого разделения нет. Программу пишет программист и он же вбивает осмысленное значение, чтобы яснее выразить свою мысль. Для читающего программиста инициализация делает код понятнее.
Вместо того, чтобы напрячь разработчика компилятора, мы, получается, заставляем прикладного программиста вводить бессмысленные значения, чтобы только компилятор не ругался! На самом деле компилятор должен найти те переменные, которые заведомо никогда не инициализируются. Если же в процессе исполнения программы до инициализации просто не дойдет дело, то тогда должно генерироваться исключение.
Вот уж чего я точно не хотел бы - это ошибки в рантайме. Понятно, что компилятор не всегда сможет проанализировать, что переменная всегда инитится. Поэтому для компилятора сильно проще, если все будет инициализировано явно в момент объявления.
P.S. Еще хочу сказать, что такой подход (тотальная инициализация и ненулевые указатели) уже несколько лет успешно обкатан мной лично на практике. Реально "ненужных" присваиваний (нулевых) чрезвычайно мало - они возникают у меня только на стыке с Win API, когда какая-то переменная будет заведомо проинициализирована вызовом функции, но я ее все равно инициализирую в 0, чтобы не нарушать стиль. Проблема только в том, что в случае С++ это вопрос стиля - компилятор позволяет так писать, но не требует.
-
К сожалению Ява, в отличае от например Ады, не позволяет определить тип ненулевой ссылки. А тут бы оно было бы очень полезно. Поэтому среди Адовцев Ява считается языком маленьким и слабеньким :-)
Угу. Глядя на жабовский/обероновский код, в который пришел указатель, никогда не сможешь сказать, что здесь никогда не будет "null dereference exception". Потому что нет _типа_ ненулевых указателей.
-
Мы говорим о разных вещах. Длинных типов обычно даже слишком много разных, что ведет к несовместимости по типу. Поэтому, например, Вирт все их просто отбросил в Обероне-07. Ему это было сделать просто - он ведь не разрабатывает игры - ему и не нужны длинные типы.
У этих библиотечных типов есть еще один порок - программисту легко ошибиться в своих запросах. Слишком короткие типы приведут к переполнению, а слишком длинные сожрут место в памяти и на диске и замедлят обработку данных.
Я же писал не о длинных типах, а о типах переменной длины (похоже на динамические массивы). Если число короткое, то оно хранится целиком, а если длинное, то вместо самого числа в ячейке хранится указатель на другую ячейку необходимой длины, где хранится само число.
У меня очень сильные сомнения, что эти проблемы решены хоть в каком-то языке программирования, включая КП.
Собственно я говорил именно об этом. Именно эти числа, о которых вы говорите есть сразу в Haskell и Erlang'e. Функция подсчета факториала от 5ти будет одинаково использоваться и для подсчета от 10000. В первом случае будет использован int32, во втором случае, начиная с некоторого шага, будет использовано длинное целое (переменной длины). При этом тип останется тот же -- Integer.
Поэтому я призываю всех подумать, как реально их можно решить. Полный отказ от указателей или какой-то другой абсолютно необходимой функциональности, требование к программисту всё инициализировать при объявлении каким-нибудь мусором, попытка всё спихнуть на обработку исключения - не считаются решением проблемы.
Ну, я собственно уже предложил :-)
-
Я думаю, что нужно предоставить программисту удобную возможность проинициализировать переменную при объявлении и напомнить ему, если это не сделано. Но принуждать к инициализации не нужно. Иначе в рантайме уже не удастся сгенерировать и обработать исключение, когда действительно осмысленная инициализация не прошла. Программа будет утверждать, что "всё хорошо, прекрасная маркиза", хотя на самом деле это не так.
-
Я думаю, что нужно предоставить программисту удобную возможность проинициализировать переменную при объявлении и напомнить ему, если это не сделано. Но принуждать к инициализации не нужно. Иначе в рантайме уже не удастся сгенерировать и обработать исключение, когда действительно осмысленная инициализация не прошла. Программа будет утверждать, что "всё хорошо, прекрасная маркиза", хотя на самом деле это не так.
Давайте зайдем от обратного :) Придумайте пример, когда вы не можете осмысленно инициализировать переменную сразу. Мы над ним помедитируем :) Рассмотрим достоинства и недостатки.
-
...
Type t1, t2;
if (c1) {
t1 = f1(p1);
t2 = f2(p2);
} else {
t1 = f3(p3);
}
...
if (c1 && c2) {
f5(t1);
f6(t2);
} else {
f7(t1);
}
-
...
Type t1, t2;
if (c1) {
t1 = f1(p1);
t2 = f2(p2);
} else {
t1 = f3(p3);
}
...
if (c1 && c2) {
f5(t1);
f6(t2);
} else {
f7(t1);
}
Сразу замечу, что компилятору, проверяющему инициализацию, c оригинальным кодом будет непросто разобраться :) Вот мой "преобразованный" вариант для критики:
Type t1 = с1 ? f1(p1) : f3(p3);
...
if (c1 && c2) {
f5(t1);
Type t2 = f2(p2);
f6(t2);
} else {
f7(t1);
}
-
В предыдущий раз недостаточно точно выразил мысль, поскольку f1..f7, это на деле не неделимые функции. Так будет точнее
Type t1, t2;
if (c1) {
Type t3 = f0(p1, p2);
t1 = f1(p1, t3);
t2 = f2(p2, t3);
} else {
t1 = f3(p3);
}
...
if (c1 && c2) {
f5(t1);
f6(t2);
} else {
f7(t1);
}
-
В предыдущий раз недостаточно точно выразил мысль, поскольку f1..f7, это на деле не неделимые функции. Так будет точнее
Type t1, t2;
if (c1) {
Type t3 = f0(p1, p2);
t1 = f1(p1, t3);
t2 = f2(p2, t3);
} else {
t1 = f3(p3);
}
...
if (c1 && c2) {
f5(t1);
f6(t2);
} else {
f7(t1);
}
Да, это интереснее :) Первый кусок кода:
if (c1) {
Type t3 = f0(p1, p2);
t1 = f1(p1, t3);
t2 = f2(p2, t3);
} else {
t1 = f3(p3);
}
выносим в функцию, которая возвращает t1 и "опциональное" t2. Тогда оставшийся кусок будет выглядеть так:
(Type t1, TypeOptional t2) = f();
...
if (t2.initialized() && c2) {
f5(t1);
f6(t2); // здесь t2 гарантировано не "пустое", компилятор про это знает, потому что выше произошла проверка/приведение типа от TypeOptional к Type
} else {
f7(t1);
}
-
Собственно я говорил именно об этом. Именно эти числа, о которых вы говорите есть сразу в Haskell и Erlang'e. Функция подсчета факториала от 5ти будет одинаково использоваться и для подсчета от 10000. В первом случае будет использован int32, во втором случае, начиная с некоторого шага, будет использовано длинное целое (переменной длины). При этом тип останется тот же -- Integer.
Мы в семантическом редакторе реализуем учебный язык без привязки к размерам встроенных типов. Я поначалу хотел даже отказаться от деления на целые и дробные, и оставить один тип Число. Но мне показалось, что это слишком радикально - все же во всех языках числа не менее двух типов.
-
Собственно я говорил именно об этом. Именно эти числа, о которых вы говорите есть сразу в Haskell и Erlang'e. Функция подсчета факториала от 5ти будет одинаково использоваться и для подсчета от 10000. В первом случае будет использован int32, во втором случае, начиная с некоторого шага, будет использовано длинное целое (переменной длины). При этом тип останется тот же -- Integer.
Мы в семантическом редакторе реализуем учебный язык без привязки к размерам встроенных типов. Я поначалу хотел даже отказаться от деления на целые и дробные, и оставить один тип Число. Но мне показалось, что это слишком радикально - все же во всех языках числа не менее двух типов.
Во многих языках тяготяющик к скриптовому применению, есть только просто число. Например в той же Lua. И мне кажется, что даже в js это так, но тут могу ошибаться.
-
Javascript - Number.
-
Да, это интереснее :) ...
Напишите, пожалуйста, весь код. А заодно опишите, какими свойствами должен обладать язык, чтобы делать такие штуки было удобно. И как Вы считаете, можно ли считать это правило маленькой добавкой в язык уровня Оберон (когда-то Вы писали, что оно легко и элегантно впишется в его гипотетический диалект).
Также подумайте, чего этот способ делает больше: решения проблем или их создания (просто подумайте, отвечать не надо, поскольку Ваш ответ и так очевиден с вероятностью 87.34%).
-
Напишите, пожалуйста, весь код.
C расчетом, что у нас в языке а-ля оберон с сишным синтаксисом есть туплы (если нет, то всегда можно отбиться структурами/записями):
(Type t1, TypeOptional t2)
f() {
if (c1) {
Type t3 = f0(p1, p2);
return f1(p1, t3), f2(p2, t3);
} else {
return t1, null;
}
}
...
Type t1, TypeOptional t2 = f();
...
if (optional_cast<Type>(t2) && c2) {
f5(t1);
f6(t2); // здесь t2 гарантировано не "пустое", компилятор про это знает, потому что выше произошла проверка/приведение типа от TypeOptional к Type
} else {
f7(t1);
}
А заодно опишите, какими свойствами должен обладать язык, чтобы делать такие штуки было удобно. И как Вы считаете, можно ли считать это правило маленькой добавкой в язык уровня Оберон (когда-то Вы писали, что оно легко и элегантно впишется в его гипотетический диалект).
Если в порядке "величины" добавки, то от языка нужны следующие свойства:
1. Механизм исключений в каком-то виде. Если инициализировать при объявлении, то код ошибки обрабатывать уже негде. Да, это совсем немаленькая добавка, но зато хорошо изученная и без нее жить трудно безотносительно к предлагаемым изменениям.
2. Объявление локальных переменных по месту (упразднение отдельной секции VAR). Тоже повсеместно доказанное благо, хотя убежденные паскалисты держатся за нее до конца :)
3. Добавление null'able в систему типов. Существующих примеров много, начиная с SQL и заканчивая C#, ничего нового, все известно и понятно.
4. Тернарный оператор и туплы. Можно и без них (особенно без туплов), но с ними приятнее :)
Также подумайте, чего этот способ делает больше: решения проблем или их создания (просто подумайте, отвечать не надо, поскольку Ваш ответ и так очевиден с вероятностью 87.34%).
Я пока не нашел каких-то проблем/противоречий, к которым могут привести предлагаемые изменения. Изменений в компиляторе относительно немного. Язык станет более выразительным (за счет расширения системы типов) и сменится стиль написания кода в сторону более гранулярной декомпозиции (как в данном примере).
-
Поправил оформление цитат в сообщении vlad'a. (добавил закрывающий тэг [/quote]).
-
C расчетом, что у нас в языке а-ля оберон с сишным синтаксисом есть туплы (если нет, то всегда можно отбиться структурами/записями):
С учётом того, что это должно быть удобным (иначе кто так будет писать?), то либо кортежи обязательны, либо структуры должны быть существенно переработаны.
Я просил привести весь код, но создаётся впечатление, что многое всё же недосказано. Что такое TypeOptional - фишка языка, позволяющая прилепливать к типу слово Optional, после чего получаем особый тип? Или он всё же где-то объявлен, тогда где его код?
1. Механизм исключений в каком-то виде. Если инициализировать при объявлении, то код ошибки обрабатывать уже негде. Да, это совсем немаленькая добавка, но зато хорошо изученная и без нее жить трудно безотносительно к предлагаемым изменениям.
2. Объявление локальных переменных по месту (упразднение отдельной секции VAR). Тоже повсеместно доказанное благо, хотя убежденные паскалисты держатся за нее до конца :)
3. Добавление null'able в систему типов. Существующих примеров много, начиная с SQL и заканчивая C#, ничего нового, все известно и понятно.
4. Тернарный оператор и туплы. Можно и без них (особенно без туплов), но с ними приятнее :)
Лихо Вы объединили тернарный оператор и кортежи в один пункт, полагаю, что 2-е намного сложнее 1-го. Также думаю, что без них нельзя, иначе фишка будет не удобной.
Полагаю, что кое-что забыто (я не прогонял это тщательно в уме):
Отказ от статических структур и от структур как таковых, одни объекты, либо добавление хитрых механизмов для работы с ними.
Добавление конструкторов, да не простых, а с инициализацией всех данных объекта.
Объявление данных модуля с инициализацией.
Отказ от данных модуля, которые не могут быть инициализированы по месту.
Как-то придумать инциализацию по месту массивов, или отказ от них.
Отказ от 1-го RETURN (Оберон ведь движется в этом направлении)
В Вашем примере, не шаблоны ли использованы для приведения типа?
Возможно, что-то ещё.
Про исключения не понял, почему нельзя возвращать код ошибки вместе с результатом?
По-моему вырисовывается не диалект Оберона, а совсем другой язык.
Также подумайте, чего этот способ делает больше: решения проблем или их создания (просто подумайте, отвечать не надо, поскольку Ваш ответ и так очевиден с вероятностью 87.34%).
Я пока не нашел каких-то проблем/противоречий, к которым могут привести предлагаемые изменения. Изменений в компиляторе относительно немного.
Предложу свои варианты ответов:
1.При необходимости многие будут не выкручиваться в эквивалентные преобразование кода, а инициализировать по месту мусорно, что затруднит обнаружение ошибки, в случае если переменная так и не будет проинициализирована нормально (за что боролись, на то и напоролись).
2.Усложнение языка и компилятора, ради небольшой проблемы. Ведь изначально шла речь о простом изменении, существенно более простом, чем то, что реализовано в анализаторе BlackBox или компиляторе java. А если нам нужен другой язык, с кортежами, исключениями, nullable типами и другими фишками, это другой вопрос.
3.Возможно, что-то ещё. Мне не так-то просто предвидеть все последствия от необходимых изменений.
-
С учётом того, что это должно быть удобным (иначе кто так будет писать?), то либо кортежи обязательны, либо структуры должны быть существенно переработаны.
С рекордами можно жить (я в С++ живу, хотя там есть boost::tuple). Писанины чуть больше, зато поля именованы, зачастую это повышает читабельность. Существенной переработки применительно к оберону не надо, достаточно иметь синтаксис для инициализации рекордов (так же как и массивов). Этот недостающий сахар уже обсуждали - без него неудобно и в классическом обероне.
Я просил привести весь код, но создаётся впечатление, что многое всё же недосказано.
Ну трудно писать на гипотетическом языке, если что-то непонятно - проще пояснить словами.
Что такое TypeOptional - фишка языка, позволяющая прилепливать к типу слово Optional, после чего получаем особый тип? Или он всё же где-то объявлен, тогда где его код?
Это что-то типа:
TYPE TypeOptional = OPTIONAL INTEGER;
или
typedef optional int TypeOptional;
Лихо Вы объединили тернарный оператор и кортежи в один пункт,
Оно не принципиально :) Хотя тернарный оператор все же нужнее.
полагаю, что 2-е намного сложнее 1-го. Также думаю, что без них нельзя, иначе фишка будет не удобной.
Чего ж там сложного??? Компилятор уже умеет иметь дело с временными переменными (для вычисления выражений). Объявление по месту - такая же временная переменная.
Полагаю, что кое-что забыто (я не прогонял это тщательно в уме):
Отказ от статических структур и от структур как таковых, одни объекты, либо добавление хитрых механизмов для работы с ними.
Не, не понял. Что такое статические структуры?
Добавление конструкторов, да не простых, а с инициализацией всех данных объекта.
Не. Можно без конструкторов. Просто синтаксис для инициализации рекордов. Если полей много - делается обычная вспомогательная функция-конструктор. Ничего в язык добавлять не надо.
Объявление данных модуля с инициализацией.
Отказ от данных модуля, которые не могут быть инициализированы по месту.
Не, не понял. Обсуждаемые свойства никак не мешают данным модуля - всегда есть OPTIONAL. Просто теперь все будет явно: если данные модуля не могут быть проиничены самостоятельно - то они OPTIONAL.
Как-то придумать инциализацию по месту массивов, или отказ от них.
Да, да. Давно пора нормально инициализировать массивы :) Если массив большой - да, есть проблема в дополнительных накладных расходах. Но мне не кажется, что это что-то принципиальное. Ну зачем может быть нужен неинициализированный большой массив? Жуткую матрицу посчитать? Так оно считать будет много дольше, чем инициализировать.
Отказ от 1-го RETURN (Оберон ведь движется в этом направлении)
А может это неправильное направление? :)
Возможно, что-то ещё.
Про исключения не понял, почему нельзя возвращать код ошибки вместе с результатом?
Потому что результат будет "мусорным" в случае ошибки. А мы не хотим мусорных значений.
По-моему вырисовывается не диалект Оберона, а совсем другой язык.
Да ладно. До какого-нибудь С++ или жабы такому диалекту очень далеко :) А определенные проблемы решаются.
Предложу свои варианты ответов:
1.При необходимости многие будут не выкручиваться в эквивалентные преобразование кода, а инициализировать по месту мусорно, что затруднит обнаружение ошибки, в случае если переменная так и не будет проинициализирована нормально (за что боролись, на то и напоролись).
Да вы никогда не заставите писать правильно. Но вот если писать правильно будет проще, чем неправильно, то среди неправильных программистов с накоплением опыта будет определенная миграция в правильную сторону. Например, проинитить указатель нулем уже не получится. Придется писать OPTIONAL, а потом явно приводить такой указатель к ненулевому. Глядишь, подумают чуть-чуть, и сделают так, что указатель сразу будет инициализирован чем-то осмысленным.
2.Усложнение языка и компилятора, ради небольшой проблемы. Ведь изначально шла речь о простом изменении, существенно более простом, чем то, что реализовано в анализаторе BlackBox или компиляторе java. А если нам нужен другой язык, с кортежами, исключениями, nullable типами и другими фишками, это другой вопрос.
В приведенном вами примере - компилятор ничего не сможет сделать. Придется инитить мусором, чтобы его заткнуть. В чем мы выиграли?
3.Возможно, что-то ещё. Мне не так-то просто предвидеть все последствия от необходимых изменений.
В том, что я описал, нет ничего нового. Оно в том или ином виде присутствует в существующих ЯП. Так что каких-то злобных последствия я не предвижу.
-
В LLVM кортэжи есть сразу. Вообще кортэж, для компилятора, и вообще на низком уровне штука весьма простая и ничем принципиально от тех же структур не отличается.
-
Вставлю свои 5 копеек:
1. Инициализацию модульных переменных можно делать в секции инициализации модуля - я правильно понимаю? Придумывать для этого специальный синтаксис - не обязательно. Хотя, конечно, можно это повесить на компилятор - пусть из инициализатора сделает преобразование в секции инициализации модуля.
2. Объявление по месту - мне нравится.
3. Объявление с инициализацией - ИМХО только для встроенных типов. Тем более, что это опять можно повесить на компилятор - пусть разнесет объявление и инициализацию.
Соответственно 4 - никаких конструкторов! Или надо МНОГО думать, как их ограничить. В С++ конструкторы вызываются по умолчанию, что есть зло. Единственное ограничение, которое программер может употребить - это explicit. Но не обязательно, поэтому мало кто пишет, особенно начинающие.
Соответственно 5 - нет необходимости в механизме исключений.
-
Соответственно 5 - нет необходимости в механизме исключений.
Вы теперь против механизма исключений, даже такого (http://sites.google.com/site/purebuilder/#TOC-28)сильно ограниченного функциональным стилем и "скрещенного" с кодами ошибки, как в PureBuilder?
Возникает несколько вопросов:
1) Что взамен - для действительно аварийных ситуаций, делающих бессмысленным возвращаемое значение функции?
2) Что изменить в языке, чтобы код обработки ошибок в рантайме был отделен в программе от нормального штатного кода?
3) Как же тогда учить студентов осторожному применению механизма исключений в "промышленных" языках?
-
Вставлю свои 5 копеек:
1. Инициализацию модульных переменных можно делать в секции инициализации модуля - я правильно понимаю? Придумывать для этого специальный синтаксис - не обязательно. Хотя, конечно, можно это повесить на компилятор - пусть из инициализатора сделает преобразование в секции инициализации модуля.
Я бы повесил на компилятор - инициализация модульных переменных в порядке объявления, затем вызов секции инициализации.
2. Объявление по месту - мне нравится.
3. Объявление с инициализацией - ИМХО только для встроенных типов. Тем более, что это опять можно повесить на компилятор - пусть разнесет объявление и инициализацию.
Не понял. Почему только для встроенных типов и зачем разносить?
Соответственно 4 - никаких конструкторов!
Да, конструкторы не нужны. Достаточно синтаксиса инициализации полей записи и элементов массива. Для массивов неизвестной длины - значение по умолчанию.
Соответственно 5 - нет необходимости в механизме исключений.
Нет, механизм исключений принципиален. Если функция возвращает ненулевой указатель, то что ей вернуть, если произошла ошибка? Без исключений вся идея nullable типов и инициализации только осмысленными значениями не работает.
-
Про инициализацию массивов... Что мешает сделать ленивую инициализацию? Т.е. инициализация данной ячейки массива непосредственно ПЕРЕД чтением. Если данную ячейку вначале не читают, а скажем сразу туда пишут, то оверхеда не будет вообще.
-
Ответ на оригинальный пост в http://forum.oberoncore.ru/viewtopic.php?p=60599#p60599 (http://forum.oberoncore.ru/viewtopic.php?p=60599#p60599)
foreach'и бывают разными. Может быть, Вы имели дело с неудачными, и надо сделать (или позаимствовать) другой вариант? Например, с не столь безальтернативной "отначаладоконцовостью"?
Так это будет WHILE и райдер, как раз.
foreach - это райдер-итератор, спаянный намертво с циклом, при этом с циклом, который идёт от начала до конца.
Максимально специализированная вещь. Если, конечно, не разрешать "стоп-кран" break и "прыжки из поезда на ходу".
Максимально специализированная вещь - это как раз очень хорошо. Особенно, если она максимально специализирована для 90% случаев использования в конкретном проекте :)
foreach дает возможность максимально удобно обработать значения элементов последовательности. И больше ничего. Причем его основное достоинство именно в этом "ничего", а не в краткости записи :) Он не даст вам неправильно выбрать следующий элемент, он не даст вам изменить значение этого элемента, он не даст вам неправильно прописать условие завершение цикла. Он не даст вам совершить кучу ошибок. А в качестве бонуса - читающий ваш код сразу увидит, что вы не делаете здесь ничего неожиданного (см. список чего foreach "не даст") - и спокойно пойдет читать дальше более интересные вещи.
-
Как всё сложно.
[17.1] What are some ways try / catch / throw can improve software quality?
FAQ: You'll have less if statements in your code: you won't have to check for errors each time you call a function. Conditional statements are known to contain more bugs than other statements. With less if tests, you'll ship a better product, faster.
FQA: This is cargo cult programming. Conditional statements are error-prone because they are used to handle complicated scenarios, where an action can result in many different outcomes, which affect the next actions. In order to make errors less probable, one has to simplify the model of the desired behavior of the software. The problem is the complexity that leads to if statements, not the if keyword, and using different keywords is not going to solve the problem by itself.
Exceptions are supposed to simplify the error handling model based on the assumption that in most cases, a function that detected an error can't handle it, and has to propagate it to the caller. Finally, a "high-level" enough caller is reached and actually makes a decision (pops up an error message, tries a different action, etc.).
Despite its promises, this approach has inherent problems. There's a "social" problem - with exceptions, people are not aware of the different errors that may happen in the code because most of the code doesn't deal with errors. And when people rarely think about a particular aspect of an application, ultimately this aspect is unlikely to be handled well. There's a more "technical" problem - functions essentially doing nothing upon error except for propagating errors to the caller still can't be completely unaware of errors. That's because they may need to release the resources they acquired before returning to the caller, which may lead to more errors, which must also be handled. Finally, in practice exception support has run-time overhead, and very significant code size overhead, even if exceptions are never raised at run time, and even if they are not mentioned in your code. C++ devotees may claim otherwise; you can check by compiling your code with and without exception support (if your compiler doesn't have such a flag, compile code as C and as C++ instead). This is unacceptable in resource-constrained systems.
Отрицательный результат - тоже результат. И хотя нас часто порывает принять за данность выполнение программы в идеальном окружении с идеальным пользователем, а всю логику обработки ошибок закинуть как можно выше, это не выход. Пора уже понять, что практически все функции не способны гарантировать результат. Что исключения одного класса обезличиваются при всплывании, превращаясь в нечто вроде TIOError. Что если мы хотим обработать 250 вызовов функций по-разному, нам понадобятся 250 обработчиков и их логика должна быть на том же уровне, что и вызов функций.
Посмотрите на самые азы - арифметику. +, -, * - возможность выхода за границы типа (переполнение), / - тоже + деление на 0. Практически каждая функция из rtl в php может вернуть ошибку. Работа со строками, массивами, форматами - чем угодно. Если возвращаемое значение функции и есть её результат, то механизм исключений будет использоваться всегда. Или же его тросянка. Часть функций с кодами ошибок, другая на исключениях. Вопрос. А что есть исключение? То, что сейчас модно называть исключением, является ничем иным как штатной ситуацией. Зачем вводить исключения, потом для них же магические способы финализации, затем перегрузку операторов, которая без исключений вообще нежизнеспособна. Затем возвращение сложных структур функциями. Что более фундаментально? Что автономно? Что проще в реализации? С какой кстати высший уровень должен знать все типы исключений низших, а если не знать, то иметь лишь единственный молчаливый способ их обработать? Наконец, зачем ещё один goto на адрес в стёковой структуре, когда стремимся к безопасному, надёжному и наглядному коду, где прыжкам в неизвестность просто не место.
P.S Кстати, пример изначально неверного подхода к проектированию можно увидеть в обероновском модуле Files. Там функции работают с идеальной файловой системой и понятия ошибки нет вообще.
Заключение: для действительно исключительных ситуаций (определённого класса) достаточно функций-финализаторов.
-
С рекордами можно жить (я в С++ живу, хотя там есть boost::tuple). Писанины чуть больше, зато поля именованы, зачастую это повышает читабельность. Существенной переработки применительно к оберону не надо, достаточно иметь синтаксис для инициализации рекордов (так же как и массивов). Этот недостающий сахар уже обсуждали - без него неудобно и в классическом обероне.
1.Необходимость больше писать приведёт к тому, что фишкой не будут пользоваться. Перепишите пример со структурами и сравните. Вспомните хотя бы, как сишники жалуются на "многословность" синтаксиса Оберона. 2.Как минимум нужно и объявление типа по месту.
Чего ж там сложного??? Компилятор уже умеет иметь дело с временными переменными (для вычисления выражений). Объявление по месту - такая же временная переменная.
Не знаю, не пробовал реализовать, но интуитивно чувствую, что не очень это просто для понимания компилятора, когда группу чего-то отдельного, можно представить как одно и наоборот. Тернарный оператор намного проще, особенно если не как в С, а как-то так :TOP(condition, value1, value2);
Не, не понял. Что такое статические структуры?
Правильнее было сказать: статически объявленные экземпляры структур, т.е. не в динамической памяти (затрудняюсь более грамотно сформулировать)
Не. Можно без конструкторов. Просто синтаксис для инициализации рекордов. Если полей много - делается обычная вспомогательная функция-конструктор. Ничего в язык добавлять не надо.
Точно ничего? А выделять память отдельной операцией будете? Чем поможет вспомогательная функция-конструктор, если инициализировать всё равно одной командой нужно? Как инициализировать расширенные записи при желании использовать код для базовых записей?
Не, не понял. Обсуждаемые свойства никак не мешают данным модуля - всегда есть OPTIONAL. Просто теперь все будет явно: если данные модуля не могут быть проиничены самостоятельно - то они OPTIONAL.
Что не понятно, конечно optional всегда может инициализироваться по месту, что и скрашивает отказ от данных, которые не могут. Тем не менее, данные могут гарантировано инициализироваться в секции инициализации, но они всё равно должны объявляться через optional. Это точно не влечёт дополнительных расходов со стороны программиста и программы в виде явных и неявных проверок/приведений типа?
Да, да. Давно пора нормально инициализировать массивы :) Если массив большой - да, есть проблема в дополнительных накладных расходах. Но мне не кажется, что это что-то принципиальное. Ну зачем может быть нужен неинициализированный большой массив? Жуткую матрицу посчитать? Так оно считать будет много дольше, чем инициализировать.
К примеру, транспонирование матрицы - штука быстрая. А если это массив структур, содержащих массивы?
Отказ от 1-го RETURN (Оберон ведь движется в этом направлении)
А может это неправильное направление? :)
Правильное. Не могу припомнить сколь-нибудь серьёзную проблему, связанную именно с неинициализированной переменной, а вот функции по нескольку тысяч строк, плохо поддающиеся пониманию и переработке из-за усеянности на самом деле ненужными return-ами мне встречаются регулярно. Надо ведь помогать программисту правильно, не так ли?
В приведенном вами примере - компилятор ничего не сможет сделать. Придется инитить мусором, чтобы его заткнуть. В чем мы выиграли?
Я бы и не хотел, чтобы компилятор вопил по этому поводу. Это должно остаться на уровне стороннего анализатора. А помимо компилятора, код просматривает и человек, вот тут-то мы и выигрываем.
В том, что я описал, нет ничего нового. Оно в том или ином виде присутствует в существующих ЯП. Так что каких-то злобных последствия я не предвижу.
В том-то и дело, что включались эти средства по большей части без большого осмысления (говорю это, основываясь ни на чём), а комбинации с запретом на неинициализацию вряд ли есть где-то. Многие не видят и более простых последствий и в ус не дуют. В языке С++ тоже уже сюрпризов нет, а писать на нём хотят всё меньше.
-
1.Необходимость больше писать приведёт к тому, что фишкой не будут пользоваться. Перепишите пример со структурами и сравните. Вспомните хотя бы, как сишники жалуются на "многословность" синтаксиса Оберона.
Ну вот я пишу на питоне тоже, там есть родные (лаконичные) кортежи. Пользуюсь ими все равно не очень часто (хотя пишу в обсуждаемом стиле - все инициализируется чем-то осмысленным). Основной недостаток - если такой кортеж пошел гулять за пределы двух/трех функций, то понять смысл того, что там лежит становится трудно. Именованная структура все же лучше. Опять же - ваш оригинальный пример довольно экстремальный :) Так что острой необходимости в кортежах я не вижу.
2.Как минимум нужно и объявление типа по месту.
В смысле рядом с функцией? Ну да, желательно. Но тоже не то, чтобы принципиально, если модули человеческих размеров.
Чего ж там сложного??? Компилятор уже умеет иметь дело с временными переменными (для вычисления выражений). Объявление по месту - такая же временная переменная.
Не знаю, не пробовал реализовать, но интуитивно чувствую, что не очень это просто для понимания компилятора, когда группу чего-то отдельного, можно представить как одно и наоборот.
Для понимания компилятора там самое сложное - разобраться с областями видимости. А так.. не знаю... ИМХО все просто.
Тернарный оператор намного проще, особенно если не как в С, а как-то так :TOP(condition, value1, value2);
Ну такая функция все равно будет встроенной, так что не знаю, ИМХО не должно быть трудностей.
Правильнее было сказать: статически объявленные экземпляры структур, т.е. не в динамической памяти (затрудняюсь более грамотно сформулировать)
Ну так - инициализировать при объявлении. Что не так?
Не. Можно без конструкторов. Просто синтаксис для инициализации рекордов. Если полей много - делается обычная вспомогательная функция-конструктор. Ничего в язык добавлять не надо.
Точно ничего? А выделять память отдельной операцией будете? Чем поможет вспомогательная функция-конструктор, если инициализировать всё равно одной командой нужно? Как инициализировать расширенные записи при желании использовать код для базовых записей?
Ну, допустим у нас есть такой синтаксис для инициализации:
TYPE X = RECORD a: INTEGER; b: INTEGER; END
...
VAR x:= X{1, 2}; // локальная переменная
Выделение в хипе:
VAR x:= NEW(X{1, 2});
Функция-конструктор:
PROCEDURE construct_x(): X
BEGIN
RETURN X{1, 2};
END construct_x
Расширение записи (Y наследуется от X с добавлением одного поля):
VAR y:= Y{X{1, 2}, 3}; // локальная переменная
Использование функции конструктора:
PROCEDURE construct_y(): Y
BEGIN
RETURN Y{construct_x(), 3};
END construct_y
Тем не менее, данные могут гарантировано инициализироваться в секции инициализации, но они всё равно должны объявляться через optional. Это точно не влечёт дополнительных расходов со стороны программиста и программы в виде явных и неявных проверок/приведений типа?
А. Понял. Нет. Просто данные модуля инициализируются так же как и локальные переменные - при объявлении. Инициализация таких данных происходит до вызова секции инициализации модуля.
К примеру, транспонирование матрицы - штука быстрая. А если это массив структур, содержащих массивы?
Не, хочется реального примера :) Мне больше видятся всякие низкоуровневые штуки, когда надо скормить кусок памяти драйверу и т.п. Но для таких случае всегда есть SYSTEM :)
Правильное. Не могу припомнить сколь-нибудь серьёзную проблему, связанную именно с неинициализированной переменной, а вот функции по нескольку тысяч строк, плохо поддающиеся пониманию и переработке из-за усеянности на самом деле ненужными return-ами мне встречаются регулярно.
Дык, согласитесь, что оригинальная проблема в функциях на 1000 строк, а не в RETURN. Думаю вы бы скорее согласились сопровождать функции на 10 строк с RETURN, чем на 1000 строк без RETURN? ;)
Я бы и не хотел, чтобы компилятор вопил по этому поводу. Это должно остаться на уровне стороннего анализатора. А помимо компилятора, код просматривает и человек, вот тут-то мы и выигрываем.
Человеком ваш код тоже воспринимается непросто. Лично мне понятнее переделанный вариант.
-
1.Необходимость больше писать приведёт к тому, что фишкой не будут пользоваться. Перепишите пример со структурами и сравните. Вспомните хотя бы, как сишники жалуются на "многословность" синтаксиса Оберона.
Ну вот я пишу на питоне тоже, там есть родные (лаконичные) кортежи. Пользуюсь ими все равно не очень часто (хотя пишу в обсуждаемом стиле - все инициализируется чем-то осмысленным). Основной недостаток - если такой кортеж пошел гулять за пределы двух/трех функций, то понять смысл того, что там лежит становится трудно. Именованная структура все же лучше. Опять же - ваш оригинальный пример довольно экстремальный :) Так что острой необходимости в кортежах я не вижу.
А в питоне разве содержимое кортэжа мутабельно?
Собственно у питона две беды -- динамическая типизация и мутабельность данных :-)
Скажем в haskell'a я часто пользуюсь кортежами, и проблем с пониманием того, что там лежит обычно не возникает -- типизация то строгая статическая.
-
А в питоне разве содержимое кортэжа мутабельно?
Собственно у питона две беды -- динамическая типизация и мутабельность данных :-)
Да, динамическая типизация усугубляет. Но у меня и в С++ такая же проблема. Начинает ходить такая тупла с двумя int. И фиг сходу скажешь - чего ж там за int'ы и какой из них тебе нужен.
Скажем в haskell'a я часто пользуюсь кортежами, и проблем с пониманием того, что там лежит обычно не возникает -- типизация то строгая статическая.
Если в языке кортеж это больше, чем анонимная структура с анонимными полями - то наверное из нее можно выжать что-то сильно полезное. Например, если представить, что в нашем случае список инициализации - это на самом деле кортеж. Или пойти еще дальше: вызов функции это на самом деле вызов функции с одним аргументом - кортежем. Но я пока не готов настолько глубоко копать...
-
Отрицательный результат - тоже результат. И хотя нас часто порывает принять за данность выполнение программы в идеальном окружении с идеальным пользователем, а всю логику обработки ошибок закинуть как можно выше, это не выход.
Классный выход. Настолько классный, что в любом распространенном ЯП уровнем выше С в каком-то виде присутствует альтернатива "отрицательным" результатам. Даже в хаскеле - при том, что там нет исключений - зато есть монады :)
Пора уже понять, что практически все функции не способны гарантировать результат.
Поняли - и придумали исключения, чтобы обойти эту проблему :) Теперь все функции могут гарантировать "положительный" результат, потому что "отрицательный" передается отдельно.
Что исключения одного класса обезличиваются при всплывании, превращаясь в нечто вроде TIOError. Что если мы хотим обработать 250 вызовов функций по-разному, нам понадобятся 250 обработчиков и их логика должна быть на том же уровне, что и вызов функций.
Они не обезличиваются. Это вы абстрагируетесь от конкретного типа, по всем правилам ООП. Хотите обработать 250 исключений по-разному - пожалуйста. Никто этого не делает, потому что никому это не надо :)
Логика обработки не может быть на одном уровне - иначе вы не сможете переиспользовать один и тот же код. Функция чтения файла не может знать, что ей делать с ошибкой открытия файла. Это может знать только конкретная операция, например операция открытия документа или операция чтения данных из файлового кэша. В одном случае документ не может быть открыт и пользователь получает внятную ошибку. В другом - данные берутся из оригинального источника и кладутся в кэш. В обоих случаях между "верхом" операции и функцией чтения файла в стэке могут быть десятки слоев абстракции, которым нафиг не нужна возня с такой ошибкой, они хотят делать свою работу.
С какой кстати высший уровень должен знать все типы исключений низших, а если не знать, то иметь лишь единственный молчаливый способ их обработать?
Действительно, с какой кстати? И почему единственный? Сколько надо - столько и сделаем. Обычно достаточно единичных обработчиков на кучу кода. И почему молчаливый? Как захотим - так и сделаем.
-
Если в языке кортеж это больше, чем анонимная структура с анонимными полями - то наверное из нее можно выжать что-то сильно полезное. Например, если представить, что в нашем случае список инициализации - это на самом деле кортеж. Или пойти еще дальше: вызов функции это на самом деле вызов функции с одним аргументом - кортежем. Но я пока не готов настолько глубоко копать...
В JavaScript во многих библиотеках (Rich Web и т.п., например, Qooxdoo) функции часто параметризуются объектами.
Т.е. типа: var panel = new qx.ui....Panel({width: 200, height: 300}).
Вообще, достаточно удобно и единообразно получается.
-
Вообще, у например F# все функции внешних .нет либ вызываются именно как функция с одним аргументом-картежем значений.
mFile.MenuItems.Add(miOpen)
Тут (miOpen) это именно что кортеж с одним элементом внутри. Нормальный вызов функции с параметром-не кортежем в F# выглядит так:
mFile.MenuItems.Add miOpen
-
Ну вот я пишу на питоне тоже, там есть родные (лаконичные) кортежи. Пользуюсь ими все равно не очень часто (хотя пишу в обсуждаемом стиле - все инициализируется чем-то осмысленным).
Основной недостаток - если такой кортеж пошел гулять за пределы двух/трех функций, то понять смысл того, что там лежит становится трудно.
Именованная структура все же лучше. Опять же - ваш оригинальный пример довольно экстремальный. Так что острой необходимости в кортежах я не вижу.
Нет ничего экстремального в инициализации по одному условию нескольких переменных и по смыслу не объединяемых в одну сущность. Ну, строго говоря, острой необходимости нет не только в кортежах, но и в...
Как минимум нужно и объявление типа по месту.
В смысле рядом с функцией? Ну да, желательно. Но тоже не то, чтобы принципиально, если модули человеческих размеров.
Модули даже человеческих размеров всё-таки велики. Вспомните как сишники жалуются, что в Паскале до переменных в функции долго добираться, а Вы аж про модули. Впрочем, записи,
нужные лишь в одной процедуре, можно объявить в ней самой, это давно есть.
статически объявленные экземпляры структур
Ну так - инициализировать при объявлении. Что не так?
Как, что? Для функций, возвращающих большие структуры, возникают большие накладные расходы на копирование. То же касается и массивов. Это можно обойти, но неизбежно вылезут другие шутки.
Точно ничего? А выделять память отдельной операцией будете? Чем поможет вспомогательная функция-конструктор, если инициализировать всё равно одной командой нужно? Как инициализировать расширенные записи при желании использовать код для базовых записей?
Ну, допустим у нас есть такой синтаксис для инициализации:...
И опять-таки, большие накладные расходы, особенно для инициализации больших структур с серией маленьких расширений. Если это устраивает, то можно и без конструкторов.
Хотя смысл статически выделенных данных по большей части в скорости работы с ними. Если она не нужна, то и статические данные не нужны, о чём я и писал. Понятное дело, что в
задачах, где хватает Питона, разработчика такое может не волновать.
И это не единственные последствия, но лень писать, ибо длинно.
Да, и всё-таки, как поможет функция-конструктор для большой структуры, если инициализировать всё-равно одной операцией?
Просто данные модуля инициализируются так же как и локальные переменные - при объявлении. Инициализация таких данных происходит до вызова секции инициализации модуля.
Нет, я имел ввиду ситуацию, когда нужно инициализировать именно в секции модуля, но мы знаем точно, что она там происходит, тем не менее везде нужно будет использовать проверку об инициализации.
Про вред размазывания инициализации по тексту модуля я уже писал когда-то, сейчас лениво, да и тогда Вам не показалось это чем-то важным.
К примеру, транспонирование матрицы - штука быстрая. А если это массив структур, содержащих массивы?
Не, хочется реального примера Мне больше видятся всякие низкоуровневые штуки, когда надо скормить кусок памяти драйверу и т.п. Но для таких случае всегда есть SYSTEM Улыбающийся
Во первых, инициализация - это за исключением наиболее простых случаев, не забитие одним и тем же значеним всего массива. Ну а возможность в оригинальном Обероне объявить большой массив записей вместе с потенциальной необходимостью тут же инициализировать эти записи вообще порождает курьёзную ситуацию.
Как пример, представьте себе большой массив, который заполняется по мере надобности и который нужно получить с минимальной задержкой, чтобы без промедлений отреагировать на действия пользователя.
Давать что-то более конкретно - лень.
... а вот функции по нескольку тысяч строк, плохо поддающиеся пониманию и переработке из-за усеянности на самом деле ненужными return-ами мне встречаются регулярно.
Дык, согласитесь, что оригинальная проблема в функциях на 1000 строк, а не в RETURN. Думаю вы бы скорее согласились сопровождать функции на 10 строк с RETURN, чем на 1000 строк без RETURN?
Проблема именно во множественных return, а также break, continue, goto, ну и головах программистах, естественно. Дело в сопровождении. Сначала функция хоть и с несколькими выходами, но небольшая, ничего криминального. Со временем функция разрастается и пора бы её разбить на множество функций, да в общем случае сделать это не так просто, поскольку return в подфункцию не засунешь, а использование механизма исключений(там где он вообще есть) только закрепит кашу, пусть и разбитую на порции. Заодно это требует от исключений быть гибкими (переносить что угодно) и быстрыми(поскольку в переходы часто закладывается нормальная логика, а не только маловероятные исключения). Легко такое сделать? Что нам об этом говорит опыт С++, Java, C#?
Человеком ваш код тоже воспринимается непросто. Лично мне понятнее переделанный вариант.
А мне - мой, что тут поделать.
Вспомнил о ещё одной неудобности. В Обероне значение переменных можно менять, передавая их через параметры в процедуры. Легко представить ситуацию, когда такими процедурами можно также инциализировать, но при таком подходе получится это только с накладными расходами.
-
Опять же - ваш оригинальный пример довольно экстремальный. Так что острой необходимости в кортежах я не вижу.
Нет ничего экстремального в инициализации по одному условию нескольких переменных и по смыслу не объединяемых в одну сущность. Ну, строго говоря, острой необходимости нет не только в кортежах, но и в...
Ну вот не знаю. В моем коде таких ситуаций (инициализация разных переменных по условию с последующим использованием этих переменных по другому условию) - не встречается. Хотите верьте - хотите нет. И я считаю такой код трудным для чтения - вместо плавного перетекания от одного к другому имеем рваные условия с возможностью запутаться - что, откуда и куда.
Ну так - инициализировать при объявлении. Что не так?
Как, что? Для функций, возвращающих большие структуры, возникают большие накладные расходы на копирование. То же касается и массивов. Это можно обойти, но неизбежно вылезут другие шутки.
Не, не понимаю, о каком копировании речь? Код "X x = f();" всегда может быть автоматически сведен к "X x; f(x);". Если, конечно, не пытаться повторять плюсовую семантику с конструкторами копирования и т.д.
Да, и всё-таки, как поможет функция-конструктор для большой структуры, если инициализировать всё-равно одной операцией?
Не, не понимаю в чем проблема...
Просто данные модуля инициализируются так же как и локальные переменные - при объявлении. Инициализация таких данных происходит до вызова секции инициализации модуля.
Нет, я имел ввиду ситуацию, когда нужно инициализировать именно в секции модуля, но мы знаем точно, что она там происходит, тем не менее везде нужно будет использовать проверку об инициализации.
Какая-то надуманная ситуация. Почему именно в секции модуля?
Про вред размазывания инициализации по тексту модуля я уже писал когда-то, сейчас лениво, да и тогда Вам не показалось это чем-то важным.
Да никто ж не заставляет размазывать. Пропишите непосредственно перед секцией. Можно даже потребовать на уровне компилятора.
Во первых, инициализация - это за исключением наиболее простых случаев, не забитие одним и тем же значеним всего массива. Ну а возможность в оригинальном Обероне объявить большой массив записей вместе с потенциальной необходимостью тут же инициализировать эти записи вообще порождает курьёзную ситуацию.
Ну я же говорил - можно порешать, если сильно парит. Например - функция конструктор для элементов или еще чего. В любом случае - я точно не хочу иметь большой массив структур с мусором. В безопасном строгом языке. Если хочу - то пожалуйста в SYSTEM. Реальный пример сильно помог бы.
Легко такое сделать? Что нам об этом говорит опыт С++, Java, C#?
Опыт говорит, что функции в конце-концов бьются. Как раз потому, что в условиях множественного RETURN сопровождать большие функции - неприятно. Отсутствие множественного RETURN - только оттягивает конец :)
Вспомнил о ещё одной неудобности. В Обероне значение переменных можно менять, передавая их через параметры в процедуры. Легко представить ситуацию, когда такими процедурами можно также инциализировать, но при таком подходе получится это только с накладными расходами.
Опять же - поясните, что за накладные расходы такие. В каком месте.
-
Нет ничего экстремального в инициализации по одному условию нескольких переменных и по смыслу не объединяемых в одну сущность.
Ну вот не знаю. В моем коде таких ситуаций (инициализация разных переменных по условию с последующим использованием этих переменных по другому условию) - не встречается. Хотите верьте - хотите нет. И я считаю такой код трудным для чтения - вместо плавного перетекания от одного к другому имеем рваные условия с возможностью запутаться - что, откуда и куда.[/quote]Обратите внимание, в цитируемом сообщении не было сказано про использование по разным условиям, поскольку в обсуждаемом вопросе важна лишь необходимость инициализации нескольких переменных по 1-му условию, не более.
Как, что? Для функций, возвращающих большие структуры, возникают большие накладные расходы на копирование. То же касается и массивов. Это можно обойти, но неизбежно вылезут другие шутки.
Не, не понимаю, о каком копировании речь? Код "X x = f();" всегда может быть автоматически сведен к "X x; f(x);". Если, конечно, не пытаться повторять плюсовую семантику с конструкторами копирования и т.д.
Потому я и написал, что можно обойти. А теперь про шутки, которые неизбежно вылезут.
struct S {int a,b};
s = S{s.b, s.a};//Прийдётся компилятор сделать шибко умным, чтобы правильно обрабатывать такие ситуации без ошибок и без накладных расходов
s = f(s);//А вот тут даже умный компилятор не знает как без накладных расходов не допустить ошибок в общем случае, прийдётся выдумывать другие хитрые механизмы.
S f(S s, int i) { return {s.b + 1, s.a / (i - 1)} }
S s = S{1, 2};//1
try {
s = f(s, 1);//2
} catch {
s = f(s, 2);//программист вправе ожидать, что до присваивания s всё ещё имеет 1-е значение.
}
Мне лениво писать ещё, просто пофантазируйте на эту тему. Например, когда параметр может оказаться частью возвращаемого значения и наоборот. Накладные расходы - это невинные шалости по сравнению с побочными эффектами подобного автоматического сведения.
Смотрю, Вы уже и в другую тему по этому поводу успели написать. Ничего, что-нибудь придумаете. Предупреждая ответ, что в случае VAR - параметров этих эффектов тоже не избежать, отвечу, что ...
Да, и всё-таки, как поможет функция-конструктор для большой структуры, если инициализировать всё-равно одной операцией?
Не, не понимаю в чем проблема...
Лень объяснять. Пусть проблемы не будет.
Нет, я имел ввиду ситуацию, когда нужно инициализировать именно в секции модуля, но мы знаем точно, что она там происходит, тем не менее везде нужно будет использовать проверку об инициализации.
Какая-то надуманная ситуация. Почему именно в секции модуля?
Потому что несколько переменных могут зависеть от общих временных данных.
Да никто ж не заставляет размазывать. Пропишите непосредственно перед секцией. Можно даже потребовать на уровне компилятора.
Как бы не так. Для того, чтобы проинициализировать осмысленным значением, бывает нужен более сложный алгоритм чем однострочник, и тут уж нужна процедура, объявленная до переменной, и, следовательно, появляется возможность размазывания. Но я не подумал в прошлый раз, что можно обойтись опережающим объявлением процедуры и минимизировать размазывание. Т.е. надо ввести возможность таких объявлений и в секции переменных.
Во первых, инициализация - это за исключением наиболее простых случаев, не забитие одним и тем же значеним всего массива. Ну а возможность в оригинальном Обероне объявить большой массив записей вместе с потенциальной необходимостью тут же инициализировать эти записи вообще порождает курьёзную ситуацию.
Ну я же говорил - можно порешать, если сильно парит. Например - функция конструктор для элементов или еще чего. В любом случае - я точно не хочу иметь большой массив структур с мусором. В безопасном строгом языке. Если хочу - то пожалуйста в SYSTEM. Реальный пример сильно помог бы.
Структуры и массивы - дело тонкое, а мусором является любое не осмысленное значение, даже если оно прописано в присваивании, а иногда само присваивание становится мусором.
struct Stack {int count, int s[N]};
Stack s;
s.count = 0;
;
Структура инициализирована или нет?
Опыт говорит, что функции в конце-концов бьются. Как раз потому, что в условиях множественного RETURN сопровождать большие функции - неприятно. Отсутствие множественного RETURN - только оттягивает конец :)
В качестве шутки - сойдёт. Вы умеете переворачивать с ног на голову.
Вспомнил о ещё одной неудобности. В Обероне значение переменных можно менять, передавая их через параметры в процедуры. Легко представить ситуацию, когда такими процедурами можно также инциализировать, но при таком подходе получится это только с накладными расходами.
Опять же - поясните, что за накладные расходы такие. В каком месте.
[/quote]
f(a);//устанавливаем значение уже объявленной
T b = {...};//мусорное присваивание, оно же накладные расходы
f(b)// ах, да - просто не надо создавать такие процедуры( так может и VAR-параметры удалить; + ещё 1 изменение ), но я уже написал про ваш хитрый автоматический механизм, так что надо.
-
Обратите внимание, в цитируемом сообщении не было сказано про использование по разным условиям, поскольку в обсуждаемом вопросе важна лишь необходимость инициализации нескольких переменных по 1-му условию, не более.
Мне кажется, что в подобных случаях изначальная проблема в правильной декомпозиции. Пусть будет одно условие:
int a, b;
if (условие)
a = ...
else
b =...
/*общий код*/
if (условие)
a;
else
b;
Вот такая штука намного естественнее смотрится приведенная к такому виду:
int a, b;
if (условие)
int a = ...
f();/*общий код*/
a;
else
int b =...
f();/*общий код*/
b;
Если, конечно, не пытаться повторять плюсовую семантику с конструкторами копирования и т.д.
Потому я и написал, что можно обойти. А теперь про шутки, которые неизбежно вылезут.
struct S {int a,b};
s = S{s.b, s.a};//Прийдётся компилятор сделать шибко умным, чтобы правильно обрабатывать такие ситуации без ошибок и без накладных расходов
Хе-хе. А вот здесь мы имеем не инициализацию, а присваивание. Сильно разные вещи. Здесь всегда будет копирование. Оно здесь будет даже если вы напишите на оригинальном обероне по всем правилам - с объявлением временной структуры, ее инициализацией и последующим копированием.
s = f(s);//А вот тут даже умный компилятор не знает как без накладных расходов не допустить ошибок в общем случае, прийдётся выдумывать другие хитрые механизмы.
Аналогично. Это присваивание.
S f(S s, int i) { return {s.b + 1, s.a / (i - 1)} }
S s = S{1, 2};//1
try {
s = f(s, 1);//2
} catch {
s = f(s, 2);//программист вправе ожидать, что до присваивания s всё ещё имеет 1-е значение.
}
Аналогично. Это присваивание.
Какая-то надуманная ситуация. Почему именно в секции модуля?
Потому что несколько переменных могут зависеть от общих временных данных.
Ну и объявите эти временные данные здесь же, не в секции. В чем проблема-то?
Да никто ж не заставляет размазывать. Пропишите непосредственно перед секцией. Можно даже потребовать на уровне компилятора.
Как бы не так. Для того, чтобы проинициализировать осмысленным значением, бывает нужен более сложный алгоритм чем однострочник, и тут уж нужна процедура, объявленная до переменной, и, следовательно, появляется возможность размазывания.
Ну правильно. Все процедуры будут объявлены до переменной и до секции. Где размазывание? Тут на самом деле более интересная проблема: если инициализировать вызовом функции, то как обеспечить, чтобы функция не обратилась к этой же переменной модуля.
Структуры и массивы - дело тонкое, а мусором является любое не осмысленное значение
Не, не скажите. С ноликами разница принципиальная. Если нолики всегда одинаковые, то и поведение всегда одинаковое (пусть даже и неправильное). А если мусор, то поведение начинает зависеть от фазы луны. Такое недопустимо в безопасном языке.
struct Stack {int count, int s[N]};
Stack s;
s.count = 0;
;
Структура инициализирована или нет?
Ошибка компиляции. Должна быть проинициализирована в точке объявления.
f(a);//устанавливаем значение уже объявленной
T b = {...};//мусорное присваивание, оно же накладные расходы
f(b)// ах, да - просто не надо создавать такие процедуры( так может и VAR-параметры удалить; + ещё 1 изменение ), но я уже написал про ваш хитрый автоматический механизм, так что надо.
Да, не надо писать такие процедуры. Если надо установить новое значение уже объявленной - пожалуйста, пишите a = f(). Да, в данном случае для a (объявление/инициализация/присваивание) накладные расходы при прочих равных будут больше, согласен. Зато есть гарантия, что структура не останется проинициализированной наполовину.
-
Мне кажется, что в подобных случаях изначальная проблема в правильной декомпозиции.
int a, b;
if (условие)
int a = ...
f();/*общий код*/
a;
else
int b =...
f();/*общий код*/
b;
Пример немного странный, потому что либо а и б не используются в общем коде, либо они взаимозаменяемые переменные, что очень частный случай, либо в общем коде есть дополнительные проверки и его естественность теряется, либо общий код не такой уж и общий. А имел ввиду я что-то совсем банальное как, например
int a, b;
if условие
a = ...
b = ...
else
a = ...
b = ...
f(a, b);//общий код, не функция
Если, конечно, не пытаться повторять плюсовую семантику с конструкторами копирования и т.д.
Потому я и написал, что можно обойти. А теперь про шутки, которые неизбежно вылезут.
struct S {int a,b};
s = S{s.b, s.a};
Хе-хе. А вот здесь мы имеем не инициализацию, а присваивание. Сильно разные вещи. Здесь всегда будет копирование. Оно здесь будет даже если вы напишите на оригинальном обероне по всем правилам - с объявлением временной структуры, ее инициализацией и последующим копированием.
s = f(s);//А вот тут даже умный компилятор не знает как без накладных расходов не допустить ошибок в общем случае, прийдётся выдумывать другие хитрые механизмы.
Аналогично. Это присваивание.
S f(S s, int i) { return {s.b + 1, s.a / (i - 1)} }
S s = S{1, 2};//1
try {
s = f(s, 1);//2
} catch {
s = f(s, 2);//программист вправе ожидать, что до присваивания s всё ещё имеет 1-е значение.
}
Аналогично. Это присваивание.
Значит, я неправильно понял Вашу мысль. Думал, что Вы имели ввиду, что X x = f()-> X x;_f(x) будет всегда, в том числе и для повторного присваивания, а на самом деле будет так X y;_f(y); x = y;
Да только это мало чего меняет в плане производительности, потому как кол-во повторных присваиваний может сильно превысить кол-во первых.
И понятно, что в приведённых примерах будет копирование в любом случае. Дело-то не в этом, а том, что при новой фишке, копирование и перерасход памяти должны выполняться вообще всегда при присваивании для простого компилятора без подвохов. А при явных механизмах программист сможет избежать этого там, где это нужно и возможно. Для циклов разница может быть большой.
Потому что несколько переменных могут зависеть от общих временных данных.
Ну и объявите эти временные данные здесь же, не в секции. В чем проблема-то?
В том, что временные переменные не должны делать вид, что они глобальные и занимать место там, где они не нужны.
бывает нужен более сложный алгоритм чем однострочник, и тут уж нужна процедура, объявленная до переменной, и, следовательно, появляется возможность размазывания.
Ну правильно. Все процедуры будут объявлены до переменной и до секции. Где размазывание? Тут на самом деле более интересная проблема: если инициализировать вызовом функции, то как обеспечить, чтобы функция не обратилась к этой же переменной модуля.
Я, конечно, имел ввиду более привычную схему. Конечно, можно всё перевернуть вверх дном, но как Вы сами заметили, это не решение, а только замена задачи.
struct Stack {int count, int s[N]};
Stack s;
s.count = 0;
;
Структура инициализирована или нет?
Ошибка компиляции. Должна быть проинициализирована в точке объявления.
Разумеется, вопрос не об этом, а о смысле.
Да, не надо писать такие процедуры. Если надо установить новое значение уже объявленной - пожалуйста, пишите a = f(). Да, в данном случае для a (объявление/инициализация/присваивание) накладные расходы при прочих равных будут больше, согласен. Зато есть гарантия, что структура не останется проинициализированной наполовину.
Иногда и бывает нужно проинициализировать наполовину, а то и практически на нуль, как со стэком или, например, при чтении из потока, к которому возвращаешься время от времени, да ещё при этом используешь прочитанную ранее часть структуры. И правильнее говорить не о гарантиях инициализации, а о гарантиях заполненности чем-то.
Или вот ещё пример
f(int a[][]) {
for (int i = 0; i < len(a, 0); i++) {
for (int k = 0; k < len(a, 1); k++) {
a[k] = i + k;
}
}
}
И ещё раз спрошу, если скорость не важна, зачем вообще статические данные?
-
А имел ввиду я что-то совсем банальное как, например
int a, b;
if условие
a = ...
b = ...
else
a = ...
b = ...
f(a, b);//общий код, не функция
Не, ну здесь просто классический случай для туплы...
Да только это мало чего меняет в плане производительности, потому как кол-во повторных присваиваний может сильно превысить кол-во первых.
С чего вдруг? У меня в коде как раз минимум повторных присваиваний. Повторные присваивания возникают, как правило, при желании экономить временные переменные. Потому что после повторного присваивания переменная, скорее всего, будет иметь уже другой смысл. А желание такое возникает от отдельной секции VAR - поэтому такая секция есть зло :)
И понятно, что в приведённых примерах будет копирование в любом случае. Дело-то не в этом, а том, что при новой фишке, копирование и перерасход памяти должны выполняться вообще всегда при присваивании для простого компилятора без подвохов. А при явных механизмах программист сможет избежать этого там, где это нужно и возможно. Для циклов разница может быть большой.
Ну блин. В крайнем случае - напишите вторую функцию, которая будет принимать существующую структуру и переинициализировать ее. Делов-то. Закроете тот жалкий 1% случаев, когда это действительно надо. Вообще не понимаю откуда такая боязнь скопировать структуру, тем более на стеке. В C++ копируется направо и налево. Не помню ни одного раза, когда это приходилось бы оптимизировать (убирать лишние копирования).
Иногда и бывает нужно проинициализировать наполовину
Не бывает никогда. Принципиально. Или сама структура "неправильная" или это хэк уровня SYSTEM.
Или вот ещё пример
f(int a[][]) {
for (int i = 0; i < len(a, 0); i++) {
for (int k = 0; k < len(a, 1); k++) {
a[k] = i + k;
}
}
}
И ещё раз спрошу, если скорость не важна, зачем вообще статические данные?
Ну да, массив, хитро инициализируется. Говорил же уже. Дайте реальный пример, когда "это" будет тормозить задачу.
-
Не, ну здесь просто классический случай для туплы...
Из-за задержки с моим предыдущим ответом Вы, похоже, выпали из контекста. Иначе сложно объяснить, почему Вы, будто бы возражая, сами написали то, к чему я и вёл.
С чего вдруг? У меня в коде как раз минимум повторных присваиваний. Повторные присваивания возникают, как правило, при желании экономить временные переменные. Потому что после повторного присваивания переменная, скорее всего, будет иметь уже другой смысл. А желание такое возникает от отдельной секции VAR - поэтому такая секция есть зло :)
С того, что повторные присваивания возникают в основном в циклах и некоторых других алгоритмах, а не из-за секции VAR (за собой такого не припомню). Но Вы-то циклов не пишите.
Ну блин. В крайнем случае - напишите вторую функцию, которая будет принимать существующую структуру и переинициализировать ее. Делов-то. Закроете тот жалкий 1% случаев, когда это действительно надо. Вообще не понимаю откуда такая боязнь скопировать структуру, тем более на стеке. В C++ копируется направо и налево. Не помню ни одного раза, когда это приходилось бы оптимизировать (убирать лишние копирования).
Это чень удобно иметь по 2-а экземпляра почти одинаковых функций, что особо актуально для функций, доступных только в исполняемых кодах. Хорошо хоть, что это жалкий % случаев.
Ну да, массив, хитро инициализируется. Говорил же уже. Дайте реальный пример, когда "это" будет тормозить задачу.
Тут я больше хотел обратить внимание на то, что часть массива (длины его измерений) используются до того, как инициализируется его тело. Подобное можно отнести и к некоторым структурам, но как мы уже абсолютно точно выяснили, это всего лишь грязный хак уровня SYSTEM.
Ваша просьба предоставить реальный пример, где вылезали бы проблемы с производительностью не более чем провокация на бессмысленные действия :) , поскольку ответ на самый крайний случай был уже озвучен.
напишите вторую функцию, которая будет принимать существующую структуру(массив) и переинициализировать ее. Делов-то. Закроете тот жалкий 1% случаев, когда это действительно надо.
-
С чего вдруг? У меня в коде как раз минимум повторных присваиваний. Повторные присваивания возникают, как правило, при желании экономить временные переменные. Потому что после повторного присваивания переменная, скорее всего, будет иметь уже другой смысл. А желание такое возникает от отдельной секции VAR - поэтому такая секция есть зло :)
С того, что повторные присваивания возникают в основном в циклах и некоторых других алгоритмах, а не из-за секции VAR (за собой такого не припомню). Но Вы-то циклов не пишите.
В циклах - это будут локальные переменные цикла. При этом, как мы выяснили, даже в случае структур никаких проблем с производительностью не будет. Если же речь идет о внешней (по отношению к циклу) переменной-структуре, то мне очень трудно представить пример, когда на каждом шаге цикла надо будет ее присваивать новому значению.
Ваша просьба предоставить реальный пример, где вылезали бы проблемы с производительностью не более чем провокация на бессмысленные действия :)
Это не провокация. Это желание показать, что это не настолько принципиальная проблема. Да, я признаю, что такой подход не всегда будет удобен и конечно у него есть свои недостатки. Но я говорю о том, что он менее "проблемный" (по моему мнению), чем существующий сейчас в обероне (отдельная секция VAR, неинициализированные переменные).
-
В циклах - это будут локальные переменные цикла. При этом, как мы выяснили, даже в случае структур никаких проблем с производительностью не будет. Если же речь идет о внешней (по отношению к циклу) переменной-структуре, то мне очень трудно представить пример, когда на каждом шаге цикла надо будет ее присваивать новому значению.
Фактически, нормальный цикл, в общем виде - это:
--код, задающий вектору варьируемых в цикле параметров начальное состояние--
ПОКА вектор не сошёлся к целевому множеству состояний ДЕЛАТЬ
-- итерация над вектором параметров --
КОН;
-- код, употребляющий конечное состояние параметров цикла--
Как Вы так собрались изолировать параметры цикла от кода до и после? Вводить синтаксические секции для кода до и кода после? Допустим.
Но мне кажется, что Вы вообще держите в голове случай с for и break внутри, и именно около break пересовываете эти значения из локальных переменных цикла во вне. Если так, то брррр.
-
Как Вы так собрались изолировать параметры цикла от кода до и после? Вводить синтаксические секции для кода до и кода после? Допустим.
Но мне кажется, что Вы вообще держите в голове случай с for и break внутри, и именно около break пересовываете эти значения из локальных переменных цикла во вне. Если так, то брррр.
Да, тут ваши "привильные" циклы с дублированием кода для первого шага и последующих - будут плохо работать :)
i := same_code;
WHILE ...i...
...
i := same_code
...
Ну извините :) Тем хуже для правильных циклов :)
-
Да, тут ваши "привильные" циклы с дублированием кода для первого шага и последующих - будут плохо работать :)
Правильность, в естественнонаучном смысле, не может зависеть от "наши-ваши" :)
Дублирование отображает элементарный объективный факт, разницу на 1 между количеством некоторых операций в развёртке некоторого процесса, грамматикой которого является алгоритм. Хоть убиццца ап стену, но если природа процесса такова...
Если, конечно, хорошо понимать дело, то можно предложить кое-что, как это сделал, например, Пётр Алмазов:
http://forum.oberoncore.ru/viewtopic.php?f=86&t=3159
-
Да, тут ваши "привильные" циклы с дублированием кода для первого шага и последующих - будут плохо работать :)
Правильность, в естественнонаучном смысле, не может зависеть от "наши-ваши" :)
Не хотелось бы опять обсуждать циклы. Поэтому ответ такой: если имеет место копирование структуры (уже существующей, потому что она нам нужна до или после цикла) и оно тормозит (потому что оно в цикле и цикл больше ничего полезного не делает), то можно завести "дублера", который будет переинициализировать поля существующей структуры.
-
В циклах - это будут локальные переменные цикла. При этом, как мы выяснили, даже в случае структур никаких проблем с производительностью не будет. Если же речь идет о внешней (по отношению к циклу) переменной-структуре, то мне очень трудно представить пример, когда на каждом шаге цикла надо будет ее присваивать новому значению.
Ну представьте себе, к примеру, любой циклический алгоритм нахождения числа(пусть Фибоначчи), для длинных чисел. Можете пофантазировать на тему, когда массивы в числах динамические и статические.
Это не провокация. Это желание показать, что это не настолько принципиальная проблема. Да, я признаю, что такой подход не всегда будет удобен и конечно у него есть свои недостатки. Но я говорю о том, что он менее "проблемный" (по моему мнению), чем существующий сейчас в обероне (отдельная секция VAR, неинициализированные переменные).
Так и я не спорю с тем, что Ваш подход невозможен, а как раз с тем, что он менее проблемный, в особенности для языка уровня Оберон.