Ремесло программиста

Объявление

форум на движке phpBB доступен для тестирования
www.strategia.space
www.strategia.space/forum/
по предложению Лиса - канал на Matrix - #remdev:matrix.org

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » Ремесло программиста » Валентина » Описание языка


Описание языка

Сообщений 31 страница 60 из 62

31

Павиа написал(а):

Породить объект и выйти из функции так что-бы он жил?

Если внутри функции создать новый поток/нить выполнения, то у него/неё создастся новый стек, а там будут новые объекты, пока нить не завершится. Но я не заметил многопоточности (операции запуска нити) в описании языка.

32

Породить объект и выйти из функции так что-бы он жил?

Создайте объект до входа в функцию.

На этом половину ООП базируется: инструктирование, фабрики, IoC и тд.

Вам не угодишь. Когда говоришь, что есть разница, Вас не устраивает и Вы ищите общие черты. Когда находятся расхождение, Вам опять не то :). В функциональном программировании как-то живут с таким положением дел и никто не жалуется. Я не вижу проблем в создании объекта раньше функции.
создать подсистему х
х=какая_то_функция_типа_фабрика()

33

Осталось понять, почему в жизни так не делают. Должна быть причина.

34

Осталось понять, почему в жизни так не делают. Должна быть причина.

В функциональном программировании так делают. Наверно тоже есть причина. А причина как мне видится в свободном манипулировании ссылками. Когда ссылка может указывать на объект, а может на другой, а может на третий и т.д. Генерировать ссылки это одно. Генерировать объекты это другое.

35

ВежливыйЛис
Человек не владеет инверсией управления. Но и функциональная парадигма не мешает. Придётся в функцию добавить ещё один параметр и рекурсивно вызывать функцию. Что будет приводит к громоздким конструкциям. Или по ходу патчить функцию то туда то обратно.

36

ВежливыйЛис написал(а):

Осталось понять, почему в жизни так не делают.

особенности коллективного труда

Павиа написал(а):

Придётся в функцию добавить ещё один параметр и рекурсивно вызывать функцию.

Кол-во передач сокращается при возможности вложения с видимостью вышестоящих данных.

37

utkin написал(а):

В функциональном программировании так делают. Наверно тоже есть причина.

В функциональном программировании существуют функции высших порядков, у которых в параметры можно передать другую функцию. А такая подстановка равна созданию объекта (замыкания) в куче. Зря что-ли говорят, что в функциональном программировании функция - это первоклассный объект? А где куча, там мусор. Лисп тому примером - вроде функциональный язык, а сборщик мусора есть.

у тебя, функций высшего порядка нет.

https://softwareengineering.stackexchan … collection

Вот тут пишут, что делают функциональные языки без GC:
https://stackoverflow.com/questions/995 … -collector
(They experimented with purely region-based memory management (i.e. no garbage collector))

А тут пишут, что регионы получаются с излишне большим временем жизни (приводят к перерасходу памяти по сравнению с GC):
https://en.wikipedia.org/wiki/Region-ba … management
Systems using regions may experience issues where regions become very large before they are deallocated and contain a large proportion of dead data

Вдруг твой язык - это прорыв в этой области. Тогда будет понятно, зачем он нужен.

Отредактировано ВежливыйЛис (2017-07-18 01:46:38)

38

В функциональном программировании существуют функции высших порядков, у которых в параметры можно передать другую функцию. А такая подстановка равна созданию объекта (замыкания) в куче. Зря что-ли говорят, что в функциональном программировании функция - это первоклассный объект? А где куча, там мусор. Лисп тому примером - вроде функциональный язык, а сборщик мусора есть.
у тебя, функций высшего порядка нет.

Почему нет? Во-первых, функции системы и соответственно в них можно добавлять функции. Во-вторых, анонимных функций нет, но внутри функций на этапе написания программы также можно объявлять функции. То есть Вы просто на время выполнения функции добавляете в нее новую.  С точки зрения реализации такой модели функция будет иметь список ссылок, доступных для поиска вложенных функций. Я обдумываю как это должно выглядеть синтаксически, там ниже по тексту еще не дописано, но есть раздел "Спецификаторы функций" (или как-то так) - это такие вот метапараметры, которые будут описывать характер параметров функции. Ну грубый пример:
функция Сумма( Параметр1 как ряд) Ряд указывает, что Параметр1 это последовательность аргументов, то есть функцию можно будет вызвать так: сумма(х, у, z)

