Викиучебник ruwikibooks https://ru.wikibooks.org/wiki/%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0 MediaWiki 1.47.0-wmf.14 first-letter Медиа Служебная Обсуждение Участник Обсуждение участника Викиучебник Обсуждение Викиучебника Файл Обсуждение файла MediaWiki Обсуждение MediaWiki Шаблон Обсуждение шаблона Справка Обсуждение справки Категория Обсуждение категории Полка Обсуждение полки Импортировано Обсуждение импортированного Рецепт Обсуждение рецепта Задача Обсуждение задачи TimedText TimedText talk Модуль Обсуждение модуля Event Event talk Введение в язык Scheme для школьников 0 2455 269774 254848 2026-08-10T06:39:16Z ~2026-43978-25 79732 /* См. также */ обновление ссылки 269774 wikitext text/x-wiki * Часть викиучебника «[[Лисп]]» * Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в [[Журнал «Потенциал»|журнале «Потенциал»]]. Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — [[w:Лисп|Лисп]]. Точнее, один из его самых интересных диалектов — [[w:Scheme|Scheme]] (''Ским''). == Введение в синтаксис== Сперва познакомимся с несколько необычным порядком слов этого языка: «действие — предмет». Но необычен он только в сравнении с популярными языками программирования. В русском языке такая последовательность нередка: * Сумма трёх и пяти. * Произведение пяти, шести и семи. * Купить булочный батон. Каждая законченная фраза на этом языке должна быть окружена парой круглых скобок. Запишем сказанное выше на Scheme: <syntaxhighlight lang="scheme">(+ 3 5) (* 5 6 7) (купить булочный батон)</syntaxhighlight> Можно записать выражения и посложнее: <syntaxhighlight lang="scheme">(купить булочная батон (+ 2 1))</syntaxhighlight> «Купи в булочной батоны: два плюс ещё один». Просто, не правда ли? Давайте двигаться дальше. Фраза <code>(* 3 5)</code> хороша, а <code>(* width height)</code> — лучше. Выражение <code>(* 2 3.1415926 5)</code> — интригующе, а <code>(* 2 pi radius)</code> гораздо более осмысленно. Здесь <code>width</code>, <code>height</code> — переменные, а <code>3</code> и <code>5</code> — их текущие значения. Переменная задаётся следующей конструкцией языка: <syntaxhighlight lang="scheme">(define имя <первоначальное значение>)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(define width 3) (define height 7) (* 2 (+ width height))</syntaxhighlight> Прочитаем записанное по-русски: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20. Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (+ (* a a) (* b b))</syntaxhighlight> Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Скиме — <code>square</code>: <syntaxhighlight lang="scheme">(+ (square a) (square b))</syntaxhighlight> «Сумма квадрата <code>a</code> и квадрата <code>b</code>». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её <code>square</code>. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом: <syntaxhighlight lang="scheme">(define (square x) (* x x))</syntaxhighlight> Общий формат: <syntaxhighlight lang="scheme">(define (название параметр параметр …) тело_функции)</syntaxhighlight> Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>square2</code>: <syntaxhighlight lang="scheme">(define (square2 x) (* 2 2) (* x x))</syntaxhighlight> вернёт тот же результат, что и <code>square</code>, перед этим умножив два на два безо всякого эффекта. Перепишем пример с суммой квадратов чисел заново: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (define (square x) (* x x)) (+ (square a) (square b))</syntaxhighlight> Нам не хватало слов в языке — мы их добавили. Вообще, когда пишете программу на Лиспе, вы описываете не алгоритм, а сначала создаёте язык, а потом на нём формулируете исходную задачу. Несколько точнее — вы «подгоняете» данный вам язык Scheme до тех пор, пока он не станет совпадать с языком, на котором задача формулируется легко. Сразу пример. Пусть перед нами стоит задача сделать программу, которая спрашивает имя пользователя, а потом выводит ему приветствие. Scheme предоставляет нам несколько готовых «глаголов»: ; <code>read</code>: для чтения имени, ; <code>display</code>: вывод чего-то на дисплее, ; <code>newline</code>: вывод перевода строки. Мы бы хотели иметь такие «глаголы»: ; <code>привет</code>: для приветствия с одним параметром — именем пользователя; ; <code>пользователь</code>: для получения имени пользователя, без параметров. Наша задача выглядела бы так: <syntaxhighlight lang="scheme">(привет (пользователь))</syntaxhighlight> Дело за малым — определить <code>привет</code> и <code>пользователь</code>. Нет проблем. Вот полный текст программы. <syntaxhighlight lang="scheme">(define (привет имя) (display "Привет, ") (display имя) (display "!") (newline)) (define (пользователь) (write "Представьтесь:") (read)) (привет (пользователь))</syntaxhighlight> [[w:Лисп|Лисп]] — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется. Например, функцию «модуль числа» можно определить так: <syntaxhighlight lang=lisp>(define (abs x) (if (positive? x ) x (- x)))</syntaxhighlight> «Определим, что функция <code>abs</code> возвращает свой аргумент, если он положителен, иначе — <code>-x</code>». А можно и так: <syntaxhighlight lang="scheme">(define (abs x) ((if (positive? x) + -) x))</syntaxhighlight> «…если аргумент положителен, то плюс, иначе минус <code>x</code>». Здесь в результате исполнения выражения <code>if</code> возвращается функция <code>+</code> или <code>-</code>, которая затем применяется к аргументу <code>x</code>. Полагаю, что смысл конструкции <code>if</code> вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков: <syntaxhighlight lang="scheme">(if условие <действие, если условие выполняется> <действие в противном случае>)</syntaxhighlight> == Где посмотреть и попробовать == В теории всё хорошо, а где немного попрактиковаться? В мире можно найти много прекрасно разработанных сред для работы со Scheme. К сожалению, большинство документации по Scheme на английском языке, но можно найти и отличные введения на русском — язык-то простой. Вот названия нескольких самых распространённых реализаций: ; [http://racket-lang.org/ Racket]: одна из самых полных реализаций, включает в себя удобную обучающую среду Dr.Scheme. Есть версии для платформ [[w:Windows|Windows]], [[w:Linux|Linux]], [[w:Mac OS|Mac OS]]. ; [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo]: тоже достаточно полная реализация. Доступна для платформ [[w:Windows|Windows]], [[w:Linux|Linux]]. ; [http://www.lispme.de/index.html LispMe]: версия для карманных компьютеров с операционной системой [[w:Palm OS|Palm OS]]. ; [https://www.gnu.org/software/guile/ GNU Guile]: распространённая реализация Scheme от проекта [https://www.gnu.org/ GNU], которая используется в ОС [https://guix.gnu.org/ GNU Guix]. Доступна для GNU/[[w:Linux|Linux]]. Возможна компиляция. Также ещё посмотрите [http://www.iro.umontreal.ca/~gambit/ Gambit-C] — один из самых быстрых компиляторов Scheme. Все перечисленные реализации Scheme — это интерпретаторы. Запускаете интерпретатор — и можно вести с ним диалог на Scheme: в ответ на его приглашение вводите конструкции на Scheme, а он будет возвращать результаты вычислений. {{console|<pre style="border:none;margin:0;padding:0">Wecome to Mz Scheme version 208, Copyright (c) 2004 PLT Scheme, Inc. >1 1 >(+ 1 2) 3 >(define a 3) >(+ a a) 6 ></pre>}} Попробуйте «проиграть» все вышеперечисленные примеры. Думаю, вам понравится! == Кот в мешке == Простота Scheme обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии. === Упражнение === Посмотрите следующие две реализации функции вычисления [[w:Факториал|факториала]] <math>f(n) = 1 \cdot 2 \cdots n</math>. Одна из них основана на [[w:Рекурсия|рекурсии]], а другая – на [[w:Итерация|итерациях]]. Напишите на Scheme рекурсивную и основанную на итерациях реализации функции возведения в степень <math>f(a, n) = a^n</math>. ==== Вариант 1 ==== <syntaxhighlight lang="scheme">(define (factorial n) (if (= n 0) 1 (* n (factorial (- n 1)))))</syntaxhighlight> ==== Вариант 2 ==== <syntaxhighlight lang="scheme">(define (fact-iter result counter) (if (= counter 0) result (fact-iter (* counter result) (- counter 1)))) (define (factorial n) (fact-iter 1 n))</syntaxhighlight> == Повторение – мать учения == Приведём небольшую шпаргалку по базовым конструкциям Scheme для тех, кто еще не ознакомился с первой частью статьи. Базовый синтаксис: <syntaxhighlight lang="scheme">(<функция> <аргумент> <аргумент> …) ; комментарий</syntaxhighlight> Например: <syntaxhighlight lang="scheme">(+ 1 2 3 4) ; сумма первых четырёх натуральных чисел (+ (+ 1 2) 5 6) ; возможно вкладывать одни вызовы функций в другие (string-append "hello" "world") ; результат — склеенная строка "helloworld"</syntaxhighlight> Задание переменных и функций: <syntaxhighlight lang="scheme">(define <имя> <значение>) (define (<имя> <аргумент> …) <тело функции>) ; функция возвращает значение последнего вычислительного выражения в теле функции</syntaxhighlight> Например: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (define (square x) (* x x)) ; вычисляет квадрат числа (define (+a x) (+ x a)) ; да-да, можно давать и такие имена функциям</syntaxhighlight> Дополнительные конструкции: <syntaxhighlight lang="scheme">(if <условие> <действие при успехе условия> <действие при неудаче>) (begin <первое действие> <второе действие> …)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(if (> a 0) (display "a > 0")) ; действие при неудаче можно не указывать (begin (display 1) (display 2) (display 3)) ; последовательно выполнятся действия, и на экран будет выведено: 123</syntaxhighlight> == И опять про повторение: функция <code>repeat</code> == Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие один раз, то вы просто его делаете один раз: <syntaxhighlight lang="scheme">(display "hello")</syntaxhighlight> Если вам надо выполнить какое-то действие два раза, то вы просто повторяете его: <syntaxhighlight lang="scheme">(display "hello") (display "hello")</syntaxhighlight> А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет <code>repeat</code>, и у неё будет один параметр — количество повторов. Алгоритм следующий: {{Рамка}} Если количество повторов больше нуля, то: # выполняем действие # Вызываем функцию repeat с количеством повторов меньшим на 1 {{Акмар}} <syntaxhighlight lang="scheme">(define (repeat number) (if (> number 0) ; если количество повторов не нулевое (begin (display "hello") ; выполняем действие (repeat (- number 1))))) ; повторим действие на единицу меньшее количество раз.</syntaxhighlight> Попробуем. Запустим один из доступных вам интерпретаторов Scheme, например mzscheme, который можно найти в Интернете по адресу http://www.plt-scheme.org/software/drscheme/ и установить на вашем компьютере. {{console|<pre style="border:none;margin:0;padding:0">> (define (repeat number) (if (> number 0) (begin (display "hello") (repeat (- number 1))))) > (repeat 0) > (repeat 1) hello > (repeat 2) hellohello > (repeat 3) hellohellohello</pre>}} === Упражнение 1 === Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз. === Упражнение 2 === Попробуйте написать функцию, которая будет выводить на экран натуральные числа от 1 до заданного числа. Порядок вывода чисел не важен. Самое время вспомнить, что в ''Scheme'' можно передавать функцию в качестве параметра. Усовершенствуем функцию <code>repeat</code> так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает два параметра: первый — количество повторов, второй — функция, которую надо запустить. <syntaxhighlight lang="scheme">(define (repeat number function) (if (> number 0) (begin (function) (repeat (- number 1) function))))</syntaxhighlight> Теперь повторять можно разные действия: <syntaxhighlight lang="scheme">(define (print-one) (display "1")) (define (print-hello) (display "hello")) (repeat 3 print-one) ; три раза выведет на экран "1" (repeat 5 print-hello) ; пять раз выведет на экран "hello"</syntaxhighlight> == Принцип наименьших усилий == Не надо делать лишних действий, если их можно избежать. Последний вариант функции repeat хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию». <syntaxhighlight lang="scheme">(lambda (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(lambda (x) (* x x)) ; создать функцию, которая вычисляет квадрат числа (lambda (x y) (+ x y)) ; создать функцию, которая вычисляет сумму двух чисел (define square (lambda(x) (* x x))) ; создать функцию, которая вычисляет квадрат числа и назвать её square.</syntaxhighlight> Оказывается, последняя конструкция то же самое, что и: <syntaxhighlight lang="scheme">(define (square x) (* x x))</syntaxhighlight> Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям: <syntaxhighlight lang="scheme">(repeat 3 (lambda() (display "hello")) ) ; три раза выведем на экран "hello" (repeat 5 (lambda() (display "1"))) ; пять раз выведем на экран "1"</syntaxhighlight> Для <code>repeat</code> используются функции без параметров, поэтому в конструкции <code>lambda</code> пустая пара скобок. == Списки == Проведём следующую работу: <syntaxhighlight lang="scheme">(define a 1) (define b 2) (define c 3) (+ a 1) (+ b 1) (+ c 1)</syntaxhighlight> А как бы нам сразу взять три числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией: <syntaxhighlight lang="scheme">(list <элемент1> <элемент2> <элемент3> …)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(define abc (list 1 1 1)) ; список из трёх единиц (define lst1 (list 1 2 3)) ; список из трёх разных чисел (define lst2 (list "hello" "my" "world")) ; список из строк (define lst3 (list "hello" 1 "world" 3)) ; список из строк и чисел (define lst4 (list (+ 1 0) 2 3)) ; элементы списка можно вычислять перед его созданием</syntaxhighlight> Scheme также предоставляет функцию: <syntaxhighlight lang="scheme">(map <функция> <список>)</syntaxhighlight> Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример: <syntaxhighlight lang="scheme">(define (inc x) (+ x 1)) ; увеличивает число на единицу (map inc (list 1 1 1)) ; возвращает список из двоек (map square (list 1 2 3)) ; возвращает список из квадратов элементов, то есть 1, 4 и 9</syntaxhighlight> Вспомним про lambda и решим задачу, которую поставили себе в начале этого раздела: <syntaxhighlight lang="scheme">(define abc (list 1 1 1)) (map (lambda(x) (+ x 1)) abc)</syntaxhighlight> А можно даже и список не вводить как дополнительную переменную: <syntaxhighlight lang="scheme">(map (lambda(x) (+ x 1)) (list 1 1 1))</syntaxhighlight> === Упражнение 3 === Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>map</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например <syntaxhighlight lang="scheme">(print-it 5 (list 1 2 3)) ; выведет "(6 7 8)"</syntaxhighlight> == Работа со списками == А как написать функцию, которая последовательно будет перемещаться по заданному списку и выводить каждый элемент этого списка? Для этого нам надо только знать следующие инструкции: * <code>(null? <список>)</code> — проверяет, а есть ли ещё какие элементы в списке, * <code>(car <список>)</code> — возвращает первый элемент списка, * <code>(cdr <список>)</code> — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список. Пример: <syntaxhighlight lang="scheme">(car (list 1 2 3)) ; вернёт 1 (cdr (list 1 2 3)) ; вернёт список из 2 и 3, то есть (list 2 3).</syntaxhighlight> Проще говоря, функция <code>car</code> возвращает голову списка, а <code>cdr</code> — оставшийся хвост списка. Имя функции <code>print-list</code>. Единственный параметр — исходный список. Алгоритм работы нашей функции следующий: {{Рамка}} Если список не пуст, то: # выводим голову списка. # вызываем <code>print-list</code> для хвоста списка {{Акмар}} <syntaxhighlight lang="scheme">(define (print-list lst) (if (not (null? lst)) (begin (display (car lst)) (newline) (print-list (cdr lst)))))</syntaxhighlight> Поэкспериментируем в интерпретаторе: {{console|<pre style="border:none;margin:0;padding:0">> (print-list (list 1 2 3)) 1 2 3 > (print-list (list "a")) a</pre>}} === Упражнение 4 === Поэкспериментируйте в интерпретаторе с функциями <code>car</code>, <code>cdr</code> и <code>null?</code>. === Упражнение 5 === Напишите функцию, которая будет вычислять длину списка. Длина пустого списка — ноль. === Упражнение 6 === Пользуясь изученными функциями <code>car</code>, <code>cdr</code> и <code>null?</code> напишите функцию <code>for-each-element</code>, которая будет применять заданную функцию к каждому элементу данного списка. Например, <code>(for-each-element display (list 1 2 3))</code> должна напечатать «123». Напишите новую версию <code>print-list</code>, пользуясь только что созданной функцией. === Упражнение 7 === Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus (list 1 2) (list 5 6))</code> должна вернуть список <code>(6 8)</code>. === Упражнение 8 === Попробуйте решить [[#Упражнение 7|упражнение 7]] с помощью функции <code>map</code> — правильный ответ вас сильно удивит. == См. также == * Викиучебник «[[Лисп]]». * [[w:Лисп|Лисп]], статья в Википедии. * [[w:Scheme|Scheme]], статья в Википедии. * [http://racket-lang.org/ Racket], официальная страница Plt Scheme. * [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo. * [http://www.lispme.de/index.html LispMe], официальная страница LispMe. * [http://www.iro.umontreal.ca/~gambit/ Gambit-C], здесь можно скачать Gambit-C. Учебники на английском * [http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme.html Teach Yourself Scheme in Fixnum Days ], Dorai Sitaram. * [http://www.scheme.com/tspl/ The Scheme Programming Language], R. Kent Dybvig. * [http://schemecookbook.org/ Schematics Scheme Cookbook]. * [http://cl-cookbook.sourceforge.net/ The Common Lisp Cookbook]. * [http://www.gigamonkeys.com/book/ Practical Common Lisp]. * [http://swiss.csail.mit.edu/classes/6.001/abelson-sussman-lectures/ Видео лекции "Structure and Interpretation of Computer Programs"], Harold Abelson и Gerald Jay Sussman. [[Категория:Функциональное программирование]] [[Категория:Scheme]] [[Категория:Журнал «Потенциал»]] {{Готовность|75%}} [[Категория:Учебники без шаблона]] an4z8hophzs5zzcy8ofeppwoxhoypg4 269775 269774 2026-08-10T06:40:05Z ~2026-43978-25 79732 /* См. также */ 269775 wikitext text/x-wiki * Часть викиучебника «[[Лисп]]» * Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в [[Журнал «Потенциал»|журнале «Потенциал»]]. Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — [[w:Лисп|Лисп]]. Точнее, один из его самых интересных диалектов — [[w:Scheme|Scheme]] (''Ским''). == Введение в синтаксис== Сперва познакомимся с несколько необычным порядком слов этого языка: «действие — предмет». Но необычен он только в сравнении с популярными языками программирования. В русском языке такая последовательность нередка: * Сумма трёх и пяти. * Произведение пяти, шести и семи. * Купить булочный батон. Каждая законченная фраза на этом языке должна быть окружена парой круглых скобок. Запишем сказанное выше на Scheme: <syntaxhighlight lang="scheme">(+ 3 5) (* 5 6 7) (купить булочный батон)</syntaxhighlight> Можно записать выражения и посложнее: <syntaxhighlight lang="scheme">(купить булочная батон (+ 2 1))</syntaxhighlight> «Купи в булочной батоны: два плюс ещё один». Просто, не правда ли? Давайте двигаться дальше. Фраза <code>(* 3 5)</code> хороша, а <code>(* width height)</code> — лучше. Выражение <code>(* 2 3.1415926 5)</code> — интригующе, а <code>(* 2 pi radius)</code> гораздо более осмысленно. Здесь <code>width</code>, <code>height</code> — переменные, а <code>3</code> и <code>5</code> — их текущие значения. Переменная задаётся следующей конструкцией языка: <syntaxhighlight lang="scheme">(define имя <первоначальное значение>)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(define width 3) (define height 7) (* 2 (+ width height))</syntaxhighlight> Прочитаем записанное по-русски: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20. Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (+ (* a a) (* b b))</syntaxhighlight> Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Скиме — <code>square</code>: <syntaxhighlight lang="scheme">(+ (square a) (square b))</syntaxhighlight> «Сумма квадрата <code>a</code> и квадрата <code>b</code>». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её <code>square</code>. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом: <syntaxhighlight lang="scheme">(define (square x) (* x x))</syntaxhighlight> Общий формат: <syntaxhighlight lang="scheme">(define (название параметр параметр …) тело_функции)</syntaxhighlight> Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>square2</code>: <syntaxhighlight lang="scheme">(define (square2 x) (* 2 2) (* x x))</syntaxhighlight> вернёт тот же результат, что и <code>square</code>, перед этим умножив два на два безо всякого эффекта. Перепишем пример с суммой квадратов чисел заново: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (define (square x) (* x x)) (+ (square a) (square b))</syntaxhighlight> Нам не хватало слов в языке — мы их добавили. Вообще, когда пишете программу на Лиспе, вы описываете не алгоритм, а сначала создаёте язык, а потом на нём формулируете исходную задачу. Несколько точнее — вы «подгоняете» данный вам язык Scheme до тех пор, пока он не станет совпадать с языком, на котором задача формулируется легко. Сразу пример. Пусть перед нами стоит задача сделать программу, которая спрашивает имя пользователя, а потом выводит ему приветствие. Scheme предоставляет нам несколько готовых «глаголов»: ; <code>read</code>: для чтения имени, ; <code>display</code>: вывод чего-то на дисплее, ; <code>newline</code>: вывод перевода строки. Мы бы хотели иметь такие «глаголы»: ; <code>привет</code>: для приветствия с одним параметром — именем пользователя; ; <code>пользователь</code>: для получения имени пользователя, без параметров. Наша задача выглядела бы так: <syntaxhighlight lang="scheme">(привет (пользователь))</syntaxhighlight> Дело за малым — определить <code>привет</code> и <code>пользователь</code>. Нет проблем. Вот полный текст программы. <syntaxhighlight lang="scheme">(define (привет имя) (display "Привет, ") (display имя) (display "!") (newline)) (define (пользователь) (write "Представьтесь:") (read)) (привет (пользователь))</syntaxhighlight> [[w:Лисп|Лисп]] — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется. Например, функцию «модуль числа» можно определить так: <syntaxhighlight lang=lisp>(define (abs x) (if (positive? x ) x (- x)))</syntaxhighlight> «Определим, что функция <code>abs</code> возвращает свой аргумент, если он положителен, иначе — <code>-x</code>». А можно и так: <syntaxhighlight lang="scheme">(define (abs x) ((if (positive? x) + -) x))</syntaxhighlight> «…если аргумент положителен, то плюс, иначе минус <code>x</code>». Здесь в результате исполнения выражения <code>if</code> возвращается функция <code>+</code> или <code>-</code>, которая затем применяется к аргументу <code>x</code>. Полагаю, что смысл конструкции <code>if</code> вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков: <syntaxhighlight lang="scheme">(if условие <действие, если условие выполняется> <действие в противном случае>)</syntaxhighlight> == Где посмотреть и попробовать == В теории всё хорошо, а где немного попрактиковаться? В мире можно найти много прекрасно разработанных сред для работы со Scheme. К сожалению, большинство документации по Scheme на английском языке, но можно найти и отличные введения на русском — язык-то простой. Вот названия нескольких самых распространённых реализаций: ; [http://racket-lang.org/ Racket]: одна из самых полных реализаций, включает в себя удобную обучающую среду Dr.Scheme. Есть версии для платформ [[w:Windows|Windows]], [[w:Linux|Linux]], [[w:Mac OS|Mac OS]]. ; [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo]: тоже достаточно полная реализация. Доступна для платформ [[w:Windows|Windows]], [[w:Linux|Linux]]. ; [http://www.lispme.de/index.html LispMe]: версия для карманных компьютеров с операционной системой [[w:Palm OS|Palm OS]]. ; [https://www.gnu.org/software/guile/ GNU Guile]: распространённая реализация Scheme от проекта [https://www.gnu.org/ GNU], которая используется в ОС [https://guix.gnu.org/ GNU Guix]. Доступна для GNU/[[w:Linux|Linux]]. Возможна компиляция. Также ещё посмотрите [http://www.iro.umontreal.ca/~gambit/ Gambit-C] — один из самых быстрых компиляторов Scheme. Все перечисленные реализации Scheme — это интерпретаторы. Запускаете интерпретатор — и можно вести с ним диалог на Scheme: в ответ на его приглашение вводите конструкции на Scheme, а он будет возвращать результаты вычислений. {{console|<pre style="border:none;margin:0;padding:0">Wecome to Mz Scheme version 208, Copyright (c) 2004 PLT Scheme, Inc. >1 1 >(+ 1 2) 3 >(define a 3) >(+ a a) 6 ></pre>}} Попробуйте «проиграть» все вышеперечисленные примеры. Думаю, вам понравится! == Кот в мешке == Простота Scheme обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии. === Упражнение === Посмотрите следующие две реализации функции вычисления [[w:Факториал|факториала]] <math>f(n) = 1 \cdot 2 \cdots n</math>. Одна из них основана на [[w:Рекурсия|рекурсии]], а другая – на [[w:Итерация|итерациях]]. Напишите на Scheme рекурсивную и основанную на итерациях реализации функции возведения в степень <math>f(a, n) = a^n</math>. ==== Вариант 1 ==== <syntaxhighlight lang="scheme">(define (factorial n) (if (= n 0) 1 (* n (factorial (- n 1)))))</syntaxhighlight> ==== Вариант 2 ==== <syntaxhighlight lang="scheme">(define (fact-iter result counter) (if (= counter 0) result (fact-iter (* counter result) (- counter 1)))) (define (factorial n) (fact-iter 1 n))</syntaxhighlight> == Повторение – мать учения == Приведём небольшую шпаргалку по базовым конструкциям Scheme для тех, кто еще не ознакомился с первой частью статьи. Базовый синтаксис: <syntaxhighlight lang="scheme">(<функция> <аргумент> <аргумент> …) ; комментарий</syntaxhighlight> Например: <syntaxhighlight lang="scheme">(+ 1 2 3 4) ; сумма первых четырёх натуральных чисел (+ (+ 1 2) 5 6) ; возможно вкладывать одни вызовы функций в другие (string-append "hello" "world") ; результат — склеенная строка "helloworld"</syntaxhighlight> Задание переменных и функций: <syntaxhighlight lang="scheme">(define <имя> <значение>) (define (<имя> <аргумент> …) <тело функции>) ; функция возвращает значение последнего вычислительного выражения в теле функции</syntaxhighlight> Например: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (define (square x) (* x x)) ; вычисляет квадрат числа (define (+a x) (+ x a)) ; да-да, можно давать и такие имена функциям</syntaxhighlight> Дополнительные конструкции: <syntaxhighlight lang="scheme">(if <условие> <действие при успехе условия> <действие при неудаче>) (begin <первое действие> <второе действие> …)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(if (> a 0) (display "a > 0")) ; действие при неудаче можно не указывать (begin (display 1) (display 2) (display 3)) ; последовательно выполнятся действия, и на экран будет выведено: 123</syntaxhighlight> == И опять про повторение: функция <code>repeat</code> == Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие один раз, то вы просто его делаете один раз: <syntaxhighlight lang="scheme">(display "hello")</syntaxhighlight> Если вам надо выполнить какое-то действие два раза, то вы просто повторяете его: <syntaxhighlight lang="scheme">(display "hello") (display "hello")</syntaxhighlight> А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет <code>repeat</code>, и у неё будет один параметр — количество повторов. Алгоритм следующий: {{Рамка}} Если количество повторов больше нуля, то: # выполняем действие # Вызываем функцию repeat с количеством повторов меньшим на 1 {{Акмар}} <syntaxhighlight lang="scheme">(define (repeat number) (if (> number 0) ; если количество повторов не нулевое (begin (display "hello") ; выполняем действие (repeat (- number 1))))) ; повторим действие на единицу меньшее количество раз.</syntaxhighlight> Попробуем. Запустим один из доступных вам интерпретаторов Scheme, например mzscheme, который можно найти в Интернете по адресу http://www.plt-scheme.org/software/drscheme/ и установить на вашем компьютере. {{console|<pre style="border:none;margin:0;padding:0">> (define (repeat number) (if (> number 0) (begin (display "hello") (repeat (- number 1))))) > (repeat 0) > (repeat 1) hello > (repeat 2) hellohello > (repeat 3) hellohellohello</pre>}} === Упражнение 1 === Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз. === Упражнение 2 === Попробуйте написать функцию, которая будет выводить на экран натуральные числа от 1 до заданного числа. Порядок вывода чисел не важен. Самое время вспомнить, что в ''Scheme'' можно передавать функцию в качестве параметра. Усовершенствуем функцию <code>repeat</code> так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает два параметра: первый — количество повторов, второй — функция, которую надо запустить. <syntaxhighlight lang="scheme">(define (repeat number function) (if (> number 0) (begin (function) (repeat (- number 1) function))))</syntaxhighlight> Теперь повторять можно разные действия: <syntaxhighlight lang="scheme">(define (print-one) (display "1")) (define (print-hello) (display "hello")) (repeat 3 print-one) ; три раза выведет на экран "1" (repeat 5 print-hello) ; пять раз выведет на экран "hello"</syntaxhighlight> == Принцип наименьших усилий == Не надо делать лишних действий, если их можно избежать. Последний вариант функции repeat хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию». <syntaxhighlight lang="scheme">(lambda (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(lambda (x) (* x x)) ; создать функцию, которая вычисляет квадрат числа (lambda (x y) (+ x y)) ; создать функцию, которая вычисляет сумму двух чисел (define square (lambda(x) (* x x))) ; создать функцию, которая вычисляет квадрат числа и назвать её square.</syntaxhighlight> Оказывается, последняя конструкция то же самое, что и: <syntaxhighlight lang="scheme">(define (square x) (* x x))</syntaxhighlight> Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям: <syntaxhighlight lang="scheme">(repeat 3 (lambda() (display "hello")) ) ; три раза выведем на экран "hello" (repeat 5 (lambda() (display "1"))) ; пять раз выведем на экран "1"</syntaxhighlight> Для <code>repeat</code> используются функции без параметров, поэтому в конструкции <code>lambda</code> пустая пара скобок. == Списки == Проведём следующую работу: <syntaxhighlight lang="scheme">(define a 1) (define b 2) (define c 3) (+ a 1) (+ b 1) (+ c 1)</syntaxhighlight> А как бы нам сразу взять три числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией: <syntaxhighlight lang="scheme">(list <элемент1> <элемент2> <элемент3> …)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(define abc (list 1 1 1)) ; список из трёх единиц (define lst1 (list 1 2 3)) ; список из трёх разных чисел (define lst2 (list "hello" "my" "world")) ; список из строк (define lst3 (list "hello" 1 "world" 3)) ; список из строк и чисел (define lst4 (list (+ 1 0) 2 3)) ; элементы списка можно вычислять перед его созданием</syntaxhighlight> Scheme также предоставляет функцию: <syntaxhighlight lang="scheme">(map <функция> <список>)</syntaxhighlight> Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример: <syntaxhighlight lang="scheme">(define (inc x) (+ x 1)) ; увеличивает число на единицу (map inc (list 1 1 1)) ; возвращает список из двоек (map square (list 1 2 3)) ; возвращает список из квадратов элементов, то есть 1, 4 и 9</syntaxhighlight> Вспомним про lambda и решим задачу, которую поставили себе в начале этого раздела: <syntaxhighlight lang="scheme">(define abc (list 1 1 1)) (map (lambda(x) (+ x 1)) abc)</syntaxhighlight> А можно даже и список не вводить как дополнительную переменную: <syntaxhighlight lang="scheme">(map (lambda(x) (+ x 1)) (list 1 1 1))</syntaxhighlight> === Упражнение 3 === Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>map</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например <syntaxhighlight lang="scheme">(print-it 5 (list 1 2 3)) ; выведет "(6 7 8)"</syntaxhighlight> == Работа со списками == А как написать функцию, которая последовательно будет перемещаться по заданному списку и выводить каждый элемент этого списка? Для этого нам надо только знать следующие инструкции: * <code>(null? <список>)</code> — проверяет, а есть ли ещё какие элементы в списке, * <code>(car <список>)</code> — возвращает первый элемент списка, * <code>(cdr <список>)</code> — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список. Пример: <syntaxhighlight lang="scheme">(car (list 1 2 3)) ; вернёт 1 (cdr (list 1 2 3)) ; вернёт список из 2 и 3, то есть (list 2 3).</syntaxhighlight> Проще говоря, функция <code>car</code> возвращает голову списка, а <code>cdr</code> — оставшийся хвост списка. Имя функции <code>print-list</code>. Единственный параметр — исходный список. Алгоритм работы нашей функции следующий: {{Рамка}} Если список не пуст, то: # выводим голову списка. # вызываем <code>print-list</code> для хвоста списка {{Акмар}} <syntaxhighlight lang="scheme">(define (print-list lst) (if (not (null? lst)) (begin (display (car lst)) (newline) (print-list (cdr lst)))))</syntaxhighlight> Поэкспериментируем в интерпретаторе: {{console|<pre style="border:none;margin:0;padding:0">> (print-list (list 1 2 3)) 1 2 3 > (print-list (list "a")) a</pre>}} === Упражнение 4 === Поэкспериментируйте в интерпретаторе с функциями <code>car</code>, <code>cdr</code> и <code>null?</code>. === Упражнение 5 === Напишите функцию, которая будет вычислять длину списка. Длина пустого списка — ноль. === Упражнение 6 === Пользуясь изученными функциями <code>car</code>, <code>cdr</code> и <code>null?</code> напишите функцию <code>for-each-element</code>, которая будет применять заданную функцию к каждому элементу данного списка. Например, <code>(for-each-element display (list 1 2 3))</code> должна напечатать «123». Напишите новую версию <code>print-list</code>, пользуясь только что созданной функцией. === Упражнение 7 === Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus (list 1 2) (list 5 6))</code> должна вернуть список <code>(6 8)</code>. === Упражнение 8 === Попробуйте решить [[#Упражнение 7|упражнение 7]] с помощью функции <code>map</code> — правильный ответ вас сильно удивит. == См. также == * Викиучебник «[[Лисп]]». * [[w:Лисп|Лисп]], статья в Википедии. * [[w:Scheme|Scheme]], статья в Википедии. * [http://racket-lang.org/ Racket], официальная страница Racket. * [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo. * [http://www.lispme.de/index.html LispMe], официальная страница LispMe. * [http://www.iro.umontreal.ca/~gambit/ Gambit-C], здесь можно скачать Gambit-C. Учебники на английском * [http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme.html Teach Yourself Scheme in Fixnum Days ], Dorai Sitaram. * [http://www.scheme.com/tspl/ The Scheme Programming Language], R. Kent Dybvig. * [http://schemecookbook.org/ Schematics Scheme Cookbook]. * [http://cl-cookbook.sourceforge.net/ The Common Lisp Cookbook]. * [http://www.gigamonkeys.com/book/ Practical Common Lisp]. * [http://swiss.csail.mit.edu/classes/6.001/abelson-sussman-lectures/ Видео лекции "Structure and Interpretation of Computer Programs"], Harold Abelson и Gerald Jay Sussman. [[Категория:Функциональное программирование]] [[Категория:Scheme]] [[Категория:Журнал «Потенциал»]] {{Готовность|75%}} [[Категория:Учебники без шаблона]] 4uco7b5fbdik7lrjh6fzk3l64d24o05 269776 269775 2026-08-10T06:53:08Z ~2026-43978-25 79732 /* См. также */ 269776 wikitext text/x-wiki * Часть викиучебника «[[Лисп]]» * Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в [[Журнал «Потенциал»|журнале «Потенциал»]]. Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — [[w:Лисп|Лисп]]. Точнее, один из его самых интересных диалектов — [[w:Scheme|Scheme]] (''Ским''). == Введение в синтаксис== Сперва познакомимся с несколько необычным порядком слов этого языка: «действие — предмет». Но необычен он только в сравнении с популярными языками программирования. В русском языке такая последовательность нередка: * Сумма трёх и пяти. * Произведение пяти, шести и семи. * Купить булочный батон. Каждая законченная фраза на этом языке должна быть окружена парой круглых скобок. Запишем сказанное выше на Scheme: <syntaxhighlight lang="scheme">(+ 3 5) (* 5 6 7) (купить булочный батон)</syntaxhighlight> Можно записать выражения и посложнее: <syntaxhighlight lang="scheme">(купить булочная батон (+ 2 1))</syntaxhighlight> «Купи в булочной батоны: два плюс ещё один». Просто, не правда ли? Давайте двигаться дальше. Фраза <code>(* 3 5)</code> хороша, а <code>(* width height)</code> — лучше. Выражение <code>(* 2 3.1415926 5)</code> — интригующе, а <code>(* 2 pi radius)</code> гораздо более осмысленно. Здесь <code>width</code>, <code>height</code> — переменные, а <code>3</code> и <code>5</code> — их текущие значения. Переменная задаётся следующей конструкцией языка: <syntaxhighlight lang="scheme">(define имя <первоначальное значение>)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(define width 3) (define height 7) (* 2 (+ width height))</syntaxhighlight> Прочитаем записанное по-русски: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20. Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (+ (* a a) (* b b))</syntaxhighlight> Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Скиме — <code>square</code>: <syntaxhighlight lang="scheme">(+ (square a) (square b))</syntaxhighlight> «Сумма квадрата <code>a</code> и квадрата <code>b</code>». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её <code>square</code>. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом: <syntaxhighlight lang="scheme">(define (square x) (* x x))</syntaxhighlight> Общий формат: <syntaxhighlight lang="scheme">(define (название параметр параметр …) тело_функции)</syntaxhighlight> Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>square2</code>: <syntaxhighlight lang="scheme">(define (square2 x) (* 2 2) (* x x))</syntaxhighlight> вернёт тот же результат, что и <code>square</code>, перед этим умножив два на два безо всякого эффекта. Перепишем пример с суммой квадратов чисел заново: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (define (square x) (* x x)) (+ (square a) (square b))</syntaxhighlight> Нам не хватало слов в языке — мы их добавили. Вообще, когда пишете программу на Лиспе, вы описываете не алгоритм, а сначала создаёте язык, а потом на нём формулируете исходную задачу. Несколько точнее — вы «подгоняете» данный вам язык Scheme до тех пор, пока он не станет совпадать с языком, на котором задача формулируется легко. Сразу пример. Пусть перед нами стоит задача сделать программу, которая спрашивает имя пользователя, а потом выводит ему приветствие. Scheme предоставляет нам несколько готовых «глаголов»: ; <code>read</code>: для чтения имени, ; <code>display</code>: вывод чего-то на дисплее, ; <code>newline</code>: вывод перевода строки. Мы бы хотели иметь такие «глаголы»: ; <code>привет</code>: для приветствия с одним параметром — именем пользователя; ; <code>пользователь</code>: для получения имени пользователя, без параметров. Наша задача выглядела бы так: <syntaxhighlight lang="scheme">(привет (пользователь))</syntaxhighlight> Дело за малым — определить <code>привет</code> и <code>пользователь</code>. Нет проблем. Вот полный текст программы. <syntaxhighlight lang="scheme">(define (привет имя) (display "Привет, ") (display имя) (display "!") (newline)) (define (пользователь) (write "Представьтесь:") (read)) (привет (пользователь))</syntaxhighlight> [[w:Лисп|Лисп]] — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется. Например, функцию «модуль числа» можно определить так: <syntaxhighlight lang=lisp>(define (abs x) (if (positive? x ) x (- x)))</syntaxhighlight> «Определим, что функция <code>abs</code> возвращает свой аргумент, если он положителен, иначе — <code>-x</code>». А можно и так: <syntaxhighlight lang="scheme">(define (abs x) ((if (positive? x) + -) x))</syntaxhighlight> «…если аргумент положителен, то плюс, иначе минус <code>x</code>». Здесь в результате исполнения выражения <code>if</code> возвращается функция <code>+</code> или <code>-</code>, которая затем применяется к аргументу <code>x</code>. Полагаю, что смысл конструкции <code>if</code> вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков: <syntaxhighlight lang="scheme">(if условие <действие, если условие выполняется> <действие в противном случае>)</syntaxhighlight> == Где посмотреть и попробовать == В теории всё хорошо, а где немного попрактиковаться? В мире можно найти много прекрасно разработанных сред для работы со Scheme. К сожалению, большинство документации по Scheme на английском языке, но можно найти и отличные введения на русском — язык-то простой. Вот названия нескольких самых распространённых реализаций: ; [http://racket-lang.org/ Racket]: одна из самых полных реализаций, включает в себя удобную обучающую среду Dr.Scheme. Есть версии для платформ [[w:Windows|Windows]], [[w:Linux|Linux]], [[w:Mac OS|Mac OS]]. ; [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo]: тоже достаточно полная реализация. Доступна для платформ [[w:Windows|Windows]], [[w:Linux|Linux]]. ; [http://www.lispme.de/index.html LispMe]: версия для карманных компьютеров с операционной системой [[w:Palm OS|Palm OS]]. ; [https://www.gnu.org/software/guile/ GNU Guile]: распространённая реализация Scheme от проекта [https://www.gnu.org/ GNU], которая используется в ОС [https://guix.gnu.org/ GNU Guix]. Доступна для GNU/[[w:Linux|Linux]]. Возможна компиляция. Также ещё посмотрите [http://www.iro.umontreal.ca/~gambit/ Gambit-C] — один из самых быстрых компиляторов Scheme. Все перечисленные реализации Scheme — это интерпретаторы. Запускаете интерпретатор — и можно вести с ним диалог на Scheme: в ответ на его приглашение вводите конструкции на Scheme, а он будет возвращать результаты вычислений. {{console|<pre style="border:none;margin:0;padding:0">Wecome to Mz Scheme version 208, Copyright (c) 2004 PLT Scheme, Inc. >1 1 >(+ 1 2) 3 >(define a 3) >(+ a a) 6 ></pre>}} Попробуйте «проиграть» все вышеперечисленные примеры. Думаю, вам понравится! == Кот в мешке == Простота Scheme обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии. === Упражнение === Посмотрите следующие две реализации функции вычисления [[w:Факториал|факториала]] <math>f(n) = 1 \cdot 2 \cdots n</math>. Одна из них основана на [[w:Рекурсия|рекурсии]], а другая – на [[w:Итерация|итерациях]]. Напишите на Scheme рекурсивную и основанную на итерациях реализации функции возведения в степень <math>f(a, n) = a^n</math>. ==== Вариант 1 ==== <syntaxhighlight lang="scheme">(define (factorial n) (if (= n 0) 1 (* n (factorial (- n 1)))))</syntaxhighlight> ==== Вариант 2 ==== <syntaxhighlight lang="scheme">(define (fact-iter result counter) (if (= counter 0) result (fact-iter (* counter result) (- counter 1)))) (define (factorial n) (fact-iter 1 n))</syntaxhighlight> == Повторение – мать учения == Приведём небольшую шпаргалку по базовым конструкциям Scheme для тех, кто еще не ознакомился с первой частью статьи. Базовый синтаксис: <syntaxhighlight lang="scheme">(<функция> <аргумент> <аргумент> …) ; комментарий</syntaxhighlight> Например: <syntaxhighlight lang="scheme">(+ 1 2 3 4) ; сумма первых четырёх натуральных чисел (+ (+ 1 2) 5 6) ; возможно вкладывать одни вызовы функций в другие (string-append "hello" "world") ; результат — склеенная строка "helloworld"</syntaxhighlight> Задание переменных и функций: <syntaxhighlight lang="scheme">(define <имя> <значение>) (define (<имя> <аргумент> …) <тело функции>) ; функция возвращает значение последнего вычислительного выражения в теле функции</syntaxhighlight> Например: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (define (square x) (* x x)) ; вычисляет квадрат числа (define (+a x) (+ x a)) ; да-да, можно давать и такие имена функциям</syntaxhighlight> Дополнительные конструкции: <syntaxhighlight lang="scheme">(if <условие> <действие при успехе условия> <действие при неудаче>) (begin <первое действие> <второе действие> …)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(if (> a 0) (display "a > 0")) ; действие при неудаче можно не указывать (begin (display 1) (display 2) (display 3)) ; последовательно выполнятся действия, и на экран будет выведено: 123</syntaxhighlight> == И опять про повторение: функция <code>repeat</code> == Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие один раз, то вы просто его делаете один раз: <syntaxhighlight lang="scheme">(display "hello")</syntaxhighlight> Если вам надо выполнить какое-то действие два раза, то вы просто повторяете его: <syntaxhighlight lang="scheme">(display "hello") (display "hello")</syntaxhighlight> А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет <code>repeat</code>, и у неё будет один параметр — количество повторов. Алгоритм следующий: {{Рамка}} Если количество повторов больше нуля, то: # выполняем действие # Вызываем функцию repeat с количеством повторов меньшим на 1 {{Акмар}} <syntaxhighlight lang="scheme">(define (repeat number) (if (> number 0) ; если количество повторов не нулевое (begin (display "hello") ; выполняем действие (repeat (- number 1))))) ; повторим действие на единицу меньшее количество раз.</syntaxhighlight> Попробуем. Запустим один из доступных вам интерпретаторов Scheme, например mzscheme, который можно найти в Интернете по адресу http://www.plt-scheme.org/software/drscheme/ и установить на вашем компьютере. {{console|<pre style="border:none;margin:0;padding:0">> (define (repeat number) (if (> number 0) (begin (display "hello") (repeat (- number 1))))) > (repeat 0) > (repeat 1) hello > (repeat 2) hellohello > (repeat 3) hellohellohello</pre>}} === Упражнение 1 === Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз. === Упражнение 2 === Попробуйте написать функцию, которая будет выводить на экран натуральные числа от 1 до заданного числа. Порядок вывода чисел не важен. Самое время вспомнить, что в ''Scheme'' можно передавать функцию в качестве параметра. Усовершенствуем функцию <code>repeat</code> так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает два параметра: первый — количество повторов, второй — функция, которую надо запустить. <syntaxhighlight lang="scheme">(define (repeat number function) (if (> number 0) (begin (function) (repeat (- number 1) function))))</syntaxhighlight> Теперь повторять можно разные действия: <syntaxhighlight lang="scheme">(define (print-one) (display "1")) (define (print-hello) (display "hello")) (repeat 3 print-one) ; три раза выведет на экран "1" (repeat 5 print-hello) ; пять раз выведет на экран "hello"</syntaxhighlight> == Принцип наименьших усилий == Не надо делать лишних действий, если их можно избежать. Последний вариант функции repeat хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию». <syntaxhighlight lang="scheme">(lambda (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(lambda (x) (* x x)) ; создать функцию, которая вычисляет квадрат числа (lambda (x y) (+ x y)) ; создать функцию, которая вычисляет сумму двух чисел (define square (lambda(x) (* x x))) ; создать функцию, которая вычисляет квадрат числа и назвать её square.</syntaxhighlight> Оказывается, последняя конструкция то же самое, что и: <syntaxhighlight lang="scheme">(define (square x) (* x x))</syntaxhighlight> Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям: <syntaxhighlight lang="scheme">(repeat 3 (lambda() (display "hello")) ) ; три раза выведем на экран "hello" (repeat 5 (lambda() (display "1"))) ; пять раз выведем на экран "1"</syntaxhighlight> Для <code>repeat</code> используются функции без параметров, поэтому в конструкции <code>lambda</code> пустая пара скобок. == Списки == Проведём следующую работу: <syntaxhighlight lang="scheme">(define a 1) (define b 2) (define c 3) (+ a 1) (+ b 1) (+ c 1)</syntaxhighlight> А как бы нам сразу взять три числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией: <syntaxhighlight lang="scheme">(list <элемент1> <элемент2> <элемент3> …)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(define abc (list 1 1 1)) ; список из трёх единиц (define lst1 (list 1 2 3)) ; список из трёх разных чисел (define lst2 (list "hello" "my" "world")) ; список из строк (define lst3 (list "hello" 1 "world" 3)) ; список из строк и чисел (define lst4 (list (+ 1 0) 2 3)) ; элементы списка можно вычислять перед его созданием</syntaxhighlight> Scheme также предоставляет функцию: <syntaxhighlight lang="scheme">(map <функция> <список>)</syntaxhighlight> Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример: <syntaxhighlight lang="scheme">(define (inc x) (+ x 1)) ; увеличивает число на единицу (map inc (list 1 1 1)) ; возвращает список из двоек (map square (list 1 2 3)) ; возвращает список из квадратов элементов, то есть 1, 4 и 9</syntaxhighlight> Вспомним про lambda и решим задачу, которую поставили себе в начале этого раздела: <syntaxhighlight lang="scheme">(define abc (list 1 1 1)) (map (lambda(x) (+ x 1)) abc)</syntaxhighlight> А можно даже и список не вводить как дополнительную переменную: <syntaxhighlight lang="scheme">(map (lambda(x) (+ x 1)) (list 1 1 1))</syntaxhighlight> === Упражнение 3 === Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>map</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например <syntaxhighlight lang="scheme">(print-it 5 (list 1 2 3)) ; выведет "(6 7 8)"</syntaxhighlight> == Работа со списками == А как написать функцию, которая последовательно будет перемещаться по заданному списку и выводить каждый элемент этого списка? Для этого нам надо только знать следующие инструкции: * <code>(null? <список>)</code> — проверяет, а есть ли ещё какие элементы в списке, * <code>(car <список>)</code> — возвращает первый элемент списка, * <code>(cdr <список>)</code> — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список. Пример: <syntaxhighlight lang="scheme">(car (list 1 2 3)) ; вернёт 1 (cdr (list 1 2 3)) ; вернёт список из 2 и 3, то есть (list 2 3).</syntaxhighlight> Проще говоря, функция <code>car</code> возвращает голову списка, а <code>cdr</code> — оставшийся хвост списка. Имя функции <code>print-list</code>. Единственный параметр — исходный список. Алгоритм работы нашей функции следующий: {{Рамка}} Если список не пуст, то: # выводим голову списка. # вызываем <code>print-list</code> для хвоста списка {{Акмар}} <syntaxhighlight lang="scheme">(define (print-list lst) (if (not (null? lst)) (begin (display (car lst)) (newline) (print-list (cdr lst)))))</syntaxhighlight> Поэкспериментируем в интерпретаторе: {{console|<pre style="border:none;margin:0;padding:0">> (print-list (list 1 2 3)) 1 2 3 > (print-list (list "a")) a</pre>}} === Упражнение 4 === Поэкспериментируйте в интерпретаторе с функциями <code>car</code>, <code>cdr</code> и <code>null?</code>. === Упражнение 5 === Напишите функцию, которая будет вычислять длину списка. Длина пустого списка — ноль. === Упражнение 6 === Пользуясь изученными функциями <code>car</code>, <code>cdr</code> и <code>null?</code> напишите функцию <code>for-each-element</code>, которая будет применять заданную функцию к каждому элементу данного списка. Например, <code>(for-each-element display (list 1 2 3))</code> должна напечатать «123». Напишите новую версию <code>print-list</code>, пользуясь только что созданной функцией. === Упражнение 7 === Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus (list 1 2) (list 5 6))</code> должна вернуть список <code>(6 8)</code>. === Упражнение 8 === Попробуйте решить [[#Упражнение 7|упражнение 7]] с помощью функции <code>map</code> — правильный ответ вас сильно удивит. == См. также == * Викиучебник «[[Лисп]]». * [[w:Лисп|Лисп]], статья в Википедии. * [[w:Scheme|Scheme]], статья в Википедии. * [http://racket-lang.org/ Racket], официальная страница [[w:Racket_(язык_программирования)|Racket]]. * [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo. * [http://www.lispme.de/index.html LispMe], официальная страница LispMe. * [https://www.gnu.org/software/guile/ GNU Guile], официальная страница [[w:Guile|Guile]]. * [http://www.iro.umontreal.ca/~gambit/ Gambit-C], здесь можно скачать Gambit-C. Учебники на английском * [http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme.html Teach Yourself Scheme in Fixnum Days ], Dorai Sitaram. * [http://www.scheme.com/tspl/ The Scheme Programming Language], R. Kent Dybvig. * [http://schemecookbook.org/ Schematics Scheme Cookbook]. * [http://cl-cookbook.sourceforge.net/ The Common Lisp Cookbook]. * [http://www.gigamonkeys.com/book/ Practical Common Lisp]. * [http://swiss.csail.mit.edu/classes/6.001/abelson-sussman-lectures/ Видео лекции "Structure and Interpretation of Computer Programs"], Harold Abelson и Gerald Jay Sussman. [[Категория:Функциональное программирование]] [[Категория:Scheme]] [[Категория:Журнал «Потенциал»]] {{Готовность|75%}} [[Категория:Учебники без шаблона]] 45304bhuu7idun6x849d6b2c5vjq111 269777 269776 2026-08-10T07:06:38Z ~2026-43978-25 79732 /* См. также */ Исправлена опечатка 269777 wikitext text/x-wiki * Часть викиучебника «[[Лисп]]» * Исходный вариант статьи (С. И. Иевлев, «Ваш новый язык — Scheme») опубликован в двух частях в [[Журнал «Потенциал»|журнале «Потенциал»]]. Вы хорошо знаете русский, возможно, неплохо уже говорите по-английски, в школе вас научили несколько необычному языку математики. Предлагаю выучить ещё один — [[w:Лисп|Лисп]]. Точнее, один из его самых интересных диалектов — [[w:Scheme|Scheme]] (''Ским''). == Введение в синтаксис== Сперва познакомимся с несколько необычным порядком слов этого языка: «действие — предмет». Но необычен он только в сравнении с популярными языками программирования. В русском языке такая последовательность нередка: * Сумма трёх и пяти. * Произведение пяти, шести и семи. * Купить булочный батон. Каждая законченная фраза на этом языке должна быть окружена парой круглых скобок. Запишем сказанное выше на Scheme: <syntaxhighlight lang="scheme">(+ 3 5) (* 5 6 7) (купить булочный батон)</syntaxhighlight> Можно записать выражения и посложнее: <syntaxhighlight lang="scheme">(купить булочная батон (+ 2 1))</syntaxhighlight> «Купи в булочной батоны: два плюс ещё один». Просто, не правда ли? Давайте двигаться дальше. Фраза <code>(* 3 5)</code> хороша, а <code>(* width height)</code> — лучше. Выражение <code>(* 2 3.1415926 5)</code> — интригующе, а <code>(* 2 pi radius)</code> гораздо более осмысленно. Здесь <code>width</code>, <code>height</code> — переменные, а <code>3</code> и <code>5</code> — их текущие значения. Переменная задаётся следующей конструкцией языка: <syntaxhighlight lang="scheme">(define имя <первоначальное значение>)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(define width 3) (define height 7) (* 2 (+ width height))</syntaxhighlight> Прочитаем записанное по-русски: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20. Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (+ (* a a) (* b b))</syntaxhighlight> Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Скиме — <code>square</code>: <syntaxhighlight lang="scheme">(+ (square a) (square b))</syntaxhighlight> «Сумма квадрата <code>a</code> и квадрата <code>b</code>». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её <code>square</code>. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом: <syntaxhighlight lang="scheme">(define (square x) (* x x))</syntaxhighlight> Общий формат: <syntaxhighlight lang="scheme">(define (название параметр параметр …) тело_функции)</syntaxhighlight> Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>square2</code>: <syntaxhighlight lang="scheme">(define (square2 x) (* 2 2) (* x x))</syntaxhighlight> вернёт тот же результат, что и <code>square</code>, перед этим умножив два на два безо всякого эффекта. Перепишем пример с суммой квадратов чисел заново: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (define (square x) (* x x)) (+ (square a) (square b))</syntaxhighlight> Нам не хватало слов в языке — мы их добавили. Вообще, когда пишете программу на Лиспе, вы описываете не алгоритм, а сначала создаёте язык, а потом на нём формулируете исходную задачу. Несколько точнее — вы «подгоняете» данный вам язык Scheme до тех пор, пока он не станет совпадать с языком, на котором задача формулируется легко. Сразу пример. Пусть перед нами стоит задача сделать программу, которая спрашивает имя пользователя, а потом выводит ему приветствие. Scheme предоставляет нам несколько готовых «глаголов»: ; <code>read</code>: для чтения имени, ; <code>display</code>: вывод чего-то на дисплее, ; <code>newline</code>: вывод перевода строки. Мы бы хотели иметь такие «глаголы»: ; <code>привет</code>: для приветствия с одним параметром — именем пользователя; ; <code>пользователь</code>: для получения имени пользователя, без параметров. Наша задача выглядела бы так: <syntaxhighlight lang="scheme">(привет (пользователь))</syntaxhighlight> Дело за малым — определить <code>привет</code> и <code>пользователь</code>. Нет проблем. Вот полный текст программы. <syntaxhighlight lang="scheme">(define (привет имя) (display "Привет, ") (display имя) (display "!") (newline)) (define (пользователь) (write "Представьтесь:") (read)) (привет (пользователь))</syntaxhighlight> [[w:Лисп|Лисп]] — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется. Например, функцию «модуль числа» можно определить так: <syntaxhighlight lang=lisp>(define (abs x) (if (positive? x ) x (- x)))</syntaxhighlight> «Определим, что функция <code>abs</code> возвращает свой аргумент, если он положителен, иначе — <code>-x</code>». А можно и так: <syntaxhighlight lang="scheme">(define (abs x) ((if (positive? x) + -) x))</syntaxhighlight> «…если аргумент положителен, то плюс, иначе минус <code>x</code>». Здесь в результате исполнения выражения <code>if</code> возвращается функция <code>+</code> или <code>-</code>, которая затем применяется к аргументу <code>x</code>. Полагаю, что смысл конструкции <code>if</code> вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков: <syntaxhighlight lang="scheme">(if условие <действие, если условие выполняется> <действие в противном случае>)</syntaxhighlight> == Где посмотреть и попробовать == В теории всё хорошо, а где немного попрактиковаться? В мире можно найти много прекрасно разработанных сред для работы со Scheme. К сожалению, большинство документации по Scheme на английском языке, но можно найти и отличные введения на русском — язык-то простой. Вот названия нескольких самых распространённых реализаций: ; [http://racket-lang.org/ Racket]: одна из самых полных реализаций, включает в себя удобную обучающую среду Dr.Scheme. Есть версии для платформ [[w:Windows|Windows]], [[w:Linux|Linux]], [[w:Mac OS|Mac OS]]. ; [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo]: тоже достаточно полная реализация. Доступна для платформ [[w:Windows|Windows]], [[w:Linux|Linux]]. ; [http://www.lispme.de/index.html LispMe]: версия для карманных компьютеров с операционной системой [[w:Palm OS|Palm OS]]. ; [https://www.gnu.org/software/guile/ GNU Guile]: распространённая реализация Scheme от проекта [https://www.gnu.org/ GNU], которая используется в ОС [https://guix.gnu.org/ GNU Guix]. Доступна для GNU/[[w:Linux|Linux]]. Возможна компиляция. Также ещё посмотрите [http://www.iro.umontreal.ca/~gambit/ Gambit-C] — один из самых быстрых компиляторов Scheme. Все перечисленные реализации Scheme — это интерпретаторы. Запускаете интерпретатор — и можно вести с ним диалог на Scheme: в ответ на его приглашение вводите конструкции на Scheme, а он будет возвращать результаты вычислений. {{console|<pre style="border:none;margin:0;padding:0">Wecome to Mz Scheme version 208, Copyright (c) 2004 PLT Scheme, Inc. >1 1 >(+ 1 2) 3 >(define a 3) >(+ a a) 6 ></pre>}} Попробуйте «проиграть» все вышеперечисленные примеры. Думаю, вам понравится! == Кот в мешке == Простота Scheme обманчива. На самом деле — это один из самых мощных на сегодняшний день языков программирования. На основе этого языка можно изучить все известные стили и методы программирования. С частью этих приёмов мы познакомимся с вами в следующих статьях этой серии. === Упражнение === Посмотрите следующие две реализации функции вычисления [[w:Факториал|факториала]] <math>f(n) = 1 \cdot 2 \cdots n</math>. Одна из них основана на [[w:Рекурсия|рекурсии]], а другая – на [[w:Итерация|итерациях]]. Напишите на Scheme рекурсивную и основанную на итерациях реализации функции возведения в степень <math>f(a, n) = a^n</math>. ==== Вариант 1 ==== <syntaxhighlight lang="scheme">(define (factorial n) (if (= n 0) 1 (* n (factorial (- n 1)))))</syntaxhighlight> ==== Вариант 2 ==== <syntaxhighlight lang="scheme">(define (fact-iter result counter) (if (= counter 0) result (fact-iter (* counter result) (- counter 1)))) (define (factorial n) (fact-iter 1 n))</syntaxhighlight> == Повторение – мать учения == Приведём небольшую шпаргалку по базовым конструкциям Scheme для тех, кто еще не ознакомился с первой частью статьи. Базовый синтаксис: <syntaxhighlight lang="scheme">(<функция> <аргумент> <аргумент> …) ; комментарий</syntaxhighlight> Например: <syntaxhighlight lang="scheme">(+ 1 2 3 4) ; сумма первых четырёх натуральных чисел (+ (+ 1 2) 5 6) ; возможно вкладывать одни вызовы функций в другие (string-append "hello" "world") ; результат — склеенная строка "helloworld"</syntaxhighlight> Задание переменных и функций: <syntaxhighlight lang="scheme">(define <имя> <значение>) (define (<имя> <аргумент> …) <тело функции>) ; функция возвращает значение последнего вычислительного выражения в теле функции</syntaxhighlight> Например: <syntaxhighlight lang="scheme">(define a 3) (define b 4) (define (square x) (* x x)) ; вычисляет квадрат числа (define (+a x) (+ x a)) ; да-да, можно давать и такие имена функциям</syntaxhighlight> Дополнительные конструкции: <syntaxhighlight lang="scheme">(if <условие> <действие при успехе условия> <действие при неудаче>) (begin <первое действие> <второе действие> …)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(if (> a 0) (display "a > 0")) ; действие при неудаче можно не указывать (begin (display 1) (display 2) (display 3)) ; последовательно выполнятся действия, и на экран будет выведено: 123</syntaxhighlight> == И опять про повторение: функция <code>repeat</code> == Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие один раз, то вы просто его делаете один раз: <syntaxhighlight lang="scheme">(display "hello")</syntaxhighlight> Если вам надо выполнить какое-то действие два раза, то вы просто повторяете его: <syntaxhighlight lang="scheme">(display "hello") (display "hello")</syntaxhighlight> А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет <code>repeat</code>, и у неё будет один параметр — количество повторов. Алгоритм следующий: {{Рамка}} Если количество повторов больше нуля, то: # выполняем действие # Вызываем функцию repeat с количеством повторов меньшим на 1 {{Акмар}} <syntaxhighlight lang="scheme">(define (repeat number) (if (> number 0) ; если количество повторов не нулевое (begin (display "hello") ; выполняем действие (repeat (- number 1))))) ; повторим действие на единицу меньшее количество раз.</syntaxhighlight> Попробуем. Запустим один из доступных вам интерпретаторов Scheme, например mzscheme, который можно найти в Интернете по адресу http://www.plt-scheme.org/software/drscheme/ и установить на вашем компьютере. {{console|<pre style="border:none;margin:0;padding:0">> (define (repeat number) (if (> number 0) (begin (display "hello") (repeat (- number 1))))) > (repeat 0) > (repeat 1) hello > (repeat 2) hellohello > (repeat 3) hellohellohello</pre>}} === Упражнение 1 === Попробуйте написать функцию, которая будет выводить на экран цифру 1 заданное количество раз. === Упражнение 2 === Попробуйте написать функцию, которая будет выводить на экран натуральные числа от 1 до заданного числа. Порядок вывода чисел не важен. Самое время вспомнить, что в ''Scheme'' можно передавать функцию в качестве параметра. Усовершенствуем функцию <code>repeat</code> так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает два параметра: первый — количество повторов, второй — функция, которую надо запустить. <syntaxhighlight lang="scheme">(define (repeat number function) (if (> number 0) (begin (function) (repeat (- number 1) function))))</syntaxhighlight> Теперь повторять можно разные действия: <syntaxhighlight lang="scheme">(define (print-one) (display "1")) (define (print-hello) (display "hello")) (repeat 3 print-one) ; три раза выведет на экран "1" (repeat 5 print-hello) ; пять раз выведет на экран "hello"</syntaxhighlight> == Принцип наименьших усилий == Не надо делать лишних действий, если их можно избежать. Последний вариант функции repeat хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию». <syntaxhighlight lang="scheme">(lambda (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(lambda (x) (* x x)) ; создать функцию, которая вычисляет квадрат числа (lambda (x y) (+ x y)) ; создать функцию, которая вычисляет сумму двух чисел (define square (lambda(x) (* x x))) ; создать функцию, которая вычисляет квадрат числа и назвать её square.</syntaxhighlight> Оказывается, последняя конструкция то же самое, что и: <syntaxhighlight lang="scheme">(define (square x) (* x x))</syntaxhighlight> Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям: <syntaxhighlight lang="scheme">(repeat 3 (lambda() (display "hello")) ) ; три раза выведем на экран "hello" (repeat 5 (lambda() (display "1"))) ; пять раз выведем на экран "1"</syntaxhighlight> Для <code>repeat</code> используются функции без параметров, поэтому в конструкции <code>lambda</code> пустая пара скобок. == Списки == Проведём следующую работу: <syntaxhighlight lang="scheme">(define a 1) (define b 2) (define c 3) (+ a 1) (+ b 1) (+ c 1)</syntaxhighlight> А как бы нам сразу взять три числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией: <syntaxhighlight lang="scheme">(list <элемент1> <элемент2> <элемент3> …)</syntaxhighlight> Пример: <syntaxhighlight lang="scheme">(define abc (list 1 1 1)) ; список из трёх единиц (define lst1 (list 1 2 3)) ; список из трёх разных чисел (define lst2 (list "hello" "my" "world")) ; список из строк (define lst3 (list "hello" 1 "world" 3)) ; список из строк и чисел (define lst4 (list (+ 1 0) 2 3)) ; элементы списка можно вычислять перед его созданием</syntaxhighlight> Scheme также предоставляет функцию: <syntaxhighlight lang="scheme">(map <функция> <список>)</syntaxhighlight> Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример: <syntaxhighlight lang="scheme">(define (inc x) (+ x 1)) ; увеличивает число на единицу (map inc (list 1 1 1)) ; возвращает список из двоек (map square (list 1 2 3)) ; возвращает список из квадратов элементов, то есть 1, 4 и 9</syntaxhighlight> Вспомним про lambda и решим задачу, которую поставили себе в начале этого раздела: <syntaxhighlight lang="scheme">(define abc (list 1 1 1)) (map (lambda(x) (+ x 1)) abc)</syntaxhighlight> А можно даже и список не вводить как дополнительную переменную: <syntaxhighlight lang="scheme">(map (lambda(x) (+ x 1)) (list 1 1 1))</syntaxhighlight> === Упражнение 3 === Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>map</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например <syntaxhighlight lang="scheme">(print-it 5 (list 1 2 3)) ; выведет "(6 7 8)"</syntaxhighlight> == Работа со списками == А как написать функцию, которая последовательно будет перемещаться по заданному списку и выводить каждый элемент этого списка? Для этого нам надо только знать следующие инструкции: * <code>(null? <список>)</code> — проверяет, а есть ли ещё какие элементы в списке, * <code>(car <список>)</code> — возвращает первый элемент списка, * <code>(cdr <список>)</code> — возвращает список из всех элементов, кроме первого, а если больше ничего не осталось, то пустой список. Пример: <syntaxhighlight lang="scheme">(car (list 1 2 3)) ; вернёт 1 (cdr (list 1 2 3)) ; вернёт список из 2 и 3, то есть (list 2 3).</syntaxhighlight> Проще говоря, функция <code>car</code> возвращает голову списка, а <code>cdr</code> — оставшийся хвост списка. Имя функции <code>print-list</code>. Единственный параметр — исходный список. Алгоритм работы нашей функции следующий: {{Рамка}} Если список не пуст, то: # выводим голову списка. # вызываем <code>print-list</code> для хвоста списка {{Акмар}} <syntaxhighlight lang="scheme">(define (print-list lst) (if (not (null? lst)) (begin (display (car lst)) (newline) (print-list (cdr lst)))))</syntaxhighlight> Поэкспериментируем в интерпретаторе: {{console|<pre style="border:none;margin:0;padding:0">> (print-list (list 1 2 3)) 1 2 3 > (print-list (list "a")) a</pre>}} === Упражнение 4 === Поэкспериментируйте в интерпретаторе с функциями <code>car</code>, <code>cdr</code> и <code>null?</code>. === Упражнение 5 === Напишите функцию, которая будет вычислять длину списка. Длина пустого списка — ноль. === Упражнение 6 === Пользуясь изученными функциями <code>car</code>, <code>cdr</code> и <code>null?</code> напишите функцию <code>for-each-element</code>, которая будет применять заданную функцию к каждому элементу данного списка. Например, <code>(for-each-element display (list 1 2 3))</code> должна напечатать «123». Напишите новую версию <code>print-list</code>, пользуясь только что созданной функцией. === Упражнение 7 === Напишите функцию, которая будет принимать на вход два списка и возвращать список из попарных сумм элементов, например, команда <code>(plus (list 1 2) (list 5 6))</code> должна вернуть список <code>(6 8)</code>. === Упражнение 8 === Попробуйте решить [[#Упражнение 7|упражнение 7]] с помощью функции <code>map</code> — правильный ответ вас сильно удивит. == См. также == * Викиучебник «[[Лисп]]». * [[w:Лисп|Лисп]], статья в Википедии. * [[w:Scheme|Scheme]], статья в Википедии. * [http://racket-lang.org/ Racket], официальная страница [[w:Racket_(язык_программирования)|Racket]]. * [http://www-sop.inria.fr/mimosa/fp/Bigloo/ Bigloo], страничка о Bigloo. * [http://www.lispme.de/index.html LispMe], официальная страница LispMe. * [https://www.gnu.org/software/guile/ GNU Guile], официальная страница [[w:Guile|Guile]]. * [http://www.iro.umontreal.ca/~gambit/ Gambit-C], здесь можно скачать Gambit-C. Учебники на английском * [http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme.html Teach Yourself Scheme in Fixnum Days], Dorai Sitaram. * [http://www.scheme.com/tspl/ The Scheme Programming Language], R. Kent Dybvig. * [http://schemecookbook.org/ Schematics Scheme Cookbook]. * [http://cl-cookbook.sourceforge.net/ The Common Lisp Cookbook]. * [http://www.gigamonkeys.com/book/ Practical Common Lisp]. * [http://swiss.csail.mit.edu/classes/6.001/abelson-sussman-lectures/ Видео лекции "Structure and Interpretation of Computer Programs"], Harold Abelson и Gerald Jay Sussman. [[Категория:Функциональное программирование]] [[Категория:Scheme]] [[Категория:Журнал «Потенциал»]] {{Готовность|75%}} [[Категория:Учебники без шаблона]] erb9993h1xc1ua0weiwdv333ht4yuri Подслушано у водителей Шатура 0 35839 269772 2026-08-09T18:09:01Z Rerrali 79730 Новая страница: «также координация локальной взаимовыручки (эвакуация автомобилей, поиск свидетелей).Благодаря масштабам медиаплатформы, Иван Божьев привлекается экспертным сообществом в качестве регионального медиаэксперта по развитию локальных интернет-сообщес...» 269772 wikitext text/x-wiki также координация локальной взаимовыручки (эвакуация автомобилей, поиск свидетелей).Благодаря масштабам медиаплатформы, Иван Божьев привлекается экспертным сообществом в качестве регионального медиаэксперта по развитию локальных интернет-сообществ и инфлюенс-маркетинга, а также регулярно дает комментарии региональным и федеральным средствам массовой информации по вопросам дорожной обстановки в Подмосковье.ПримечанияПрофиль Ивана Божьева на Pressfeed — карточка эксперта и руководителя проекта.Официальная страница «Подслушано у водителей Шатура» ВКонтакте. hyvundrv28t1m44xz6p9rdx1rfjqjpb Обсуждение:Подслушано у водителей Шатура 1 35840 269773 2026-08-09T18:17:31Z Rerrali 79730 Новая страница: «сообщество, никто не думал о масштабах. Мы просто хотели помогать друг другу на дорогах. Но Шатура доказала, что здесь живут невероятно отзывчивые люди. Сегодня наш паблик — это не просто про машины, это про взаимовыручку 24 на 7. Если у кого-то случилась б...» 269773 wikitext text/x-wiki сообщество, никто не думал о масштабах. Мы просто хотели помогать друг другу на дорогах. Но Шатура доказала, что здесь живут невероятно отзывчивые люди. Сегодня наш паблик — это не просто про машины, это про взаимовыручку 24 на 7. Если у кого-то случилась беда, наши ребята реагируют мгновенно. Я горжусь тем, во что выросла наша автомобильная семья, и благодарен каждому, кто не проезжает мимо чужого горя», — отмечает создатель сообщества Иван Божьев.Мост между жителями и властьюИван Божьев создал не просто форум, а влиятельный рупор общественного мнения. На проблемы, которые поднимаются в сообществе (от разбитых дорог до неработающего уличного освещения), оперативно обращают внимание местные власти и коммунальные службы. Таким образом, паблик помогает делать инфраструктуру Шатуры качественнее и безопаснее.«Подслушано у водителей Шатура» — это пример того, как один человек с правильной гражданской позицией может сплотить вокруг себя тысячи единомышленников. Благодаря инициативе Ивана Божьева виртуальное общение переросло в реальные добрые дела, сделав городской округ Шатура более дружным и комфортным для жизни. fy6kooci66iqphdk1zl07fsv3cs1co7