PascalABC.NET для школьных учителей

Кому адресована данная статья

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

  1. Что такое PascalABC.NET
  2. Почему “стандартный” Паскаль устарел
  3. Почему PascalABC.NET является современной и долговременной разновидностью Паскаля
  4. Какие новые возможности PascalABC.NET рекомендуется использовать в обучении школьников
  5. Как меняется обучение школьников при использовании этих возможностей. Какие преимущества мы получаем и какие возникают риски.

PascalABC.NET

Почему написан PascalABC.NET

Язык Паскаль традиционно использовался и используется в российском образовании для обучения начинающих (преимущественно школьников). Наиболее известная реализация – Borland Pascal и впоследствии Delphi – стали стандартом де-факто этого языка. Но время шло, мир менялся, появлялись и развивались новые языки, и к настоящему моменту язык Паскаль в его исходном воплощении потерял былую привлекательность. Свободная реализация – Free Pascal – уже длительное время практически не развивает язык и содержит устаревшую консольную интегрированную среду разработки, спроектированную в стиле MS DOS.

Проект PascalABC.NET, начатый 10 лет назад в 2005 году, как раз и был ориентирован на то, чтобы внедрить в Паскаль современные возможности, появившиеся на тот момент в платформе .NET, и предоставить простую удобную современную интегрированную среду разработки.

Недостатки “старого” Паскаля

Все современные языки программирования так или иначе развиваются, обмениваются идеями. Например, лямбда-выражения перекочевали из функциональных языков программирования вначале в C#, а затем в Java и C++.

Примем за аксиому тот факт, что если некоторый язык прекращает развитие, то он постепенно умирает. Примерно это и происходит со “старым” Паскалем. Язык программированя Delphi – основной источник новшеств в Паскале - постепенно сдал свои позиции, да и среда стала исключительно платной. По этой причине Delphi перестала использоваться для обучения вообще. Ее нишу заняли Free Pascal и Lazarus, которые к сожалению пошли по пути копирования языка и оболочки Delphi. Именно поэтому за последнее десятилетие язык Free Pascal не претерпел никаких существенных изменений. Именно поэтому возникло стойкое ощущение, что Паскаль умирает.

Это ощущение подкрепляется еще и тем, что стали появляться и активно использоваться в обучении легковесные языки с очевидными конструкциями и хорошими библиотеками. К числу таких языков несомненно относится язык Python.

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

Почему PascalABC.NET эффективен и современен

PascalABC.NET занимает образовавшуюся нишу. С одной стороны, он поддерживает все средства “стандартного” языка Паскаль, обеспечивая мягкий переход к современному преподаванию. С другой стороны, он вводит ряд новых возможностей на разных уровнях, позволяя использовать разные модели обучения программированию: от модели с упором на современные объектно-ориентированные библиотеки до программирования в функциональном стиле.

Одно из несомненных преимуществ PascalABC.NET состоит в том, что это – полноценный .NET-язык. Это значит, что изучение PascalABC.NET неотвратимо приводит к изучению современных конструкций программирования. Поскольку библиотека для PascalABC.NET и языка C# – общая, то изучение PascalABC.NET автоматически означает изучение средств одного из самых современных языков программирования C#. Поскольку порог вхождения для языка Паскаль ниже, чем для C#, можно рекомендовать PascalABC.NET как первый полноценный язык для обучения программированию с прицелом на изучение таких промышленных языков как C# и Java.

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

Новые возможности PascalABC.NET

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

Базовые возможности: внутриблочные описания и автовывод типов

Рассмотрим одну из первых программ на PascalABC.NET

begin
  var a := 2;
  var b := 3;
  Print(a,b,a+b);
end.

Здесь мы видим несколько простейших особенностей PascalABC.NET:

  • переменные можно описывать внутри блока begin-end
  • при описании переменные можно инициализировать
  • тип переменной при описании с инициализацией можно не указывать - он выводится по типу значения
  • для вывода можно использовать стандартную процедуру Print

Что дают эти возможности?

Внутриблочные описания переменных позволяют не захламлять область описания до begin многочисленными описаниями переменных. Здесь используется принцип “понадобилась переменная - описал”, что проще чем возвращаться каждый раз в раздел описаний и описывать новую переменную. Все временные переменные описываются обязательно внутри блока: они не должны быть глобальными поскольку отвлекают внимание.

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

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

Наконец, автоматический вывод типов при инициализации позволяет предельно упростить запись:

  var a := 2;

при сохранении ее очевидности.

Стандартная процедура Print работает так же как и write, но разделяет данные при выводе пробелом пробелом, что удобнее для начинающих и выглядит гораздо чище чем запись

  write(a,' ',b,' ',a+b);

Ввод, совмещенный с описанием и приглашением к вводу