Или функция Сортировка(сравнение как функция, данные).  То есть на время исполнения в область видимости Сортировки будет добавлена функция сравнение. Я еще сам не до конца продумал, но в целом будет выглядеть как-то так. Вообще Вы сильно далеко в глубь копаете. Вопрос хранения данных это проблема реализации языка, а не проблема алгоритма. Иными словами как реально располагаются данные внутри модели это не дело программиста. Все что ему нужно знать, это то как описать дерево систем, чтобы достигнуть результата. Все остальное должен делать интерпретатор (и если повезет, то и компилятор).

Вдруг твой язык - это прорыв в этой области.

Сильно сомневаюсь  :crazyfun:  Основное правило в языке - максимально общие правила для всех и минимум индивидуальных костылей. Поэтому и типов данных всего два - строки и системы.

Человек не владеет инверсией управления.

Это тоже копание в реализации, а не в языке. Я уже привел пример - Вы можете заранее создать систему, а потом натравливать на нее свои паттерны. Эффект будет абсолютно одинаковый. В чем проблема? Вы создаете на фабрике объект. Я предлагаю его на фабрике модифицировать.

Но и функциональная парадигма не мешает. Придётся в функцию добавить ещё один параметр и рекурсивно вызывать функцию. Что будет приводит к громоздким конструкциям. Или по ходу патчить функцию то туда то обратно.

О чем это Вы? Дайте условный пример, какой-нибудь, чтобы понять.

Новая версия: https://yadi.sk/i/UOoaULEi3L9nsu

39

Породить объект и выйти из функции так что-бы он жил?

Создайте объект до входа в функцию.

Или функция должна уметь создавать объекты в стеке той функции, которая её вызвала: можно посмотреть тут и тут: Размещение объектов переменной длины с использованием двух стеков и Реализация двухстековой модели размещения данных.

40

Или функция должна уметь создавать объекты в стеке той функции, которая её вызвала

Да видите, еще решение. Но опять же это реализация возможностей языка, а не сами возможности.

41

Юрий написал(а):

Или функция должна уметь создавать объекты в стеке той функции, которая её вызвала: можно посмотреть тут и тут

utkin написал(а):

Да видите, еще решение.

Это не решение задачи в целом, а лишь заглушка на один уровень передачи, что приведёт больше к запутыванию.
Заявляется же отсутствие перекладывания ответственности на вложенные функции.

42

Решение задачи в целом в сравнении с решениями для частных случаев выигрывает в универсальности. Но зачастую проигрывает по другим критериям, типа простоты и скорости. Например, однопоточное приложение – частный случай многопоточного. И для одного потока проще решать означенную выше проблему без кучи.

43

Юрий написал(а):

Решение задачи в целом в сравнении с решениями для частных случаев выигрывает в универсальности. Но зачастую проигрывает по другим критериям, типа простоты и скорости. Например, однопоточное приложение – частный случай многопоточного. И для одного потока проще решать означенную выше проблему без кучи.

Заявленная пара стеков не решает проблему управления памятью дальше одного уровня передачи - потребуется перекопировать по стеку несколько раз, что как-то не похоже на "скорость и простоту". Для сравнения: в Паскале есть видимость стековых переменных во вложенных функциях, ничего не требуется передавать вообще, достаточно объявить и заполнить переменную на нужном уровне.
Но проще-то как раз обойтись без стека вообще, особенно вначале. А для сравнения по скорости надо учитывать много факторов, начиная с кратности решения задачи, нагрузки на среднестатистическую систему использования, задержки м/у действиями пользователя. Да и запросто может оказаться, что многопоточное на куче обойдёт однопоточное без её применения.

44

Немного дополнил описание: https://yadi.sk/i/jz_xqnKy3Lhejc

45

Красивое и простое описание системы:

Определение  системы: система есть совокупность  взаимосвязанных элементов,  обособленная от среды  и взаимодействующая с ней  как целое.

