Учебник языка Перфо для школьников — различия между версиями
Линукс (обсуждение | вклад) м (→Соответствие ключевых слов Ским (Scheme) и Перфо) |
Админ (обсуждение | вклад) м (→Упражнение 6) |
||
(не показано 29 промежуточных версий 3 участников) | |||
Строка 23: | Строка 23: | ||
* set! - | * set! - | ||
* list - список | * list - список | ||
− | * map - [[ | + | * null? - Пустой? - для использования необходимо подключить [[Стандартную Библиотеку Перфо]] |
− | * apply - [[ | + | * map - [[ПоВсем]] |
+ | * apply - [[ПоКаждому]] | ||
* begin - не к спискам относится, а к последовательности действий... в Перфо можно просто писать последовательность без ключевого слова '''begin'''. Например, на Scheme надо было бы написать: | * begin - не к спискам относится, а к последовательности действий... в Перфо можно просто писать последовательность без ключевого слова '''begin'''. Например, на Scheme надо было бы написать: | ||
Строка 93: | Строка 94: | ||
Общий формат: | Общий формат: | ||
− | (функция (название параметр параметр …) тело_функции) | + | (функция (название параметр параметр …) (тело_функции)) |
Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>квадрат2</code>: | Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>квадрат2</code>: | ||
Строка 145: | Строка 146: | ||
(Вывод "Вы ввели имя " ИмяПользователя) | (Вывод "Вы ввели имя " ИмяПользователя) | ||
− | Как присвоить новое значение переменной ИмяПользователя ? В Функцию присваивания '''Перем''' вместо "Володя" необходимо вставить результат работы другой функции. | + | Как присвоить новое значение переменной '''ИмяПользователя''' ? В Функцию присваивания '''Перем''' вместо "Володя" необходимо вставить результат работы другой функции. |
(Перем ИмяПользователя (Ввод "Введи своё имя: ")) | (Перем ИмяПользователя (Ввод "Введи своё имя: ")) | ||
Строка 263: | Строка 264: | ||
// результат = 7 | // результат = 7 | ||
− | + | Конструкция Если То Иначе: | |
(если <условие> <действие при успехе условия> <действие при неудаче>) | (если <условие> <действие при успехе условия> <действие при неудаче>) | ||
− | ( | + | |
+ | Выполнение действий последовательно, как в обычном языке программирования: | ||
+ | |||
+ | (<первое действие> <второе действие> …) | ||
Пример: | Пример: | ||
− | (если (> | + | (Перем Б -10) // Переменной Б присваивается 10 |
− | (вывод " | + | (если (> Б 0) // Если Б>0 |
− | ( | + | (вывод "Б > 0" ПС)) // Б > 0 - ПРАВДА выводится "Б > 0" и переводится строка, |
− | + | // действие Б > 0 - ЛОЖЬ - можно не указывать | |
− | + | ((вывод 1) | |
+ | (вывод 2 ПС) | ||
+ | (вывод 3)) // последовательно выполнятся действия, и на экран будет выведено: 12 и 3 | ||
+ | |||
+ | Если вы ранее использовали Scheme то в нём последовательность действий (<первое действие> <второе действие> …) записывается со словом begin ('''begin''' <первое действие> <второе действие> …) | ||
== И опять про повторение: функция <code>повторить</code> == | == И опять про повторение: функция <code>повторить</code> == | ||
Строка 289: | Строка 297: | ||
Если количество повторов больше нуля, то: | Если количество повторов больше нуля, то: | ||
− | 1. выполняем действие | + | 1. выполняем необходимое действие |
− | 2. | + | 2. из функции Повторить рекурсивно вызываем функцию <code>повторить</code>с количеством повторов меньшим на 1 |
(функция (повторить ЧислоПовторов) | (функция (повторить ЧислоПовторов) | ||
(если (> ЧислоПовторов 0) // если количество повторов не нулевое | (если (> ЧислоПовторов 0) // если количество повторов не нулевое | ||
− | ( | + | ((вывод "Привет") // выполняем действие |
(повторить(- ЧислоПовторов 1))))) // повторим действие на единицу меньшее количество раз. | (повторить(- ЧислоПовторов 1))))) // повторим действие на единицу меньшее количество раз. | ||
// проверим функцию Повторить | // проверим функцию Повторить | ||
Строка 303: | Строка 311: | ||
Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз. | Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз. | ||
+ | |||
+ | // функция повторить | ||
+ | (функция (повторить ЧислоПовторов ЧтоПовторять) | ||
+ | (если (> ЧислоПовторов 0) // если количество повторов не нулевое | ||
+ | ((вывод ЧтоПовторять) // выполняем действие | ||
+ | (повторить (- ЧислоПовторов 1) ЧтоПовторять ) )) // функция вызывает сама себя с числом повторов на 1 меньше | ||
+ | ) // повторим действие на единицу меньшее количество раз. | ||
+ | // начало выполнения кода, вызываем функцию Повторить с параметром 5 | ||
+ | (повторить 5 1) | ||
=== Упражнение 2 === | === Упражнение 2 === | ||
Строка 326: | Строка 343: | ||
== Принцип наименьших усилий. Анонимные (лямбда)-функции == | == Принцип наименьших усилий. Анонимные (лямбда)-функции == | ||
− | Не надо делать лишних действий, если их можно избежать. Последний вариант функции | + | Не надо делать лишних действий, если их можно избежать. Последний вариант функции '''повторить''' хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию». |
(функ (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>) | (функ (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>) | ||
Строка 377: | Строка 394: | ||
//прочитаем текст из файла | //прочитаем текст из файла | ||
− | (Перем Ф (+ ( | + | (Перем Ф (+ (ФС.ТекущийКаталог) "Пример2.perfo")) |
(Вывод "Имя файла: " Ф пс) | (Вывод "Имя файла: " Ф пс) | ||
(Перем Чт (Новый ЧтениеТекста Ф)) | (Перем Чт (Новый ЧтениеТекста Ф)) | ||
Строка 384: | Строка 401: | ||
== Списки == | == Списки == | ||
− | Из Файла справки | + | Из Файла справки [[Перфо - Списки]] |
− | + | (Список Элемент1 .. ЭлементН) - создание списка | |
− | + | (Длина Список) - количество элементов списка (работает так же для строк и коллекций) | |
− | + | (ПЭЛ Список Индекс) - Получить Значение Элемента (работает так же для строк, массивов и индексируемых коллекций) | |
− | + | (УЭЛ Список Значение Индекс) - Установить Значение Элемента (работает так же для массивов и индексируемых коллекций) | |
− | + | (ОЭЛ Список) - возвращает список '''О'''стальных '''ЭЛ'''ементов кроме первого (работает так же с массивами и индексируемыми коллекциями) | |
− | + | (Лев Список КоличествоЭлементов) - отрезать от списка сЛева указанное количество элементов | |
− | + | (Прав Список КоличествоЭлементов) - отрезать от списка сПрава указанное количество элементов | |
− | + | (Сред Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера. | |
− | + | ИЛИ сделать: (Вырез Список НомерНачальногоЭлемента КоличествоЭлементов) - вырезать из списка указанное количество элементов начиная с указанного номера. | |
− | |||
− | |||
− | |||
− | |||
Проведём следующую работу: | Проведём следующую работу: | ||
Строка 415: | Строка 428: | ||
Пример: | Пример: | ||
− | ( | + | (Перем АБВ (список 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)) )// возвращает список из двоек |
− | ( | + | //(ПоКаждому квадрат (список 1 2 3)) // возвращает список из квадратов элементов, то есть 1, 4 и 9 |
Вспомним про анонимные (лямбда)-функции и решим задачу, которую поставили себе в начале этого раздела: | Вспомним про анонимные (лямбда)-функции и решим задачу, которую поставили себе в начале этого раздела: | ||
− | ( | + | (Перем АБВ (список 1 1 1)) |
− | ( | + | (ПоКаждому (функ(Б) (+ Б 1)) АБВ) |
А можно даже и список не вводить как дополнительную переменную: | А можно даже и список не вводить как дополнительную переменную: | ||
− | ( | + | (ПоКаждому (функ(Б) (+ Б 1)) |
− | ( | + | (список 1 1 1)) |
=== Упражнение 3 === | === Упражнение 3 === | ||
− | Пользуясь функциями <code>write</code> (для печати списка на экране) и <code> | + | Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>ПоКаждому</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например |
− | (print-it 5 ( | + | (print-it 5 (список 1 2 3)) // выведет "(6 7 8)" |
== Работа со списками == | == Работа со списками == | ||
Строка 452: | Строка 468: | ||
списка? Для этого нам надо только знать следующие инструкции: | списка? Для этого нам надо только знать следующие инструкции: | ||
− | * <code>( | + | * <code>(пустой? <список>)</code> — проверяет, а есть ли ещё какие элементы в списке, |
− | * <code>( | + | * <code>(ПЭЛ <список>)</code> — возвращает первый элемент списка, (car) |
− | * <code>( | + | * <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>ВыводСписка </code>. Единственный параметр — исходный список. Алгоритм работы нашей функции следующий: | ||
Если список не пуст, то: | Если список не пуст, то: | ||
1. выводим голову списка. | 1. выводим голову списка. | ||
− | 2. вызываем <code> | + | 2. вызываем <code>ВыводСписка</code> для хвоста списка |
− | ( | + | (Функция (ВыводСписка внСписок) |
− | + | ( если (НеПустой? внСписок) | |
− | + | ( (Вывод (ПЭЛ внСписок) (ПС)) | |
− | + | (ВыводСписка (ОЭЛ внСписок))) | |
− | + | ) | |
− | + | ) | |
− | + | // Проверка работы функции | |
− | + | (ВыводСписка (Список спис2 1 2 3 4)) | |
− | |||
− | |||
− | |||
− | |||
− | |||
=== Упражнение 4 === | === Упражнение 4 === | ||
− | Поэкспериментируйте в интерпретаторе с функциями <code> | + | Поэкспериментируйте в интерпретаторе с функциями <code>ПЭЛ</code>, <code>ОЭЛ</code> и <code>пустой?</code>. |
=== Упражнение 5 === | === Упражнение 5 === | ||
Строка 494: | Строка 515: | ||
=== Упражнение 6 === | === Упражнение 6 === | ||
− | Пользуясь изученными функциями <code> | + | Пользуясь изученными функциями <code>ПЭЛ</code>, <code>ОЭЛ</code> и <code>пустой?</code> напишите функцию <code>для-каждого-элемента</code>, которая будет применять заданную функцию к каждому элементу данного списка. Например, <code>(для-каждого-элемента Вывод (Список 1 2 3))</code> должна напечатать «123». Напишите новую версию <code>ВыводСписка </code>, пользуясь только что созданной функцией. |
=== Упражнение 7 === | === Упражнение 7 === | ||
− | Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus ( | + | Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus (список 1 2) (список 5 6))</code> должна вернуть список <code>(6 8)</code>. |
=== Упражнение 8 === | === Упражнение 8 === | ||
− | Попробуйте решить [[#Упражнение 7|упражнение 7]] с помощью функции <code> | + | Попробуйте решить [[#Упражнение 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 | ||
+ | |||
+ | == [[Препроцессор языка Перфо]] == | ||
== См. также == | == См. также == | ||
Строка 510: | Строка 582: | ||
* [[w:Лисп|Лисп]], статья в Википедии. | * [[w:Лисп|Лисп]], статья в Википедии. | ||
* [[w:Scheme|Scheme]], статья в Википедии. | * [[w:Scheme|Scheme]], статья в Википедии. | ||
+ | * [http://www.inr.ac.ru/~info21/software.htm примеры задач по Компонентному Паскалю] | ||
* [http://www.plt-scheme.org/ Plt Scheme], официальная страница Plt Scheme. | * [http://www.plt-scheme.org/ Plt Scheme], официальная страница Plt Scheme. | ||
* [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo. | * [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo. | ||
* [http://www.lispme.de/index.html LispMe], официальная страница LispMe. | * [http://www.lispme.de/index.html LispMe], официальная страница LispMe. | ||
− | * [http:// | + | * [http://dynamo.iro.umontreal.ca/wiki/index.php/Main_Page Gambit-C], здесь можно скачать Gambit-C. |
[[Категория:Функциональное программирование]] | [[Категория:Функциональное программирование]] | ||
[[Категория:Перфо]] | [[Категория:Перфо]] | ||
[[Категория:Scheme]] | [[Категория:Scheme]] |
Текущая версия на 18:54, 3 декабря 2020
Это заготовка для переделки под язык Перфо - учебника "Введение в Scheme для школьников".
- https://ru.wikibooks.org/wiki/Введение_в_язык_Scheme_для_школьников - Оригинал "Введение в Scheme для школьников"
- https://habr.com/ru/post/505928/ Почему функциональное программирование такое сложное - Хабр
- Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в журнале «Потенциал».
Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — Лисп. Точнее, один из его самых интересных диалектов — Перфо, который является русским вариантом Scheme (Ским). Перфо не является копией Ским. Это адаптированный для практического программирования вариант интерпретатора функционального языка программирования.
Содержание
- 1 Примерное соответствие ключевых слов Ским (Scheme) и Перфо
- 2 Введение в синтаксис
- 3 Где посмотреть и попробовать
- 4 Рекурсия и итерация
- 5 Повторение – мать учения
- 6 И опять про повторение: функция повторить
- 7 Принцип наименьших усилий. Анонимные (лямбда)-функции
- 8 Работа с файлами
- 9 Списки
- 10 ПоКаждому (аналог map в Scheme)
- 11 Работа со списками
- 12 Частичное применение функций (Каррирование) в Перфо
- 13 Препроцессор языка Перфо
- 14 См. также
Примерное соответствие ключевых слов Ским (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
Препроцессор языка Перфо
См. также
- https://ru.wikibooks.org/wiki/Введение_в_язык_Scheme_для_школьников - оригинал который был переделан в учебник по Перфо
- Викиучебник «Лисп».
- Лисп, статья в Википедии.
- Scheme, статья в Википедии.
- примеры задач по Компонентному Паскалю
- Plt Scheme, официальная страница Plt Scheme.
- Bigloo, страничка о Bigloo.
- LispMe, официальная страница LispMe.
- Gambit-C, здесь можно скачать Gambit-C.