Соблюдая принцип легковесности и очевидности восприятия команд, PascalABC.NET реализует ввод в переменную, совмещенный с описанием этой переменной и приглашением к вводу:

begin
  var a := ReadReal('Введите a: ');
  var b := ReadReal('Введите b: ');
  
  var t := a;
  a := b;
  b := t;
  
  Print('После перемены местами:',a,b);
end.

Нетрудно видеть, что запись

  var a := ReadReal('Введите a: ');

в старом Паскале занимала по крайней мере 3 строки:

var a: real;
begin
  write('Введите a: ');
  read(a);
  ...

Операции += и *=

Операции += и *= пришли в PascalABC.NET из языка C и ранее были введены во Free Pascal. Они легче запоминаются школьниками чем полные аналоги и читаются соответственно как “увеличить на” и “увеличить в”. Сравниваем:

begin
  var a := 3;
  var b := 5;
  
  a += 1;
  a := a + 1;
  
  b *= 2;
  b := b * 2;
end.

Точечная нотация и ее эффективность

Рассмотрим следующий код:

var i: integer := 345;
begin
  i.
end.

Если мы в среде разработки нажмем точку после имени переменной, то система интеллектуальных подсказок Intellisense любезно представит в выпадающем списке все действия, которые можно выполнять с данной переменной. Такие действия внутри переменной называются методами и являются частью описания типа этой переменной. Наиболее часто используемый для переменной типа integer метод называется ToString - он преобразует целое в строку:

begin
  var i := 345;
  var s := i.ToString;
end.

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

Таким образом, не нужно помнить множество функций преобразования из данного типа в строковый - достаточно нажать точку и вызвать метод ToString.

Аналогично проделаем другой эксперимент: нажмем точку после имени типа:

begin
  integer.
end.

Система интеллектуальных подсказок любезно предложит нам в выпадающем списке всё, что связано с типом integer. В частности, здесь находится константа integer.MaxValue, представляющая самое большое значение типа integer. Нетрудно догадаться, что каждый числовой тип “знает” максимальное значение этого типа.

Использование динамических массивов вместо статических

В PascalABC.NET помимо статических массивов вида

var stat: array [1..100] of integer;

имеются динамические массивы, появившиеся в Delphi:

var a: array of integer;

Основное преимущество динамических массивов состоит в том, что память под них можно выделить при выполнении программы:

var a: array of integer;
var n := ReadInteger('Введите количество элементов в массиве: ');
a := new integer[n];

При этом можно совместить описание динамического массива и выделение для него памяти:

var n := ReadInteger('Введите количество элементов в массиве: ');
var a := new integer[n];

Отметим, что динамические массивы всегда индексируются с нуля. Кроме того, динамический массив знает свой размер: для этого необходимо обратиться к свойству a.Length.

Поэтому цикл по динамическому массиву обычно выглядит следующим образом:

  for var i:=0 to a.Length-1 do
    a[i] *= 2;

Если элементы динамического массива не надо менять и в задаче не требуется информация об индексе элемента, то можно использовать цикл foreach:

  foreach var x in a do
    Print(x);

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

При традиционном изучении базовых алгоритмов работы с массивами разница между статическими и динамическими массивами незначительна.

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

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

Пример 1.

Рассмотрим классическую задачу инвертирования массива. Вот ее решение с использованием традиционного подхода и статических массивов:

var 
  a: array [1..100] of real;
  n: integer;
  t: real;
begin
  read(n);
  for var i:=1 to n do
    read(a[i]);

  for var i:=1 to n div 2 do
  begin
    t := a[i];
    a[i] := a[n-i+1];
    a[n-i+1] := t;
  end;

  for var i:=1 to n do
    write(a[i]);
end.

В данном коде мы использовали стиль “старого” Паскаля за одним исключением - переменную цикла for мы всегда описываем в заголовке цикла. Мы считаем эту особенность предельно важнойи не рекомендуем от нее отказываться даже ради “чистоты” стиля “старого” Паскаля.

Здесь основным раздражающим моментом является то, что под статический массив выделяется память в 100 элементов - это заведомо больше реальной заполненности массива n и крайне расточительно. У школьника это сразу формирует плохой стиль: “выделим много памяти - вдруг потребуется!”.

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

begin
  var n := ReadInteger('Введите размер массива: ');
  var a := ReadArrReal(n);

  for var i:=0 to n div 2 - 1 do
    Swap(a[i],a[n-i]);

  a.Println;
end.

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

Кроме этого, необходимо обратить внимание, что функция ReadArrReal возвращает array of real, поэтому переменная a получает тип array of real. Мы получим эту информацию если в среде разработки наведем курсор на имя массива a.