Функционирование  уровень и развитие
Уже на этапе «черного ящика» различают  два типа  динамики системы:  функционирование и развитие. Под  функционированием подразумевают  процессы, которые  происходят в системе, стабильно реализующей фиксированную цель. Функционируют, например, часы,  городской транспорт,  радиоприемник и т.д.
Развитием называют то, что происходит с системой при изменении ее целей. Характерной  чертой развития является тот  факт, что существующая структура  перестает соответствовать  новой цели. Для  обеспечения новой  функции приходится  изменять структуру,  а иногда и состав системы, т.е. перестраивать  всю систему. Возможны и такие системы,  для функционирования  которых, какие-то  ее подсистемы должны  быть постоянно  в развитии.

Взято из работы по экономической безопасности.

46

Немного дополнил описание: https://yadi.sk/i/1Ihhc6Lq3MC7by

47

Думая над контролем параметров с помощью пост и предусловий, я пришел к мысли, что условий, применяемых к аргументу может быть очень много и это вероятно может загромождать вызов функций. Поэтому есть идея введения усеченного подмножества функций - предикатов. Это функция (со всеми вытекающими свойствами систем), которая будет иметь один параметр на входе и один на выходе. Она предположительно должна использоваться в логических условиях и в контроле параметров. Будет иметь упрощенную форму записи.  Ну например:

Предикат: Диапазон
   * > 0
   * < 10
   * >< 5
Конец

Здесь подразумевается что * это входящий параметр (который естественно тоже система). Предикаты будут возвращать предопределенные значения (которые можно будет трактовать как истина/ложь) в зависимости от того, выполнены ли все заданны условия или нет.
Смысл этой конструкции в том, чтобы абстрагировать контроль параметров функций и не загромождать их вызов огромным количеством проверок.

48

utkin написал(а):

Смысл этой конструкции в том, чтобы абстрагировать контроль параметров функций и не загромождать их вызов огромным количеством проверок.

А почему нельзя решить этот вопрос типизацией? Набор допустимых значений это же тип данных.

49

MihalNik

MihalNik написал(а):

Набор допустимых значений это же тип данных.

Ну да в питоне так сделано.

utkin

utkin написал(а):

Предикаты будут возвращать предопределенные значения (которые можно будет трактовать как истина/ложь) в зависимости от того, выполнены ли все заданны условия или нет.Смысл этой конструкции в том, чтобы абстрагировать контроль параметров функций и не загромождать их вызов огромным количеством проверок.

А вот этого я не понял. Каким образом вы хотите избавится от проверок или абстрагироваться? Для чего это надо, какая цель?

Постусловия существуют для двух целей чтобы передающий мог проверить себя и что-бы приемная сторона могла проверить входные параметры.
К примеру есть такая техника как сокрытие, тогда проверку таких условий можно вынести в отдельную функцию. Выделив проверку в отдельную функцию её могут использовать не только вызываемая сторона, но и все кто хочет вызвать.

50

А почему нельзя решить этот вопрос типизацией? Набор допустимых значений это же тип данных.

1. Попробуйте это написать и поддерживать.
    а) Это долго
    б) Это медленно при разработке
    в) Это медленно при работе
2. Типы в языке не автоматические (это значит, что их контроль не проводится), вместо этого используются пост и предусловия. В описании про функции и спецификаторы это написано. Типы играют очень слабую роль, так как фактически типов всего два - это строки и системы, остальное их вариации.
3. Да это можно представить как тип, но этот тип не для хранения данных, а для проверки функций. Потому что для другой функции вполне может быть допустим такой интервал (ей например, нужно чтобы система в принципе содержало число, а какое для нее не важно - пример, организация какого-нибудь списка, массива и пр. кортежей). Или та же арифметика. Имеется два вызова функции для одной системы. В одной происходит сложение, а в другой деление (ну хоть бы и калькулятор :) ). И? Для одной ноль можно, а для другой функции ноль нельзя. Заводить два типа? Выполнять преобразование типов? Типы удобны для примитивов, но более сложные вещи поддерживать типами становится сложно. И посмотрите на с++ или на паскаль. Вывод сообщения на экран уже может требовать преобразование одного типа в другой. А вот возьмите большой проект (более 1000 строк кода) и просто посчитайте сколько раз и зачем выполняются явные и неявные преобразования типов. Разве они нужны для решения задачи? Это просто дань компьютеру, организации процесса, а не описание алгоритма.

А вот этого я не понял. Каким образом вы хотите избавится от проверок или абстрагироваться? Для чего это надо, какая цель?

