Учебник языка Перфо для школьников — различия между версиями

Материал из ТХАБ.РФ
Перейти к: навигация, поиск
(Новая страница: «* Часть викиучебника «Лисп» * Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Sche…»)
 
м (Упражнение 6)
 
(не показано 126 промежуточных версий 4 участников)
Строка 1: Строка 1:
* Часть викиучебника «[[Лисп]]»
+
Это заготовка для переделки под язык [[Перфо]] - учебника "Введение в Scheme для школьников".
 +
 
 +
* https://ru.wikibooks.org/wiki/Введение_в_язык_Scheme_для_школьников - Оригинал "Введение в Scheme для школьников"
 +
* https://habr.com/ru/post/505928/ Почему функциональное программирование такое сложное - Хабр
 +
 
 
* Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в [[Журнал «Потенциал»|журнале «Потенциал»]].
 
* Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в [[Журнал «Потенциал»|журнале «Потенциал»]].
  
Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — [[w:Лисп|Лисп]]. Точнее, один из его самых интересных диалектов — [[w:Scheme|Scheme]] (''Ским'').
+
Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — [[w:Лисп|Лисп]]. Точнее, один из его самых интересных диалектов — [[Перфо]], который является русским вариантом [[w:Scheme|Scheme]] (''Ским''). [[Перфо]] не является копией Ским. Это адаптированный для практического программирования вариант интерпретатора функционального языка программирования.
 +
 
 +
== Примерное соответствие ключевых слов Ским (Scheme) и [[Перфо]] ==
 +
* ; - // - комментарий, многострочный комментарий /*  */ - не поддерживается
 +
* define - перем, функция - (определить)
 +
** перем - создаёт и изменяет переменную
 +
** функция - создаёт функцию
 +
** lambda - функ - создаёт анонимную (лямбда)-функцию
 +
* read - ввод - ввод строки пользователя
 +
* display - вывод
 +
* write - вывод
 +
* newline - ПС - перевод строки
 +
* if - если - оператор ветвления ЕСЛИ
 +
* positive? - (> переменная 0)
 +
* string-append -  - склеивание строк
 +
* set! -
 +
* list - список
 +
* null? - Пустой? - для использования необходимо подключить [[Стандартную Библиотеку Перфо]]
 +
* map - [[ПоВсем]]
 +
* apply - [[ПоКаждому]]
 +
* begin -  не к спискам относится, а к последовательности действий... в Перфо можно просто писать последовательность без ключевого слова '''begin'''. Например, на Scheme надо было бы написать:
 +
 
 +
(+ 3 (begin (set! y 4) y)  5)
 +
 
 +
а на Перфо будет так:
 +
 
 +
(+ 3 ((перем ф 4) ф) 5)
 +
 
 +
Перем и Уст с переменными работают похоже, пока дело не касается установки значений полей или свойств объектов, тут уже только Уст подойдет...
 +
 
 +
=== [[Перевод терминов функционального программирования]] ===
  
 
== Введение в синтаксис==
 
== Введение в синтаксис==
Строка 12: Строка 46:
 
* Купи в булочной батон.
 
* Купи в булочной батон.
  
Каждая законченная фраза на этом языке должна быть окружена парой круглых скобок. Запишем сказанное выше на Scheme:
+
Каждая законченная фраза на этом языке должна быть окружена парой круглых скобок. Запишем сказанное выше на Перфо:
  
 
  (+ 3 5)
 
  (+ 3 5)
Строка 21: Строка 55:
  
 
  (купить булочная батон (+ 2 1))
 
  (купить булочная батон (+ 2 1))
 +
 +
«Купи в булочной батоны: два плюс ещё один». Просто, не правда ли? Давайте двигаться дальше.
 +
 +
Фраза <code>(* 3 5)</code> хороша, а <code>(* ширина высота)</code> — лучше.
 +
 +
Выражение <code>(* 2 3.1415926 5)</code> — интригующе, а <code>(* 2 ЧислоПи Радиус)</code> гораздо более осмысленно.
 +
 +
Здесь <code>ширина</code>, <code>высота</code> — переменные, а <code>3</code> и <code>5</code> — их текущие значения.
 +
 +
Переменная задаётся следующей конструкцией языка:
 +
 +
(Перем имя <первоначальное значение>)
 +
 +
Пример:
 +
 +
(перем ширина 3)
 +
(перем высота 7)
 +
(* 2 (+ ширина высота))
 +
 +
Прочитаем записанное по-человечески: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20.
 +
 +
Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так:
 +
 +
(перем А 3)
 +
(перем Б 4)
 +
(+ (* А А) (* Б Б))
 +
 +
Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Перфо — <code>квадрат</code>:
 +
 +
(+ (квадрат А) (квадрат Б))
 +
 +
=== Добавим новые слова в программу ===
 +
 +
«Сумма квадрата <code>А</code> и квадрата <code>Б</code>». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её <code>квадрат</code>. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом:
 +
 +
(функция (квадрат x) (* x x))
 +
 +
Общий формат:
 +
 +
(функция (название параметр параметр …) (тело_функции))
 +
 +
Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>квадрат2</code>:
 +
 +
(функция (квадрат2 x) (* 2 2) (* x x))
 +
 +
вернёт тот же результат, что и <code>квадрат</code>, перед этим умножив 2 на 2 безо всякого эффекта. Перепишем пример с суммой квадратов чисел заново:
 +
 +
(перем А 3)
 +
(перем Б 4)
 +
(функция (квадрат x) (* x x))
 +
(+ (квадрат А) (квадрат Б))
 +
 +
Нам не хватало слов в языке — мы их добавили. Вообще, когда пишете программу на Перфо, вы описываете не алгоритм, а сначала создаёте язык, а потом на нём формулируете исходную задачу. Несколько точнее — вы «подгоняете» данный вам язык Перфо до тех пор, пока он не станет совпадать с языком, на котором задача формулируется легко.
 +
 +
Сразу пример. Пусть перед нами стоит задача сделать программу, которая спрашивает имя пользователя, а потом выводит ему приветствие.
 +
 +
Перфо предоставляет нам несколько готовых «глаголов»:
 +
 +
* <code>ввод</code>: для чтения имени,
 +
* <code>вывод</code>: вывод чего-то на дисплее,
 +
* <code>ПС</code>: вывод перевода строки.
 +
 +
Мы бы хотели иметь такие «глаголы»:
 +
 +
* <code>привет</code>: для приветствия с одним параметром — именем пользователя;
 +
* <code>пользователь</code>: для получения имени пользователя, без параметров.
 +
 +
Наша задача выглядела бы так:
 +
 +
(привет (пользователь))
 +
 +
Дело за малым — определить <code>привет</code> и <code>пользователь</code>. Нет проблем. Вот полный текст программы.
 +
 +
(функция (привет имя) // объявление функции Привет с параметром Имя
 +
  (вывод "Привет, ")
 +
  (вывод имя)
 +
  (вывод "!")
 +
  (ПС))
 +