Наконец, следует отметить, что ReadArrReal(n) сама выделяет память для n вещественных элементов массива a.

Пример 2. Сортировка массива.

Решим данную классическую задачу с использованием динамических массивов и новых средств ввода-заполнения-вывода:

begin
  var n := ReadInteger('Введите размер массива: ');
  var a := ArrRandom(n,1,100); // заполнение n случайными целыми в диапазоне 1,100

  // Используем пузырьковую сортировку
  for var i := 0 to n - 2 do
  for var j := n-1 downto i+1 do
    if a[j] < a[j-1] then
      Swap(a[j], a[j-1]);

  a.Println
end.

Данная задача может быть примерно столь же просто решена с помощью статических массивов. В чем же здесь преимущество?

Значительное преимущество динамических массовов состоит в том, что в стандартной библиотеке имеется множество стандартных подпрограмм и для работы именно с динамическими массивами. Например, для сортировки динамического массова можно использовать стандартную подпрограмму Sort(a).

Кроме того, сами динамические массивы внутри себя содержат множество методов. Например, каждый динамический массив умеет находить свой минимальный элемент, используя вызов a.Min, и осуществлять поиск индекса некоторого элемента x, используя вызов метода a.IndexOf(x). Приведем рещение нашей задачи с использованием этих средств.

Можно совместить код, написанный учеником, с вызовом стандартных подпрограмм - например, чтобы убедиться в том, что код ученика не содержит ошибок:

begin
  var n := ReadInteger('Введите размер массива: ');
  var a := ArrRandom(n,1,100); // заполнение n случайными целыми в диапазоне 1,100
  a.Println;

  var b := Copy(a);

  // Используем пузырьковую сортировку
  for var i := 0 to n - 2 do
  for var j := n - 1 downto i + 1 do
    if a[j] < a[j - 1] then
      Swap(a[j], a[j-1]);

  writeln('Отсортированный массив:');
  a.Println;
  
  Sort(b);
  
  writeln('Проверка:');
  b.Println;
end.

Здесь в массив b вызовом var b := Copy(a); сохраняется несортированная копия массива a.

Стандартные подпрограммы против ручной реализации алгоритмов

Школьный учитель неизбежно задает вопрос: оправдано ли использование стандартных подпрограмм и методов если ученик должен освоить основные алгоритмы? Не приведет ли разрешение использования стандартных алгоритмов к отказу от понимания того, как они устроены внутри? Не лишит ли такой подход школьника фундаментальности получаемых знаний, не станут ли они от этого поверхностными? Ведь если ученик будет знать, что есть подпрограмма Sort сортировки массива, то он не будет писать соответствующие алгоритмы?

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

Прежде всего, необходимо понимать, что “старый” Паскаль был разработан в 1970 году, а на момент написания данной статьи мы живем в 2015 году. За 45 лет произошли большие изменения в языках программирования и многие из них стали использоваться в обучении. Необходимо понимать, что держась только за ценности 45-летней давности, мы не можем сегодня в школьнике воспитать будущего грамотного специалиста. Поэтому сознательное ограничение того, что окружает нас повсюду, вплоть до полного запрещения - неприемлемо, свидетельствует о непрофессионализме учителя.

Но нужно ли учить стандартные алгоритмы? Разумеется. Куда же без них? Они - основа.

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

В качестве примера рассмотрим программу, предваряющую пузырьковую сортировку.

Пример 3.

В массиве из 4 элементов поменяем местами третий с четвертым, второй с третим, первый со вторым. В результате этих действий первый элемент окажется на последнем месте, остальные сдвинутся влево. Для иллюстрации будем выводить содержимое массива после каждой перемены местми.

begin
  var a := Arr(8,3,5,1);
  a.Println;

  Swap(a[2],a[3]);
  a.Println;
  Swap(a[1],a[2]);
  a.Println;
  Swap(a[0],a[1]);
  a.Println;
end.

Подобный код может быть предварительным для объяснения алгоритма пузырьковой сортировки. Он показывает, как самый маленький элемент “всплывает”, сохраняя при этом относительное положение остальных элементов.

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

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

Итак, основная методическая задача - перейти к внедрению использования стандартных подпрограмм и методов в различные темы курса алгоритмизации. Это внедрение может быть незначительным (например, использование только вывода ммассива a.Print) или более глубоким.

Универсальный вывод в Writeln, Println

Множества произвольного типа

Новое в работе с символами и строками

Множества произвольных типов

Новое в работе с файлами

Списки List как расширяемые динамические массивы

Словари Dictionary как наборы пар (ключ,значение)

Графический модуль - простота и эффективность

Управление событиями - создание интерактивных и игровых приложений

Как меняется парадигма обучения программированию при использовании PascalABC.NET

Будущее обучения школьному программированию