Ну вот вызов функции:

а=какая_то_функция(б, в, г, д)

Вводим указанные условия проверки, допустим имеется операция "соответствие" - ::

а=какая_то_функция(б, в, г, д :: >0, :: <10, :: ><5) :: >0, :: <10, :: ><5

Громоздко получается, и это при том, что функция получает параметры и возвращает результат имеющих один вид (тут уже наверно термин тип не совсем подходит). Представьте, что все параметры различны - функция станет абсолютно не читаема.
А здесь мы просто сокращаем запись немного:

Предикат: Диапазон
   * > 0
   * < 10
   * >< 5
Конец

И потом вызов функции: а=какая_то_функция(б, в, г, д :: Диапазон) :: Диапазон
Уже более лаконично, потом можно писать различные короткие тесты, просто меняя предикаты (или содержимое предиката в одном только месте, а не в пяти вызовах функций, разбросанных по коду).
Это только размышления пока - синтаксис условный.

К примеру есть такая техника как сокрытие, тогда проверку таких условий можно вынести в отдельную функцию. Выделив проверку в отдельную функцию её могут использовать не только вызываемая сторона, но и все кто хочет вызвать.

Ну вот я об этом и думаю, может просто немного кривовато, но пришел к какому-то похожему решению. И потом эти функции (предикаты) можно еще использовать в обычных условиях тип if-then-else.

51

utkin написал(а):

И потом вызов функции: а=какая_то_функция(б, в, г, д :: Диапазон) :: Диапазон

И что хочешь сказать это не типы?
Классические типы.
Что-бы не было грамоздко обычно применяют слабую типизацию. Int, int, int
А если нужна средняя строгость, то описываем каждый тип. По отдельности.
Type TForB=10..20
Type TForC=(Const10,Const20, Const30)

А совсем строгая это ООП.
В ООП, что-бы не делать каждый раз проверку применяют инкапсулирование. вводится свой тип.

К примеру
QPair<QHostAddress, int> QHostAddress::parseSubnet(const QString &subnet)
typdef QPairHostAddressSubnet = QPair<QHostAddress, int>
И далее этот  QPairHostAddressSubnet тип передаётся в параметрах.

а=какая_то_функция(ОбъектБВГД :: О_БВГД) :: Диапазон

Правда в результате имеем вместо 3000 функция 3000-объектов.

Иерархия типов достаточно хорошо проработана в современных языках. Но если почитать Буча, то можно увидеть что на самом деле в жизни требуется разнообразие такое какое и не снилась. Причём с кучей противоречий, исключений, и невероятных метаморфоз. Поэтому программистам и и приходиться искать компромисс.
Мягкая, средняя и жёсткая типизация. А также различные переходы и полутона добиваемые в виде шаблонов и генераторов и макросов.

Отредактировано Павиа (2017-09-13 16:45:57)

52

И что хочешь сказать это не типы?
Классические типы.

Нет конечно - это контракты. Типы для данных, я там даже выделил жирным специально в чем разница. Типы навсегда - Int не может быть строкой. Контракт нужен только для отладки. Когда функция исправна в контракте нет нужды и в релизе проверку условий можно исключить тем самым сделав код быстрей.

Иерархия типов достаточно хорошо проработана в современных языках.

Ответ на вопрос что делать с бесконечным преобразованием типов в программе по прежнему нет. Давайте не будем нахваливать систему, лучше поговорим о недостатках.

53

utkin написал(а):

Нет конечно - это контракты.

Диапазоны так диапазоны, контракты так контракты. Могут ли там быть переменные значения?

54

Диапазоны так диапазоны, контракты так контракты.

Разница в том, что диапазон он для данных. И там вообще может быть не диапазон. Диапазон он в примере представлен. Одни и те же данные для разных функций должны удовлетворять разным условиям. Точней не всегда должны, а могут вообще говоря. Снова вспомним арифметику - функция для сложения например принимает целые числа. Для деления не включат ноль. Данные остались прежние, что тип поменялся? А здесь перед вызовом функции идет проверка предусловия, что деление не может иметь аргументом ноль (сами данные нулем могут быть для других функций).
Вот пример две функции в калькуляторе:
1) Function Adder(x, y: Integer): Integer;
2) Function Divider(x, y: Integer): Integer;
Очевидно что тип Integer совершенно не решает проблему Divider  для аргумента у. Ваши предложения? Завести еще один тип? Integer без нуля? То есть потенциально Integer в данном примере источник ошибок и контроль типов не решает проблему деления на нуль. И второй косяк в печатании:

