System и Random

Вот такой код:

uses System;

begin
  var randomNumber: Integer;
  randomNumber := random(42);
  writeln(randomNumber);
end.

в версии “3.1, сборка 1230 (27.04.2016)” будет выдавать ошибку о невозможности приведения Integer к System.Random. Похоже, что описание функции Random скрывается описанием класса System.Random.

Очевидным ответом было бы “Не стоит использовать System почём зря”, но System подключается автоматически при попытке написания WPF-приложения, чем несколько осложняет написание оконных приложений с использованием ГПСЧ. Конечно, всегда можно написать

var rnd := System.Random.Create();

и использовать потом

rnd.next();

и

rnd.nextDouble();

, но необходимость в оконном и консольном приложениях использовать различный синтаксис для ГПСЧ несколько огорчает.

1 лайк

А как Вы хотите чтобы эта проблема решалась?

А как Вы хотите чтобы эта проблема решалась?

Зависит от направления развития языка.

  • Если Вы хотите развивать объектно-ориентированную составляющую PABC.Net , то пометьте функции Random как deprecated и в итоге избавьтесь от них. В конце концов, в Java и C# так и сделано.
  • Если хотите сохранять совместимость с legacy-кодом, добавьте обработку одинаковых имён классов и функций - то есть, если какой-то идентификатор одновременно объявляет и имя класса, и функцию, пробуйте подставлять оба значения и выбирайте то, которое будет иметь смысл. Если имеют смысл оба варианта - ошибка.
  • По самой крайней мере - предупреждайте о конфликтах имён функций и классов.