(функция (пользователь) // объявление функции Пользователь без параметров
 +
  (ввод "Представьтесь: "))
 +
(привет (пользователь))
 +
 +
[[Перфо]] — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется.
 +
 +
=== Ввод данных в программу ===
 +
 +
Ввод данных пользователя прост и логичен. Как мы объявляем переменную ?
 +
 +
(Перем ИмяПользователя "Володя")
 +
(Вывод "Вы ввели имя " ИмяПользователя)
 +
 +
Как присвоить новое значение переменной '''ИмяПользователя''' ? В Функцию присваивания '''Перем''' вместо "Володя" необходимо вставить результат работы другой функции.
 +
 +
(Перем ИмяПользователя (Ввод "Введи своё имя: "))
 +
(Вывод "Вас зовут " ИмяПользователя ПС) // проверим какое  значение присвоилось, напоминаю ПС - перевод строки
 +
 +
Формат функции '''Ввод'''
 +
 +
(ввод "Сообщение-подсказка пользователю, что ввести ")
 +
 +
=== Операция унарный минус ===
 +
(-) "минус" - Перфо тоже функция/глагол. Если её применить к значению (- 3) или (- ЛюбоеЧисло) то знак поменяется на противоположный. Математическим аналогом является умножение на -1. Операция унарный плюс возможна но не имеет смысла как умножение на +1. 
 +
 +
(Вывод "Отрицательное число -3 = " -3 ПС)
 +
(Вывод "Операция унарный минус (- 3) = " (- 3) ПС)
 +
(Вывод "Операция унарный минус (- -3) = " (- -3) ПС)
 +
 +
=== Ветвление программы - оператор '''если''' ===
 +
Например, функцию «модуль числа» можно определить так:
 +
 +
(функция (модуль ф) // описание функции Модуль с параметром Ф
 +
          (если (> ф 0) // Если ф>0 то выполняется Часть 1 иначе Часть 2
 +
                  ф // Часть 1
 +
                (- ф) // Часть 2
 +
          ) // Конец алгоритма функции Модуль
 +
  ) // Конец описания функции Модуль
 +
 +
«Определим, что функция <code>модуль</code> возвращает свой аргумент, если он положителен, иначе — <code>-ф</code>». А можно и так:
 +
 +