Memo1.Lines.Add('Результат: '+IntToStr(x));

Разве в алгоритме калькулятора где-то есть преобразование типа? Нет, это особенность реализации. Я понимаю, что и мое видение не решает многих проблем, но оно должно уменьшить количество преобразований.

Могут ли там быть переменные значения?

В смысле? Я не совсем понял вопроса, дайте пример как Вы хотите. Диапазон в примере это и есть имя функции-предиката, не ключевое слово. Назовите по-другому. Или Вы хотите косвенную адресацию? Я думал об этом вскользь. Для функций такая фишка должна быть, типа вызвать функцию по содержимому строки. Так как предикат это разновидность функции, на нее должны распространяться эти правила. И второе - функция это система, а системы можно переименовывать. Следовательно, Вы можете поступить так - передать в функцию более высокого уровня параметром предикат, переименовать его так как Вам нужно и скормить его для вызова.
Ну например, есть функции а, б, в и предикат г.
В функции б требуется отследить работу функции в (допустим, что бы результат в соответствовал условиям г). Тогда функция а, в зависимости от каких-то условий передает предикат д (а может е, а может ё) в функцию б. Функция б переименовывает ё в г и отслеживает вызов функции в. Функция в исполняется и ее результат сравнивается с в. При этом в может быть и д и е и ё и т.д.
Я не знаю насколько это практично само по себе. Просто такая возможность есть, она предоставляется системам. При добавлении некоторых простейших механизмов можно даже сделать так, что в ряде случаев даже порядок расположения параметров будет не важен. Можно будет сравнивать по типу. Например, через foreach пройтись по параметрам и если он предикат, переименовать его в г (а если нет, значит генерить ошибку или использовать какой-то свой, может пустой предикат, который всегда True. Ну или что-то типа того.
Конечно, когда нужно два предиката, отличить их будет сложней. Но если предикат один, а остальное прочие данные, то в принципе все равно каким он передан в функцию в качестве первого параметра или второго. То есть при правильном проектировании сам порядок расположении входящих параметров в функции теряет смысл.
Есть такая простая идея - при старте функции она получает две подсистемы параметры и результат. Так как это будет использоваться очень часто можно обозвать их как-нибудь. Допустим минус это параметры (удобно - не нужно переключать раскладку и всего один символ), а плюс это результат функции.
В параметры копируется все что туда передали в качестве подсистем в том порядке в каком они там следуют. Соответственно для их пакетной обработки нужен только foreach. Дальше определяем природу системы и работаем. Смысл в том, что если точно известно что перед нами, то не важно как оно располагается в аргументах. Пока коряво, но как-то так.
Это также открывает ряд мелких плюшек - например, написать функцию по лисповски с произвольным числом параметров (тут только нужно очень тщательно проработать синтаксис). Типа Сложить (1, 2) или Сложить (1, 2, 3, 4). Если передавать параметры как описано выше тем же Foreach можно без проблем одним кодом складывать хоть один аргумент, хоть тысячу (правда в этом тоже нет особого смысла - ведь все параметры можно просто засунуть в одну надсистему и передавать  уже ее).

55

utkin написал(а):

В смысле?

В смысле это чистые функции, ну т.е. только для вх. параметров?
Не совсем ясен смысл прямо отдельного выделения и названия. Ну вызов функции. Там первым делом вызвали проверку входных данных, которая просто логическая функция и м.б. использована как угодно. Не является ли абстракция избыточной?

utkin написал(а):

Так как предикат это разновидность функции

Предикатом называют любую функцию логического значения, специализация может неверно сузить общее понятие.
Хотя если не сужать, то одно слово "предикат" короче словосочетаний вроде "функций возвращающих логическое значение".

utkin написал(а):

Допустим минус это параметры (удобно - не нужно переключать раскладку и всего один символ), а плюс это результат функции.

Напомнило ключи консольных программ, но я бы сделал прямо наоборот:
функция + параметры - результат
Просто минус похож на тире=)

56

В смысле это чистые функции, ну т.е. только для вх. параметров?