В принципе, если учесть, что напоролся я на эту проблему при написании приложения Windows Forms, можно просто изменить шаблон таким образом, чтобы пространство имён System (как я понял, Ваше uses примерно соответствует C#'ному using?) по умолчанию не использовалось, а ко всем типам/классам из System приписать этот самый префикс. Исходной проблемы это не решит, но её наличие хотя бы “спрячет”.

Мне кажется, Вы просто не умеете пользоваться пространствами имён Object Pascal. Странно помечать функцию из стандартного модуля как запрещенную.

Напомню, что в Object Pascal, в отличие от, скажем, C#, нет конфликтов имён если подключаются разные модули, содержащие одни и те же имена. Если бы Вы на C# подключили два файла и в каждом из них был бы описан класс Random на глобальном уровне, то была бы ошибка компиляции. В Object Pascal этого нет, поскольку конфликты имён там решаются по-другому. В вашем случае строка

uses System;

трактуется как

uses PABCSystem,System;

и имена ищутся в модулях и пространствах имён из этого списка справа налево. Поскольку System - первый справа - в нём и находится.

Чтобы сделать так как Вы хотели, достаточно просто написать

PABCSystem.Random(10)

или

uses System,PABCSystem;

а не развивать объектно-ориентированное программирование как Вы хотели.

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

Этого отрицать не могу, действительно с такими правилами не знаком.

Вот это - очень ценная вещь, и с этого и надо было начинать. Не уловил, что по умолчанию для любого файла подключается модуль PABCSystem (да и о существовании его как-то догадывался весьма смутно).

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

Но ситуация, как видно, меняется, когда имена совпадают у функции и у типа. Компилятор начинает воспринимать запись <имя-функции>(<переменная>) как попытку привести переменную к новому типу.

При этом, кстати, IntelliSense будет радостно подсказывать, какие аргументы можно передать функции. И ошибка компилятора тоже не всегда понятна - как человеку понимать фразу “У операции преобразования типов допустим только один параметр” или “Ожидалась переменная” и выделенную красным строчку с “вызовом” Random? Было бы гораздо приятнее и проще видеть уточнение вида “Random - класс, объявленный в System, перекрывает функцию Random из PABCSystem”.

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

Давайте это обсудим. Я согласен, что интеллисенс у нас слабый потому что он работает по слабой семантике. Это сложная проблема, но мы постараемся со случаями, аналогичными Random, это исправить.

То, что показывает Интеллисенс, - это баг, и спорить с этим бессмысленно.

По поводу сообщения об ошибке. Давайте на него посмотрим.

Вот программа

uses System;

begin
  var r := Random(10);  
end.

Вот сообщение об ошибке:

Невозможно явно преобразовать тип integer к типу System.Random

Это очень точное и правильное сообщение об ошибке. То, что Random - тип, а Вы считали, что это - имя функции - кто ж Вам виноват. Но после того как сообщение получено - Вы задумались - ага! Random - имя типа, значит, наверное, оно - в System, ну да - точно - System.Random - ошибка об этом и говорит. Надо лишь вспомнить, что в Паскале Random(10) трактуется в этом случае как преобразование типа.

Это - проблема Паскаля, что функции с одним параметром выглядят как и явные преобразования типов - если бы мы выбирали синтаксис - мы бы выбрали другой.

Перегрузка в таких языках как C#, C++ работает только в одном пространстве имён, перегрузка в Object Pascal на удивление существенно более сложная. Она работает через пространства имён модулей, но исключая случай полного (ну почти полного) совпадения параметров у функций. Но если перегруженные версии функций перекрыло такое же имя из следующего модуля, не являющееся именем функции, то всё. Что у Вас и произошло.

Выдавать какие-то подсказывающие сообщения нельзя, потому что это штатный момент, а не ошибка.

Например:

var sin: integer; // sin - уже нельзя будет пользоваться как функцией - сам виноват
      real: real; // real уже не тип
begin

end.

На лекциях приходится рассказывать эту ерунду чтобы студенты были готовы, но это - тёмные стороны языка (у каждого языка такие тёмные стороны есть)

Это некоторый оффтопик уже, но не рассматривали ли Вы возможность использовать в качестве источника данных для IntelliSense информацию от компилятора, сделав интерфейс наподобие libclang’овского для code completion? Скорее всего, это было бы довольно трудоёмко, но в итоге дало бы выигрыш в “честном” IntelliSense (который точно знал бы, что подсказывать можно, а что нельзя) и дало бы возможность на не-Windows системах использовать, по сути, любой редактор с поддержкой плагинов (хоть тот же vim с его YouCompleteMe).

Хорошо, давайте проведём похожий эксперимент.

uses System;

begin
  var r := Random;
end.

“Program1.pas(4) : Ожидалась переменная”


uses System;

begin
  var r := Random(); // Может, тут синтаксис вызова как в других языках?
end.

"Program1.pas(4) : У операции преобразования типов допустим только один параметер "

uses System;

begin
  var r := Random(24, 42); // А если нужно число из диапазона?
end.

“Program1.pas(4) : У операции преобразования типов допустим только один параметер”

Я, конечно, могу ошибаться, но ни одно из этих сообщений не сказало мне ничего про System.Integer. Именно в таком виде, кстати, я и столкнулся с этой проблемой - и только после попытки использовать код, аналогичный Вашему, я увидел упоминание о System.Random. (Попутный багрепорт: слово “Параметр” написано неверно, но это к нашему разговору отношения не имеет)

Возможно, я не прав, возможно, знакомство с OpenGL на меня повлияло, но я считаю, что нет такого понятия, как слишком подробное сообщение об ошибке. Понимаете, GL_INVALID_ENUM или GL_INVALID_OPERATION - это тоже весьма точные и правильные сообщения об ошибках, но вот точно сказать, почему и где произошла ошибка, они не помогут. Но там на то есть причина - это всё разрабатывалось в 90-е, доступа к исходному коду у сторонней библиотеки нет, да и есть уже KHR_debug, дающий куда больше информации об ошибке. Да тот же clang с радостью покажет не только строку, в которой компилятор “споткнулся”, но и то место, которое ему помешало, начиная с какого символа идёт неверная конструкция! Так что более длинные, подробные, с указанием неверных конструкций сообщения об ошибках - большое, большое добро.

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

if (c = someFunc()) 
{
  /*Do stuff*/
}

, и она вполне штатная и иногда даже нужная. И тем не менее что у clang’а, что у gcc (у msvc это C4706 - кстати, у этого предупреждения есть целая страница, с разъяснениями и примерами кода - это к вопросу о подробных сообщениях компилятора) присутствуют флаги, помечающие такую строку как предупреждение (и даже как ошибку). И есть статические анализаторы, указывающие программисту, что его код, возможно, работает не так, как он думает. Хотя всё это - штатные моменты, а не ошибки, просто в этих штатных моментах чаще всего ошибки и скрываются.

А кто Вам, собственно, мешает? В одной из соседних тем, кажется, Вы говорили (возможно - полушутя), что конструкция

for i := 0 to n - 1 do something(i);

с объявлением i вне цикла скоро станет предупреждением, а затем и ошибкой. Единого стандарта Pascal - если верить Вашим презентациям - де-факто для широкой публики не существует, Вы 100% совместимость не гарантируете (безусловно, о совместимости сказано - но с чем? Диалектов паскаля, несовместимых между собой, как я понял, хватает). По сути, PascalABC.Net - вполне себе самостоятельный язык программирования с паскалеобразным синтаксисом. Да что уж там, в свежем С++ (который позиционировался всегда как “обратно совместимый с C”) ключевое слово auto значит совсем не то, что в C. Чем Вы хуже? Сделайте typecast’ы в каком-нибудь C-style или C+±style (со всякими cast<Destination-Type>(SourceType) - выглядеть будет ужасно, но и при каждом typecast’е будет заставлять задуматься). Если надо во что бы то ни стало сохранять совместимость со старым кодом - добавьте флаг компиляции (типа --std=legacy).

Да это покруче #define TRUE FALSE будет! Хотя, конечно, валидная конструкция, в python’е ровно так же можно в ногу выстрелить каким-нибудь list = [].

Прошу прощения за резкость и грубость, просто, во-первых, подробность сообщений компилятора - больная тема, во-вторых - сталкиваться с какими-то неочевидными моментами в языке - то ещё удовольствие. И огромное спасибо за разъяснения - надо признать, Паскаль сильно глубже и шире, чем я себе представлял.

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

Да, я понимаю Вас. Если бы Intellisense показал, что Random - это тип, то Вы бы по-другому относились к этим сообщениям об ошибках. Итак, Random - это всё-таки тип. Надо похоронить даже мысль о том, что Random - имя функции, возвращающей целое. Если Вы её не похоронили, то всё равно по поводу любого сообщения об ошибке у Вас будет двойственное чувство. Ни одно из сообщений об ошибках, как бы оно хорошо ни было, ничего не может сказать Вам о типе integer. Потому что там его уже нет. То есть, вообще. И - все сообщения об ошибках - правильные. Все.

Первое сообщение не очень информативное: “Ожидалась переменная” - надо бы заменить на что-то типа “Ожидалась переменная, а встречено имя типа”. Но в действительности, и это не совсем точно. Вот я смотрю в дебаггере компилятора - на месте Random может быть не только переменная, но и константа, свойство - 5 штук разных видов объектов. А не может быть 12 - среди них - типы, метки, параметры обобщений. Наверное, надо поработать над точностью сообщения - это морока, но надо поработать. А вот два следующих сообщения - предельно точные: " У операции преобразования типов допустим только один параметер". Компилятор прав, что если Вы написали имя типа и открыли скобку, то НЕ СУЩЕСТВУЕТ НИКАКОЙ ДРУГОЙ ТРАКТОВКИ кроме как приведение типа. А оно может быть только с одним параметром.

Кстати, параметЕр уже исправили - спасибо.

С OpenGL не сравнивайте всё-таки - там я вижу лишь классы ошибок - здесь полноценные ошибки. Здесь - прошу заметить - показывается не только строка ошибки, но и позиция - она будет В ТОЧНОСТИ перед Random.

Я не шутил.

По поводу того чтобы навернуть что-нибудь такое-эдакое - дурное дело нехитрое, но язык можно легко испортить. Практически полная совместимость с языком Паскаль позволила нам добиться известности - теперь можно потихонечку отходить от безобразных конструкций старого Паскаля. for i с описанием переменной на 50 строк выше - как раз - из их числа. Но без причины наворачивать нельзя. Язык быстро портится, захламляется, теряет индивидуальность. Здесь я прошу просто поверить моему опыту и глубокому знанию языка - здесь объяснения не помогут. Можно скатиться в религиозные войны, но не хочется. У PascalABC.NET есть некая своя самобытность - стараемся её сохранить.

Насчет преобразований типов - надеюсь, Вы шутили. В C++ это - монстрообразные конструкции, которые не любит никто. И - в C++ сделаны 4 вида потому что они дифференцируют грубость ошибок. Здесь (в .NET) преобразований типа всего одно (нет reinterpret_cast - это невозможно написать в .NET, нет const_cast, а static_cast совпадает с dynamic_cast). Поэтому все безумные идеи мы обычно просеиваем через сито мнений и времени.

Несомненно, последними “безумными” идеями, которые всё-таки возникли в языке после многочисленных обсуждений, стали кортежные присваивания (a,b) := (b,a) и срезы массивов и строк a[2:8:2]. Первый реализуется очень просто и потрясающе эффективен, а второй - эффективен в своём классе задач. Оба абсолютно не портят старые конструкции - если программируешь в старом стиле, то и никогда этого не узнаешь. К сожалению, обе конструкции немного портят качество сообщений об ошибках - раньше слева от присваивания нельзя было вообще записать (a,b), а сейчас можно - и что-то вроде (Random,1) := (2,3) породит такую груду предположений компилятора о правильном коде, что мало не покажется :slight_smile:

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

1 лайк

Добрый день. Пишу по вот какой проблеме. Не работает функция random (ну или я её не правильно применяю). Подскажите пожалуйста, что я не так делаю. Создаю очень простое приложение: на форме размещаю кнопку button1 и на событие MouseMove пишу код: button1.Top:=random(500); button1.Left:=random(500); Программа, что б кнопка по наведению мыши перемещалась в другое место. В итоге вижу ошибку: “Невозможно явно преобразовать тип integer к типу System.Random”. Что я не так сделал. Спрасибо заранее.

Напишите PABCSystem.Random(500)

Спасибо, еще библиотеку нужно было подключить PABCSystem.

Не нужно - она автоматически подключается - и это модуль.