(функция (модуль ф) // описание функции Модуль с параметром Ф
 +
    // результат выполнения функции - знак (+) или (-) который применяется к переменной Ф
 +
          ((если (> ф 0) + -) ф) // Если ф>0 то возвращает знак (+) иначе возвращает знак (-)
 +
          // знаки + и - на самом деле функции которые применяются к переменным или числам
 +
          // поэтому результат выполнения функции Модуль - одна из 2-х функций + или -
 +
          // в  зависимости от значения Ф
 +
 +
 +
«…если аргумент положителен, то плюс, иначе минус <code>ф</code>». Здесь в результате исполнения выражения <code>если</code> возвращается функция <code>+</code> или <code>-</code>, которая затем применяется к аргументу <code>ф</code>. Полагаю, что смысл конструкции <code>если</code> вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков:
 +
 +
(если условие <действие, если условие выполняется> <действие в противном случае>)
 +
 +
== Где посмотреть и попробовать ==
 +
 +
В теории всё хорошо, а где немного попрактиковаться? Необходимо скачать среду языка [[Перфолента.NET]] 0.46+, в ней выбрать Файл - "Создать программа на языке Перфо..."
 +
 +
== Рекурсия и итерация ==
 +
 +
Простота Перфо ([[Лисп]], [[Scheme]] ) обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии.
 +
 +
=== Упражнение ===
 +
 +
Посмотрите следующие две реализации функции вычисления [[w:Факториал|факториала]] (т.е. умножаются все числа от 1 до N)
 +
 +
f(N) = 1 * 2 * N
 +
 +
Одна из них основана на [[w:Рекурсия|рекурсии]] - это когда функция (или кусок кода) вызывает сама себя, а другая – на [[w:Итерация|итерациях]] - это когда кусок кода повторяется несколько раз в цикле.
 +
 +
Напишите на Перфо рекурсивную (вызывающую саму себя) и основанную на итерациях (повторении) реализации функции возведения в степень N числа А
 +
 +
f(А, N) = A^N.
 +
 +
==== Вариант 1. Рекурсия - функция вызывает сама себя ====
 +
 +
(функция (факториал ф) // функция Факториал с параметром Ф
 +
          (если (= ф 0) // если a = 0
 +
                  1 // то функция возвращает в то место в коде откуда её вызвали 1
 +
                (* ф (факториал (- ф 1) ) ) // умножается значение Ф на значение возвращаемое
 +
                  // (факториал (- ф 1) ) - с параметром Ф-1 (на единицу меньше)
 +
                  // т.е. внутри функции '''факториал''' вызывается функция '''факториал'''
 +
                  // с другим (на единицу меньше) параметром!!
 +
    )
 +
)
 +
 +
==== Вариант 2. Повторение (Итерация) ====
 +
Вычисление факториала методом повторения (итерации):
 +
 +
(функция (факториал-повторением результат счётчик) // определение функции Факториал-повторением
 +
        (если (= счётчик 0)
 +
              результат
 +
              (факториал-повторением (* счётчик результат)
 +
                                  (- счётчик 1))))
 +
(функция (факториал Число) (факториал-повторением 1 Число)) // описание функции Факториал которая использует функцию факториал-повторением
 +
// использование функции Факториал
 +
(Вывод "Факториал 6: " (факториал 6)) // вывод " Факториал 6:" +  результат работы функции "Факториал" с параметром 6
 +
                                      //(которая сама вызывает другую функцию факториал-повторением )
 +
 +
== Повторение – мать учения ==
 +
 +
Приведём небольшую шпаргалку по базовым конструкциям [[Перфо]].
 +
 +
Базовый синтаксис:
 +
 +
(<функция> <аргумент> <аргумент> …) // комментарий
 +
 +
Например:
 +
 +
(+ 1 2 3 4)                    // сумма первых 4-х натуральных чисел
 +
(+ (+ 1 2) 5 6)                // возможно вкладывать одни вызовы функций в другие
 +
(+ "Привет" "Мир")              // результат — склеенная строка "ПриветМир"
 +
 +
Присвоить значение переменной:
 +
 +
(перем <имя> <значение>)
 +
 +
Объявление Функции:
 +
 +
(функция (<имя> <параметр1> … <параметрН>) <тело функции>)
 +
 +
Функция возвращает значение последнего вычисленного выражения в теле функции
 +
 +
Например:
 +
 +
(перем А 3) // создаётся переменная А = 3
 +
(перем Б 4) // создаётся переменная Б = 4
 +
(функция (квадрат П) (* П П)) // описывается функция которая вычисляет квадрат числа переданного в параметр П
 +
// результат = 16
 +
(функция (+А Б) (+ Б А)) // да-да, можно давать и такие имена функциям (любое символьное выражение)
 +
// результат = 7
 +
 +
Конструкция Если То Иначе:
 +
 +
(если <условие> <действие при успехе условия> <действие при неудаче>)
 +
 +
Выполнение действий последовательно, как  в обычном языке программирования:
 +
 +
(<первое действие> <второе действие> …)
 +
 +
Пример:
 +
(Перем Б -10) // Переменной Б присваивается 10
 +
(если (> Б 0) // Если Б>0
 +
    (вывод "Б > 0" ПС)) // Б > 0 - ПРАВДА выводится "Б > 0" и переводится строка, 
 +
    // действие Б > 0 - ЛОЖЬ -  можно не указывать
 +
((вывод 1)
 +
  (вывод 2 ПС)
 +
  (вывод 3)) // последовательно выполнятся действия, и на экран будет выведено: 12 и 3
 +
 +
Если вы ранее использовали Scheme то в нём последовательность действий (<первое действие> <второе действие> …) записывается со словом begin ('''begin''' <первое действие> <второе действие> …)
 +
 +
== И опять про повторение: функция <code>повторить</code> ==
 +
 +
Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие 1 раз, то вы просто его делаете 1 раз:
 +
 +
(Вывод "Привет")
 +
 +
Если вам надо выполнить какое-то действие 2 раза, то вы просто повторяете его:
 +
 +
(Вывод "Привет") (Вывод "Привет")
 +
 +
А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет <code>повторить</code>, и у неё будет один параметр — количество повторов. Алгоритм следующий:
 +
 +
Если количество повторов больше нуля, то:
 +
 +
1. выполняем необходимое действие
 +
 +
2. из функции Повторить рекурсивно вызываем функцию <code>повторить</code>с количеством повторов меньшим на 1
 +
 +
(функция (повторить ЧислоПовторов)
 +
      (если (> ЧислоПовторов 0) // если количество повторов не нулевое
 +
          ((вывод "Привет") // выполняем действие
 +
                  (повторить(- ЧислоПовторов 1))))) // повторим действие на единицу меньшее количество раз.
 +
// проверим функцию Повторить
 +
(повторить 5)
 +
 +
=== Упражнение 1 ===
 +
 +
Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз.
 +
 +
// функция повторить
 +
(функция (повторить ЧислоПовторов ЧтоПовторять)
 +
      (если (> ЧислоПовторов 0) // если количество повторов не нулевое
 +
          ((вывод ЧтоПовторять) // выполняем действие
 +
                  (повторить (- ЧислоПовторов 1) ЧтоПовторять ) )) // функция вызывает сама себя с числом повторов на 1 меньше
 +
      ) // повторим действие на единицу меньшее количество раз.
 +
// начало выполнения кода, вызываем функцию Повторить с параметром 5
 +
(повторить 5 1)
 +
 +
=== Упражнение 2 ===
 +
 +
Попробуйте написать функцию, которая будет выводить на экран натуральные числа от 1 до заданного числа. Порядок вывода чисел не важен.
 +
 +
Самое время вспомнить, что в ''Перфо'' можно передавать функцию в качестве параметра. Усовершенствуем функцию <code>повторить</code> так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает 2 параметра:
 +
* первый параметр — количество повторов,
 +
* второй параметр — функция, которую надо запустить.
 +
 +
(функция(повторить ЧислоПовторов ВызываемаяФункция) // описание функции Повторить
 +
(если (> ЧислоПовторов 0)
 +
    ( (ВызываемаяФункция)
 +
          (повторить (- ЧислоПовторов 1) ВызываемаяФункция))))
 +
 +
(функция (Вывести-Один) (вывод "1")) // описание функции Вывести-Один
 +
(функция (Вывести-Привет) (вывод "Привет")) // описание функции Вывести-Привет
 +
// Начало исполнения
 +
(повторить 5 Вывести-Один) // 3 раза выведет на экран "1", используемая функция Вывести-Один
 +
(вывод ПС) // Перевод строки
 +
(повторить 5 Вывести-Привет) // 5 раз выведет на экран "Привет", используемая функция Вывести-Привет
 +
 +
== Принцип наименьших усилий. Анонимные (лямбда)-функции ==
 +
 +
Не надо делать лишних действий, если их можно избежать. Последний вариант функции '''повторить''' хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию».
 +
 +
(функ (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)
 +
 +
Пример:
 +
 +
(функ (ф) (* ф ф))    // создать функцию, которая вычисляет квадрат числа
 +
(функ (ф ч) (+ ф ч)) // создать функцию, которая вычисляет сумму двух чисел
 +
(перем квадрат (функ (ф) (* ф ф)))  // создать лямбда-функцию с переменой Ф.
 +
// ссылка на функцию присваивается ПЕРЕМЕННОЙ "квадрат".
 +
// При использовании переменной "квадрат" числа 100, это число обрабатывается
 +
// функцией которая была присвоена переменной "квадрат"
 +
// этой переменной можно присвоить другую функцию, и следующее число будет обработано другой функцией
 +
(Вывод "Квадрат  = " (квадрат 100) ПС)
 +
 +
Оказывается, последняя конструкция то же самое, что и:
 +
 +
(функция (квадрат ф) (* ф ф))
 +
 +
Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям:
 +
 +
(повторить 3 (функ() (вывод "Привет")) ) // три раза выведем на экран "Привет"
 +
(повторить 5 (функ() (вывод "1"))) // пять раз выведем на экран "1"
 +
 +
Для <code>повторить</code> используются функции без параметров, поэтому в конструкции <code>функ</code> пустая пара скобок.
 +
 +
Анонимные (Лямбда)-функции позволяют передавать и принимать в качестве параметров функции не только числа, переменные строки, но и ссылки на другие функции (а фактически куски кода) которые будут обрабатывать данные внутри вашей функции. При этом при каждом вызове функции в качестве параметра ей можно передавать другую функцию. т.е. менять не только данные которые обрабатывает функция но и часть алгоритма по которому эти данные обрабатываются.
 +
 +
Представьте, что у вас есть функция, которая может строить графики и принимает в качестве параметров начало и конец интервала, а так же функцию график которой необходимо построить. Если вы передадите туда функцию Квадрат, то получите график параболы, а если передадите функцию Куб, то получите график гиперболы.
 +
 +
Пример:
 +
//создадим две анонимных функции
 +
//и сохраним их в переменные квадрат и куб
 +
(перем квадрат (функ (ф) (* ф ф))) 
 +
(перем куб (функ (ф) (* ф ф ф)))
 +
//создадим функцию для построения графика
 +
(функция (график НачИнтервала КонИнтервала ФункцияГрафика)
 +
    (Для (Инд НачИнтервала КонИнтервала)
 +
      (Вывод "х=" Инд " у=" (ФункцияГрафика Инд) "; ") 
 +
    )
 +
    (Вывод пс)
 +
)
 +
//строим график функции квадрат
 +
(график 1 4 квадрат)
 +
//строим график функции куб
 +
(график -3 3 куб)
 +
 +
== Работа с файлами ==
 +
Чтение текста из файла Пример2.perfo  - файл должен существовать в файле с запускаемой программой на Перфо
 +
 +
//прочитаем текст из файла
 +
(Перем Ф (+ (ФС.ТекущийКаталог) "Пример2.perfo"))
 +
(Вывод "Имя файла: " Ф пс)
 +
(Перем Чт (Новый ЧтениеТекста Ф))
 +
(Перем Т (Чт.ПрочитатьДоКонца))
 +
(Вывод "Содержимое файла: " (СтрЗаменить (СтрЗаменить (Сред Т 100 100) " " "-") (Символы.ВКПС) "-") пс)
 +
 +
== Списки ==
 +
Из Файла справки [[Перфо - Списки]]
 +
 +
(Список Элемент1 .. ЭлементН) - создание списка
 +
(Длина Список) - количество элементов списка (работает так же для строк и коллекций)
 +
(ПЭЛ Список Индекс) - Получить Значение Элемента (работает так же для строк, массивов и индексируемых коллекций)
 +
(УЭЛ Список Значение Индекс) - Установить Значение Элемента (работает так же для массивов и индексируемых коллекций)
 +
(ОЭЛ Список) - возвращает список '''О'''стальных '''ЭЛ'''ементов кроме первого (работает так же с массивами и индексируемыми коллекциями)
 +
(Лев Список КоличествоЭлементов) - отрезать от списка сЛева указанное количество элементов
 +
(Прав Список КоличествоЭлементов) - отрезать от списка сПрава указанное количество элементов
 +
(Сред Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера.
 +
ИЛИ сделать: (Вырез Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера.
 +
 +
Проведём следующую работу:
 +
 +
(перем А 1)
 +
(перем Б 2)
 +
(перем В 3)
 +
(+ А 1)
 +
(+ Б 1)
 +
(+ В 1)
 +
 +
А как бы нам сразу взять 3 числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией:
 +
 +
(Список <элемент1> <элемент2> <элемент3> …)
 +
 +
Пример:
 +
 +
(Перем АБВ (список 1 1 1))                // список АБВ из трёх единиц
 +
(Перем Спис1 (список 1 2 3))                // список Спис1 из трёх разных чисел
 +
(Перем Спис2 (список "Привет" "Мой" "Мир")) // список Спис2 из строк
 +
(Перем Спис3 (список "Привет" 1 "Мир" 3))  // список Спис3 из строк и чисел
 +
(Перем Спис4 (список (+ 1 0) 2 3))          // элементы списка Спис4 можно вычислять перед его созданием
 +
 +
(вывод (длина Спис4) " элемента в списке " Спис4)
 +
 +
== ПоКаждому (аналог map в Scheme) ==
 +
Перфо также предоставляет функцию [[ПоВсем]] (map):
 +
 +
(ПоВсем <функция> <список>) -> возвращает <список>
 +
 +
Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример:
 +
 +
(функция (ПлюсОдин ф) (+ ф 1))  // увеличивает число на единицу
 +
(вывод (ПоКаждому ПлюсОдин (список 1 1 1)) )// возвращает список из двоек
 +
//(ПоКаждому квадрат (список 1 2 3)) // возвращает список из квадратов элементов, то есть 1, 4 и 9
 +
 +
Вспомним про анонимные (лямбда)-функции и решим задачу, которую поставили себе в начале этого раздела:
 +
 +
(Перем АБВ (список 1 1 1))
 +
(ПоКаждому (функ(Б) (+ Б 1)) АБВ)
 +
 +
А можно даже и список не вводить как дополнительную переменную:
 +
 +
(ПоКаждому (функ(Б) (+ Б 1))
 +
      (список 1 1 1))
 +
 +
=== Упражнение 3 ===
 +
 +
Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>ПоКаждому</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например
 +
 +
(print-it 5 (список 1 2 3)) // выведет "(6 7 8)"
 +
 +
== Работа со списками ==
 +
 +
А как написать функцию, которая последовательно будет перемещаться по заданному списку и выводить каждый элемент этого
 +
списка? Для этого нам надо только  знать следующие инструкции:
 +
 +
* <code>(пустой? <список>)</code> — проверяет, а есть ли ещё какие элементы в списке,
 +
* <code>(ПЭЛ <список>)</code> — возвращает первый элемент списка, (car)
 +
* <code>(ОЭЛ <список>)</code> — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список (crd).
 +
 +
'''Важно'''! Функция <code>Пустой?</code> - не является частью языка Перфо, это обычная функция написана на самом Перфо и помещённая в [[Стандартную Библиотеку Перфо]]. Функция <code>Длина</code> - определена в самом языке Перфо
 +
 +
// Функция Пустой?
 +
(функция (Пустой? впСписок) (если (= (Длина впСписок) 0) Да Нет))
 +
(функция (НеПустой? впСписок) (если (= (Длина впСписок) 0) Нет Да))
 +
 +
Пример:
 +
 +
(ПЭЛ (Список 1 2 3)) ; вернёт 1
 +
(ОЭЛ (список 1 2 3)) ; вернёт список из 2 и 3, то есть (список 2 3)
 +
 +
//длина списка
 +
(Вывод "длина списка: " (Длина спис1) пс)  // Вернёт 3
 +
(Вывод "Список пустой? " (Пустой? спис1) пс) // Венёт '''Да'''
 +
 +
Проще говоря, функция <code>ПЭЛ</code> возвращает голову списка, а <code>ОЭЛ</code> — оставшийся хвост списка.
 +
 +
Имя функции <code>ВыводСписка </code>. Единственный параметр — исходный список. Алгоритм работы нашей функции следующий:
 +
 +
Если список не пуст, то:
 +
 +
1. выводим голову списка.
 +
2. вызываем <code>ВыводСписка</code> для хвоста списка
 +
 +
(Функция (ВыводСписка внСписок)
 +
          ( если (НеПустой? внСписок)
 +
            (  (Вывод (ПЭЛ внСписок) (ПС))
 +
              (ВыводСписка (ОЭЛ внСписок)))
 +
            )
 +
)
 +
// Проверка работы функции
 +
(ВыводСписка (Список спис2 1 2 3 4))
 +
 +
=== Упражнение 4 ===
 +
 +
Поэкспериментируйте в интерпретаторе с функциями <code>ПЭЛ</code>, <code>ОЭЛ</code> и <code>пустой?</code>.
 +
 +
=== Упражнение 5 ===
 +
 +
Напишите функцию, которая будет вычислять длину списка. Длина пустого списка — ноль.
 +
 +
=== Упражнение 6 ===
 +
 +
Пользуясь изученными функциями <code>ПЭЛ</code>, <code>ОЭЛ</code> и <code>пустой?</code> напишите функцию <code>для-каждого-элемента</code>, которая будет применять заданную функцию к каждому элементу данного списка. Например, <code>(для-каждого-элемента Вывод (Список 1 2 3))</code> должна напечатать «123». Напишите новую версию <code>ВыводСписка </code>, пользуясь только что созданной функцией.
 +
 +
=== Упражнение 7 ===
 +
 +
Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus (список 1 2) (список 5 6))</code> должна вернуть список <code>(6 8)</code>.
 +
 +
=== Упражнение 8 ===
 +
 +
Попробуйте решить [[#Упражнение 7|упражнение 7]] с помощью функции <code>ПоКаждому</code> — правильный ответ вас сильно удивит.
 +
 +
== Частичное применение функций (Каррирование) в Перфо ==
 +
 +
//изначально существует функция с двумя параметрами
 +
(функция (сумма А Б)
 +
    (+ А Б)
 +
)
 +
//тест
 +
(вывод "(сумма 1 10) = " (сумма 1 10) пс) // возвращает 11
 +
 +
//мы хотим получить возможность предварительно задавать один параметр,
 +
//получая при этом новую функцию, примающую только второй параметр
 +
 +
(Функция (сумматор А)          // возвращает замкнутое лямбда-выражение, где:
 +
  (Функ (Б)                    // А - параметр принимающий значение, которое будет захваченно лямбда-выражением во время его создания,
 +
    (+ А Б))                  // Б - параметр получаемого лямбда-выражения
 +
)                             
 +
 +
(Перем плюс1 (сумматор 1))            // делаем функцию для прибавления 1 и сохраняем её в переменную
 +
(вывод "(плюс1 5) = " (плюс1 5) пс)  // возвращает 6
 +
(вывод "(плюс1 10) = " (плюс1 10) пс) // возвращает 11
 +
 +
(Перем минус1 (сумматор -1))            // делаем функцию для вычитания 1 и сохраняем её в переменную
 +
(вывод "(минус1 5) = " (минус1 5) пс)  // возвращает 4
 +
(вывод "(минус1 10) = " (минус1 10) пс) // возвращает 9
 +
 +
=== Частичное применение функции с тремя параметрами ===
 +
 +
//изначально существует функция с тремя параметрами
 +
(функция (СуммаСУмножением А Б К)
 +
    (* (+ А Б) К)
 +
)
 +
//тест
 +
(вывод "(СуммаСУмноженим 2 3 4) = " (СуммаСУмножением 2 3 4) пс) // возвращает 20
 +
 +
//мы хотим предварительно задавать коэффициет умножения
 +
 +
(Функция (УмножительСуммы К)  // возвращает замкнутое лямбда-выражение, где:
 +
  (Функ (А Б)                  // К - параметр принимающий значение, которое будет захваченно лямбда-выражением во время его создания,
 +
    (* (+ А Б) К))            // А и Б - параметры получаемого лямбда-выражения 
 +
)                 
 +
 +
(Перем умнож2 (УмножительСуммы 2))            // делаем функцию умножения суммы на 2 и сохраняем её в переменную
 +
(вывод "(умнож2 5 6) = " (умнож2 5 6) пс)    // возвращает 22
 +
(вывод "(умнож2 10 20) = " (умнож2 10 20) пс) // возвращает 60
 +
 +
(Перем умнож10 (УмножительСуммы 10))            // делаем функцию умножения суммы на 10 и сохраняем её в переменную
 +
(вывод "(умнож10 5 6) = " (умнож10 5 6) пс)    // возвращает 110
 +
(вывод "(умнож10 10 20) = " (умнож10 10 20) пс) // возвращает 300
 +
 +
== [[Препроцессор языка Перфо]] ==
 +
 +
== См. также ==
 +
* https://ru.wikibooks.org/wiki/Введение_в_язык_Scheme_для_школьников - оригинал который  был переделан в учебник по Перфо
 +
 +
* Викиучебник «[[Лисп]]».
 +
* [[w:Лисп|Лисп]], статья в Википедии.
 +
* [[w:Scheme|Scheme]], статья в Википедии.
 +
* [http://www.inr.ac.ru/~info21/software.htm примеры задач по Компонентному Паскалю]
 +
* [http://www.plt-scheme.org/ Plt Scheme], официальная страница Plt Scheme.
 +
* [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo.
 +
* [http://www.lispme.de/index.html LispMe], официальная страница LispMe.
 +
* [http://dynamo.iro.umontreal.ca/wiki/index.php/Main_Page Gambit-C], здесь можно скачать Gambit-C.
 +
 +
[[Категория:Функциональное программирование]]
 +
[[Категория:Перфо]]
 +
[[Категория:Scheme]]

Текущая версия на 18:54, 3 декабря 2020

Это заготовка для переделки под язык Перфо - учебника "Введение в Scheme для школьников".
  • Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в журнале «Потенциал».

Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — Лисп. Точнее, один из его самых интересных диалектов — Перфо, который является русским вариантом Scheme (Ским). Перфо не является копией Ским. Это адаптированный для практического программирования вариант интерпретатора функционального языка программирования.

Содержание

Примерное соответствие ключевых слов Ским (Scheme) и Перфо

  •  ; - // - комментарий, многострочный комментарий /* */ - не поддерживается
  • define - перем, функция - (определить)
    • перем - создаёт и изменяет переменную
    • функция - создаёт функцию
    • lambda - функ - создаёт анонимную (лямбда)-функцию
  • read - ввод - ввод строки пользователя
  • display - вывод
  • write - вывод
  • newline - ПС - перевод строки
  • if - если - оператор ветвления ЕСЛИ
  • positive? - (> переменная 0)
  • string-append - - склеивание строк
  • set! -
  • list - список
  • null? - Пустой? - для использования необходимо подключить Стандартную Библиотеку Перфо
  • map - ПоВсем
  • apply - ПоКаждому
  • begin - не к спискам относится, а к последовательности действий... в Перфо можно просто писать последовательность без ключевого слова begin. Например, на Scheme надо было бы написать:
(+ 3 (begin (set! y 4) y)  5)

а на Перфо будет так:

(+ 3 ((перем ф 4) ф) 5)

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

Перевод терминов функционального программирования

Введение в синтаксис

Сперва познакомимся с несколько необычным порядком слов этого языка: «действие — предмет». Но необычен он только в сравнении с популярными языками программирования. В русском языке такая последовательность нередка:

  • Сумма трёх и пяти.
  • Произведение пяти, шести и семи.
  • Купи в булочной батон.

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

(+ 3 5)
(* 5 6 7)
(купить булочная батон)

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

(купить булочная батон (+ 2 1))

«Купи в булочной батоны: два плюс ещё один». Просто, не правда ли? Давайте двигаться дальше.

Фраза (* 3 5) хороша, а (* ширина высота) — лучше.

Выражение (* 2 3.1415926 5) — интригующе, а (* 2 ЧислоПи Радиус) гораздо более осмысленно.

Здесь ширина, высота — переменные, а 3 и 5 — их текущие значения.

Переменная задаётся следующей конструкцией языка:

(Перем имя <первоначальное значение>)

Пример:

(перем ширина 3)
(перем высота 7)
(* 2 (+ ширина высота)) 

Прочитаем записанное по-человечески: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20.

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

(перем А 3)
(перем Б 4)
(+ (* А А) (* Б Б)) 

Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Перфо — квадрат:

(+ (квадрат А) (квадрат Б)) 

Добавим новые слова в программу

«Сумма квадрата А и квадрата Б». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её квадрат. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом:

(функция (квадрат x) (* x x)) 

Общий формат:

(функция (название параметр параметр …) (тело_функции)) 

Функция возвращает последнее вычисленное значение. Это означает, что следующая функция квадрат2:

(функция (квадрат2 x) (* 2 2) (* x x)) 

вернёт тот же результат, что и квадрат, перед этим умножив 2 на 2 безо всякого эффекта. Перепишем пример с суммой квадратов чисел заново:

(перем А 3)
(перем Б 4)
(функция (квадрат x) (* x x))
(+ (квадрат А) (квадрат Б)) 

Нам не хватало слов в языке — мы их добавили. Вообще, когда пишете программу на Перфо, вы описываете не алгоритм, а сначала создаёте язык, а потом на нём формулируете исходную задачу. Несколько точнее — вы «подгоняете» данный вам язык Перфо до тех пор, пока он не станет совпадать с языком, на котором задача формулируется легко.

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

Перфо предоставляет нам несколько готовых «глаголов»:

  • ввод: для чтения имени,
  • вывод: вывод чего-то на дисплее,
  • ПС: вывод перевода строки.

Мы бы хотели иметь такие «глаголы»:

  • привет: для приветствия с одним параметром — именем пользователя;
  • пользователь: для получения имени пользователя, без параметров.

Наша задача выглядела бы так:

(привет (пользователь)) 

Дело за малым — определить привет и пользователь. Нет проблем. Вот полный текст программы.

(функция (привет имя) // объявление функции Привет с параметром Имя
  (вывод "Привет, ")
  (вывод имя)
  (вывод "!")
  (ПС))
(функция (пользователь) // объявление функции Пользователь без параметров
  (ввод "Представьтесь: "))
(привет (пользователь)) 

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

Ввод данных в программу

Ввод данных пользователя прост и логичен. Как мы объявляем переменную ?

(Перем ИмяПользователя "Володя")
(Вывод "Вы ввели имя " ИмяПользователя)

Как присвоить новое значение переменной ИмяПользователя ? В Функцию присваивания Перем вместо "Володя" необходимо вставить результат работы другой функции.

(Перем ИмяПользователя (Ввод "Введи своё имя: "))
(Вывод "Вас зовут " ИмяПользователя ПС) // проверим какое  значение присвоилось, напоминаю ПС - перевод строки

Формат функции Ввод

(ввод "Сообщение-подсказка пользователю, что ввести ")

Операция унарный минус

(-) "минус" - Перфо тоже функция/глагол. Если её применить к значению (- 3) или (- ЛюбоеЧисло) то знак поменяется на противоположный. Математическим аналогом является умножение на -1. Операция унарный плюс возможна но не имеет смысла как умножение на +1.

(Вывод "Отрицательное число -3 = " -3 ПС)
(Вывод "Операция унарный минус (- 3) = " (- 3) ПС)
(Вывод "Операция унарный минус (- -3) = " (- -3) ПС)

Ветвление программы - оператор если

Например, функцию «модуль числа» можно определить так:

(функция (модуль ф) // описание функции Модуль с параметром Ф
         (если (> ф 0) // Если ф>0 то выполняется Часть 1 иначе Часть 2
                  ф // Часть 1
               (- ф) // Часть 2
         ) // Конец алгоритма функции Модуль
 ) // Конец описания функции Модуль

«Определим, что функция модуль возвращает свой аргумент, если он положителен, иначе — ». А можно и так:

(функция (модуль ф) // описание функции Модуль с параметром Ф
   // результат выполнения функции - знак (+) или (-) который применяется к переменной Ф 
         ((если (> ф 0) + -) ф) // Если ф>0 то возвращает знак (+) иначе возвращает знак (-)
         // знаки + и - на самом деле функции которые применяются к переменным или числам
         // поэтому результат выполнения функции Модуль - одна из 2-х функций + или - 
         // в  зависимости от значения Ф
)  

«…если аргумент положителен, то плюс, иначе минус ф». Здесь в результате исполнения выражения если возвращается функция + или -, которая затем применяется к аргументу ф. Полагаю, что смысл конструкции если вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков:

(если условие <действие, если условие выполняется> <действие в противном случае>)

Где посмотреть и попробовать

В теории всё хорошо, а где немного попрактиковаться? Необходимо скачать среду языка Перфолента.NET 0.46+, в ней выбрать Файл - "Создать программа на языке Перфо..."

Рекурсия и итерация

Простота Перфо (Лисп, Scheme ) обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии.

Упражнение

Посмотрите следующие две реализации функции вычисления факториала (т.е. умножаются все числа от 1 до N)

f(N) = 1 * 2 * N

Одна из них основана на рекурсии - это когда функция (или кусок кода) вызывает сама себя, а другая – на итерациях - это когда кусок кода повторяется несколько раз в цикле.

Напишите на Перфо рекурсивную (вызывающую саму себя) и основанную на итерациях (повторении) реализации функции возведения в степень N числа А

f(А, N) = A^N.

Вариант 1. Рекурсия - функция вызывает сама себя

(функция (факториал ф) // функция Факториал с параметром Ф
         (если (= ф 0) // если a = 0
                  1 // то функция возвращает в то место в коде откуда её вызвали 1 
               (* ф (факториал (- ф 1) ) ) // умножается значение Ф на значение возвращаемое 
                 // (факториал (- ф 1) ) - с параметром Ф-1 (на единицу меньше)
                 // т.е. внутри функции факториал вызывается функция факториал
                 // с другим (на единицу меньше) параметром!!
    ) 
)

Вариант 2. Повторение (Итерация)

Вычисление факториала методом повторения (итерации):

(функция (факториал-повторением результат счётчик) // определение функции Факториал-повторением 
        (если (= счётчик 0)
              результат
              (факториал-повторением (* счётчик результат)
                                  (- счётчик 1))))
(функция (факториал Число) (факториал-повторением 1 Число)) // описание функции Факториал которая использует функцию факториал-повторением 
// использование функции Факториал
(Вывод "Факториал 6: " (факториал 6)) // вывод " Факториал 6:" +  результат работы функции "Факториал" с параметром 6 
                                      //(которая сама вызывает другую функцию факториал-повторением )

Повторение – мать учения

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

Базовый синтаксис:

(<функция> <аргумент> <аргумент> …) // комментарий 

Например:

(+ 1 2 3 4)                     // сумма первых 4-х натуральных чисел
(+ (+ 1 2) 5 6)                 // возможно вкладывать одни вызовы функций в другие
(+ "Привет" "Мир")              // результат — склеенная строка "ПриветМир" 

Присвоить значение переменной:

(перем <имя> <значение>) 

Объявление Функции:

(функция (<имя> <параметр1> … <параметрН>) <тело функции>)

Функция возвращает значение последнего вычисленного выражения в теле функции

Например:

(перем А 3) // создаётся переменная А = 3
(перем Б 4) // создаётся переменная Б = 4
(функция (квадрат П) (* П П)) // описывается функция которая вычисляет квадрат числа переданного в параметр П
// результат = 16 
(функция (+А Б) (+ Б А)) // да-да, можно давать и такие имена функциям (любое символьное выражение)
// результат = 7

Конструкция Если То Иначе:

(если <условие> <действие при успехе условия> <действие при неудаче>)

Выполнение действий последовательно, как в обычном языке программирования:

(<первое действие> <второе действие> …) 

Пример:

(Перем Б -10) // Переменной Б присваивается 10
(если (> Б 0) // Если Б>0
    (вывод "Б > 0" ПС)) // Б > 0 - ПРАВДА выводится "Б > 0" и переводится строка,  
    // действие Б > 0 - ЛОЖЬ -  можно не указывать
((вывод 1)
 (вывод 2 ПС)
 (вывод 3)) // последовательно выполнятся действия, и на экран будет выведено: 12 и 3
Если вы ранее использовали Scheme то в нём последовательность действий (<первое действие> <второе действие> …) записывается со словом begin (begin <первое действие> <второе действие> …)

И опять про повторение: функция повторить

Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие 1 раз, то вы просто его делаете 1 раз:

(Вывод "Привет") 

Если вам надо выполнить какое-то действие 2 раза, то вы просто повторяете его:

(Вывод "Привет") (Вывод "Привет") 

А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет повторить, и у неё будет один параметр — количество повторов. Алгоритм следующий:

Если количество повторов больше нуля, то:

1. выполняем необходимое действие

2. из функции Повторить рекурсивно вызываем функцию повторитьс количеством повторов меньшим на 1

(функция (повторить ЧислоПовторов)
      (если (> ЧислоПовторов 0) // если количество повторов не нулевое
          ((вывод "Привет") // выполняем действие
                 (повторить(- ЧислоПовторов 1))))) // повторим действие на единицу меньшее количество раз. 
// проверим функцию Повторить
(повторить 5)

Упражнение 1

Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз.

// функция повторить
(функция (повторить ЧислоПовторов ЧтоПовторять)
      (если (> ЧислоПовторов 0) // если количество повторов не нулевое
          ((вывод ЧтоПовторять) // выполняем действие
                 (повторить (- ЧислоПовторов 1) ЧтоПовторять ) )) // функция вызывает сама себя с числом повторов на 1 меньше
      ) // повторим действие на единицу меньшее количество раз. 
// начало выполнения кода, вызываем функцию Повторить с параметром 5
(повторить 5 1)

Упражнение 2

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

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

  • первый параметр — количество повторов,
  • второй параметр — функция, которую надо запустить.
(функция(повторить ЧислоПовторов ВызываемаяФункция) // описание функции Повторить
(если (> ЧислоПовторов 0)
   ( (ВызываемаяФункция)
          (повторить (- ЧислоПовторов 1) ВызываемаяФункция)))) 
(функция (Вывести-Один) (вывод "1")) // описание функции Вывести-Один
(функция (Вывести-Привет) (вывод "Привет")) // описание функции Вывести-Привет
// Начало исполнения
(повторить 5 Вывести-Один) // 3 раза выведет на экран "1", используемая функция Вывести-Один
(вывод ПС) // Перевод строки
(повторить 5 Вывести-Привет) // 5 раз выведет на экран "Привет", используемая функция Вывести-Привет

Принцип наименьших усилий. Анонимные (лямбда)-функции

Не надо делать лишних действий, если их можно избежать. Последний вариант функции повторить хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию».

(функ (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)

Пример:

(функ (ф) (* ф ф))    // создать функцию, которая вычисляет квадрат числа
(функ (ф ч) (+ ф ч)) // создать функцию, которая вычисляет сумму двух чисел
(перем квадрат (функ (ф) (* ф ф)))  // создать лямбда-функцию с переменой Ф.
// ссылка на функцию присваивается ПЕРЕМЕННОЙ "квадрат".
// При использовании переменной "квадрат" числа 100, это число обрабатывается 
// функцией которая была присвоена переменной "квадрат"
// этой переменной можно присвоить другую функцию, и следующее число будет обработано другой функцией
(Вывод "Квадрат  = " (квадрат 100) ПС)

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

(функция (квадрат ф) (* ф ф))

Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям:

(повторить 3 (функ() (вывод "Привет")) ) // три раза выведем на экран "Привет"
(повторить 5 (функ() (вывод "1"))) // пять раз выведем на экран "1"

Для повторить используются функции без параметров, поэтому в конструкции функ пустая пара скобок.

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

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

Пример:

//создадим две анонимных функции 
//и сохраним их в переменные квадрат и куб
(перем квадрат (функ (ф) (* ф ф)))  
(перем куб (функ (ф) (* ф ф ф)))
//создадим функцию для построения графика
(функция (график НачИнтервала КонИнтервала ФункцияГрафика) 
   (Для (Инд НачИнтервала КонИнтервала)
     (Вывод "х=" Инд " у=" (ФункцияГрафика Инд) "; ")  
   ) 
   (Вывод пс)
)
//строим график функции квадрат
(график 1 4 квадрат)
//строим график функции куб
(график -3 3 куб)

Работа с файлами

Чтение текста из файла Пример2.perfo - файл должен существовать в файле с запускаемой программой на Перфо

//прочитаем текст из файла
(Перем Ф (+ (ФС.ТекущийКаталог) "Пример2.perfo"))
(Вывод "Имя файла: " Ф пс)
(Перем Чт (Новый ЧтениеТекста Ф))
(Перем Т (Чт.ПрочитатьДоКонца))
(Вывод "Содержимое файла: " (СтрЗаменить (СтрЗаменить (Сред Т 100 100) " " "-") (Символы.ВКПС) "-") пс)

Списки

Из Файла справки Перфо - Списки

(Список Элемент1 .. ЭлементН) - создание списка
(Длина Список) - количество элементов списка (работает так же для строк и коллекций)
(ПЭЛ Список Индекс) - Получить Значение Элемента (работает так же для строк, массивов и индексируемых коллекций)
(УЭЛ Список Значение Индекс) - Установить Значение Элемента (работает так же для массивов и индексируемых коллекций)
(ОЭЛ Список) - возвращает список Остальных ЭЛементов кроме первого (работает так же с массивами и индексируемыми коллекциями)
(Лев Список КоличествоЭлементов) - отрезать от списка сЛева указанное количество элементов 
(Прав Список КоличествоЭлементов) - отрезать от списка сПрава указанное количество элементов 
(Сред Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера.
ИЛИ сделать: (Вырез Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера.

Проведём следующую работу:

(перем А 1)
(перем Б 2)
(перем В 3)
(+ А 1)
(+ Б 1)
(+ В 1) 

А как бы нам сразу взять 3 числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией:

(Список <элемент1> <элемент2> <элемент3> …) 

Пример:

(Перем АБВ (список 1 1 1))                 // список АБВ из трёх единиц
(Перем Спис1 (список 1 2 3))                // список Спис1 из трёх разных чисел
(Перем Спис2 (список "Привет" "Мой" "Мир")) // список Спис2 из строк
(Перем Спис3 (список "Привет" 1 "Мир" 3))   // список Спис3 из строк и чисел
(Перем Спис4 (список (+ 1 0) 2 3))          // элементы списка Спис4 можно вычислять перед его созданием 

(вывод (длина Спис4) " элемента в списке " Спис4)

ПоКаждому (аналог map в Scheme)

Перфо также предоставляет функцию ПоВсем (map):

(ПоВсем <функция> <список>) -> возвращает <список>

Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример:

(функция (ПлюсОдин ф) (+ ф 1))  // увеличивает число на единицу
(вывод (ПоКаждому ПлюсОдин (список 1 1 1)) )// возвращает список из двоек
//(ПоКаждому квадрат (список 1 2 3)) // возвращает список из квадратов элементов, то есть 1, 4 и 9 

Вспомним про анонимные (лямбда)-функции и решим задачу, которую поставили себе в начале этого раздела:

(Перем АБВ (список 1 1 1))
(ПоКаждому (функ(Б) (+ Б 1)) АБВ) 

А можно даже и список не вводить как дополнительную переменную:

(ПоКаждому (функ(Б) (+ Б 1))
      (список 1 1 1)) 

Упражнение 3

Пользуясь функциями write (для печати списка на экране) и ПоКаждому, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например

(print-it 5 (список 1 2 3)) // выведет "(6 7 8)"

Работа со списками

А как написать функцию, которая последовательно будет перемещаться по заданному списку и выводить каждый элемент этого списка? Для этого нам надо только знать следующие инструкции:

  • (пустой? <список>) — проверяет, а есть ли ещё какие элементы в списке,
  • (ПЭЛ <список>) — возвращает первый элемент списка, (car)
  • (ОЭЛ <список>) — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список (crd).

Важно! Функция Пустой? - не является частью языка Перфо, это обычная функция написана на самом Перфо и помещённая в Стандартную Библиотеку Перфо. Функция Длина - определена в самом языке Перфо

// Функция Пустой?
(функция (Пустой? впСписок) (если (= (Длина впСписок) 0) Да Нет))
(функция (НеПустой? впСписок) (если (= (Длина впСписок) 0) Нет Да))

Пример:

(ПЭЛ (Список 1 2 3)) ; вернёт 1
(ОЭЛ (список 1 2 3)) ; вернёт список из 2 и 3, то есть (список 2 3)
//длина списка
(Вывод "длина списка: " (Длина спис1) пс)  // Вернёт 3
(Вывод "Список пустой? " (Пустой? спис1) пс) // Венёт Да

Проще говоря, функция ПЭЛ возвращает голову списка, а ОЭЛ — оставшийся хвост списка.

Имя функции ВыводСписка . Единственный параметр — исходный список. Алгоритм работы нашей функции следующий:

Если список не пуст, то:

1. выводим голову списка. 2. вызываем ВыводСписка для хвоста списка

(Функция (ВыводСписка внСписок)
         ( если (НеПустой? внСписок) 
           (  (Вывод (ПЭЛ внСписок) (ПС))
              (ВыводСписка (ОЭЛ внСписок)))
           )
)
// Проверка работы функции
(ВыводСписка (Список спис2 1 2 3 4))

Упражнение 4

Поэкспериментируйте в интерпретаторе с функциями ПЭЛ, ОЭЛ и пустой?.

Упражнение 5

Напишите функцию, которая будет вычислять длину списка. Длина пустого списка — ноль.

Упражнение 6

Пользуясь изученными функциями ПЭЛ, ОЭЛ и пустой? напишите функцию для-каждого-элемента, которая будет применять заданную функцию к каждому элементу данного списка. Например, (для-каждого-элемента Вывод (Список 1 2 3)) должна напечатать «123». Напишите новую версию ВыводСписка , пользуясь только что созданной функцией.

Упражнение 7

Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда (plus (список 1 2) (список 5 6)) должна вернуть список (6 8).

Упражнение 8

Попробуйте решить упражнение 7 с помощью функции ПоКаждому — правильный ответ вас сильно удивит.

Частичное применение функций (Каррирование) в Перфо

//изначально существует функция с двумя параметрами
(функция (сумма А Б)
   (+ А Б)
)
//тест
(вывод "(сумма 1 10) = " (сумма 1 10) пс) // возвращает 11
//мы хотим получить возможность предварительно задавать один параметр,
//получая при этом новую функцию, примающую только второй параметр
(Функция (сумматор А)          // возвращает замкнутое лямбда-выражение, где:
  (Функ (Б)                    // А - параметр принимающий значение, которое будет захваченно лямбда-выражением во время его создания,
    (+ А Б))                   // Б - параметр получаемого лямбда-выражения
)                               
(Перем плюс1 (сумматор 1))            // делаем функцию для прибавления 1 и сохраняем её в переменную
(вывод "(плюс1 5) = " (плюс1 5) пс)   // возвращает 6
(вывод "(плюс1 10) = " (плюс1 10) пс) // возвращает 11
(Перем минус1 (сумматор -1))            // делаем функцию для вычитания 1 и сохраняем её в переменную
(вывод "(минус1 5) = " (минус1 5) пс)   // возвращает 4
(вывод "(минус1 10) = " (минус1 10) пс) // возвращает 9

Частичное применение функции с тремя параметрами

//изначально существует функция с тремя параметрами
(функция (СуммаСУмножением А Б К)
   (* (+ А Б) К)
)
//тест
(вывод "(СуммаСУмноженим 2 3 4) = " (СуммаСУмножением 2 3 4) пс) // возвращает 20
//мы хотим предварительно задавать коэффициет умножения
(Функция (УмножительСуммы К)   // возвращает замкнутое лямбда-выражение, где:
 (Функ (А Б)                  // К - параметр принимающий значение, которое будет захваченно лямбда-выражением во время его создания,
   (* (+ А Б) К))             // А и Б - параметры получаемого лямбда-выражения  
)                   
(Перем умнож2 (УмножительСуммы 2))            // делаем функцию умножения суммы на 2 и сохраняем её в переменную
(вывод "(умнож2 5 6) = " (умнож2 5 6) пс)     // возвращает 22
(вывод "(умнож2 10 20) = " (умнож2 10 20) пс) // возвращает 60
(Перем умнож10 (УмножительСуммы 10))            // делаем функцию умножения суммы на 10 и сохраняем её в переменную
(вывод "(умнож10 5 6) = " (умнож10 5 6) пс)     // возвращает 110
(вывод "(умнож10 10 20) = " (умнож10 10 20) пс) // возвращает 300

Препроцессор языка Перфо

См. также