Это функции для:
- проверки входящих параметров;
- проверки результата функции;
- использования в логических условиях блоков if-then-else.
В принципе наверно их можно и в обычных выражениях использовать, я просто еще не продумал как это будет выглядеть.

Предикатом называют любую функцию логического значения, специализация может неверно сузить общее понятие.

Ну да об этом просто надо написать в описании языка.

Напомнило ключи консольных программ, но я бы сделал прямо наоборот:
функция + параметры - результат
Просто минус похож на тире=)

Ну синтаксиса пока нет вообще никакого - просто наметки. Можно и + для аргументов, - для результата.

57

Оказывается есть часть стандарта UML 2.0, называется OCL (при этом он может использоваться независимо). Это самый что ни на есть стандарт контрактного программирования (просто рассматривается немного под  другим углом и немного с другими целями).
Немного тут: sp.cmc.msu.ru/temp/OOAD-04.ppt
При рассмотрении ограничений на типы запись очень близка к выводу типов (при описании инвариантов):
Int + Int -> Int
Init / Int -> Real
Real + Real -> Real
Real / Real -> Real
и т.д.

58

И вот сейчас, когда пришло время тестов и сборки интерпретатора из основных кусков возник вопрос диалектов. Нечто подобное рассматривал Юрий на своем сайте.  Суть проблемы в том, чтобы дать возможность использовать в одной программы код из разных диалектов. А для этого в начале исходника нужно как-то указать на каком диалекте написан текст программы. Ну для основного диалекта, используемого по умолчанию достаточно будет ничего не указывать и интерпретатор "поймет" что работает по умолчанию (причем интерпретатор должен уметь выбирать из двух вариантов - краткая форма для записи или подробная форма для чтения исходных текстов). А вот для самописных синтаксисов нужна какая-то команда, которая будет встречаться раньше остальных и указывать интерпретатору на какой язык переключиться.
Вторая грабля всплыла только в момент реализации. Суть беды следующая - в одном исходнике теоретически могут быть такие правила, которые не позволят вызвать функцию из другого исходника. Например, если токеном объявлен символ ? (знак вопроса), то нельзя будет вызвать функцию равно_нулю?, так как такая функция просто не увидится интерпретатором. Он вместо функции равно_нулю? будет видеть две лексемы отдельно равно_нулю и ?. Вероятность такого исхода мала, но она в принципе существует. Можно конечно придумать костыль типа написать обертку над функцией, так чтобы она была без экзотических символов, чтобы корректно читалась по правилам вызывающего юнита. Можно вызывать косвенно (например, символьно имя функции хранить в переменной).
У кого какие идеи, есть специалисты по "диалектизму" :) ? Уже сейчас понятно, что надо готовить какое-то пособие по составлению диалектов языка и описывать момент с отдельными токенами.

59

Запретить диалекты. Есть Code Rules или по русски "стандарт предприятия(СТП)" в котором описаны общие правила для единообразия.

Требования единого стиля программирования не ограничиваются одним форматирование кода, а включают:
1. Требования к форматированию кода.
2. Требования к выбору имён переменных функций, модулей, библиотек.
3. Требования к защите программного обеспечения и требования к устойчивости.
4. Требования к тестам и тесто пригодности исходного кода.
5. Требования к библиотекам, требования к программам, требования к плагинам.
6. Требования к проведению оптимизации кода.
7. Требования к коду зависящему от целевого процессора.
8. Требования к написанию текстов программ на псевдоязыке.
9. Требования к эргономики и дизайну формы.

Примером такого стандарта является: https://en.wikipedia.org/wiki/MISRA_C
А если говорить число о форматировании: https://en.wikipedia.org/wiki/Coding_conventions
Требования к дизайну https://ru.wikipedia.org/wiki/HIG

utkin написал(а):

Вероятность такого исхода мала, но она в принципе существует.

Сплошь и рядом. Для этого к примеру придумали пространства имён namespace в си++ - правда там он сделан криво.
А в документации на ПО требуют указывать среду разработки. Иначе совмещения тыстирование вызывают трудности.

Отредактировано Павиа (2017-09-21 16:33:46)

60

Запретить диалекты.

Не хочется :(
Если топтаться на месте новое не появится. Надо пытаться двигаться дальше.


Вы здесь » Ремесло программиста » Валентина » Описание языка