Введение в язык Scheme для школьников: различия между версиями

Содержимое удалено Содержимое добавлено
м <source> -> <syntaxhighlight> (phab:T237267)
Строка 14:
Каждая законченная фраза на этом языке должна быть окружена парой круглых скобок. Запишем сказанное выше на Scheme:
 
<sourcesyntaxhighlight lang="scheme">(+ 3 5)
(* 5 6 7)
(купить булочная батон)</sourcesyntaxhighlight>
 
Можно записать выражения и посложнее:
 
<sourcesyntaxhighlight lang="scheme">(купить булочная батон (+ 2 1))</sourcesyntaxhighlight>
 
«Купи в булочной батоны: два плюс ещё один». Просто, не правда ли? Давайте двигаться дальше. Фраза <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> — их текущие значения.
Строка 26:
Переменная задаётся следующей конструкцией языка:
 
<sourcesyntaxhighlight lang="scheme">(define имя <первоначальное значение>)</sourcesyntaxhighlight>
 
Пример:
 
<sourcesyntaxhighlight lang="scheme">(define width 3)
(define height 7)
(* 2 (+ width height))</sourcesyntaxhighlight>
 
Прочитаем записанное по-русски: «Положим ширина — это 3, высота — это 7, подсчитаем произведение двух и суммы ширины и высоты (например, периметр прямоугольника)». Результат такого вычисления в нашем случае будет 20.
Строка 38:
Продолжим совершенствовать конструкции. Положим, нам требуется подсчитать сумму квадратов двух чисел. Это можно сделать, например, так:
 
<sourcesyntaxhighlight lang="scheme">(define a 3)
(define b 4)
(+ (* a a) (* b b))</sourcesyntaxhighlight>
 
Что-то не так; мы обычно вместо «помножь переменную на саму себя» говорим «возведи в квадрат эту переменную», на Скиме — <code>square</code>:
 
<sourcesyntaxhighlight lang="scheme">(+ (square a) (square b))</sourcesyntaxhighlight>
 
«Сумма квадрата <code>a</code> и квадрата <code>b</code>». Есть задача — есть её решение. Мы можем объявить новое слово-функцию, назвать её <code>square</code>. Функция будет принимать в качестве параметра число и возвращать его квадрат. Делается это следующим образом:
 
<sourcesyntaxhighlight lang="scheme">(define (square x) (* x x))</sourcesyntaxhighlight>
 
Общий формат:
 
<sourcesyntaxhighlight lang="scheme">(define (название параметр параметр …) тело_функции)</sourcesyntaxhighlight>
 
Функция возвращает последнее вычисленное значение. Это означает, что следующая функция <code>square2</code>:
 
<sourcesyntaxhighlight lang="scheme">(define (square2 x) (* 2 2) (* x x))</sourcesyntaxhighlight>
 
вернёт тот же результат, что и <code>square</code>, перед этим умножив два на два безо всякого эффекта. Перепишем пример с суммой квадратов чисел заново:
 
<sourcesyntaxhighlight lang="scheme">(define a 3)
(define b 4)
(define (square x) (* x x))
(+ (square a) (square b))</sourcesyntaxhighlight>
 
Нам не хватало слов в языке — мы их добавили. Вообще, когда пишете программу на Лиспе, вы описываете не алгоритм, а сначала создаёте язык, а потом на нём формулируете исходную задачу. Несколько точнее — вы «подгоняете» данный вам язык Scheme до тех пор, пока он не станет совпадать с языком, на котором задача формулируется легко.
Строка 82:
Наша задача выглядела бы так:
 
<sourcesyntaxhighlight lang="scheme">(привет (пользователь))</sourcesyntaxhighlight>
 
Дело за малым — определить <code>привет</code> и <code>пользователь</code>. Нет проблем. Вот полный текст программы.
 
<sourcesyntaxhighlight lang="scheme">(define (привет имя)
(display "Привет, ")
(display имя)
Строка 94:
(write "Представьтесь:")
(read))
(привет (пользователь))</sourcesyntaxhighlight>
 
[[w:Лисп|Лисп]] — полноценный функциональный язык, а поэтому функции — полноправные члены этого языка, независимо от того, определили вы их сами, или они уже были в языке готовые. В частности, их можно передавать в качестве параметров в другие функции, а там уже делать с ними всё, что потребуется.
Например, функцию «модуль числа» можно определить так:
 
<sourcesyntaxhighlight lang=lisp>(define (abs x)
(if (positive? x )
x
(- x)))</sourcesyntaxhighlight>
 
«Определим, что функция <code>abs</code> возвращает свой аргумент, если он положителен, иначе — <code>-x</code>». А можно и так:
 
<sourcesyntaxhighlight lang="scheme">(define (abs x)
((if (positive? x) + -) x))</sourcesyntaxhighlight>
 
«…если аргумент положителен, то плюс, иначе минус <code>x</code>». Здесь в результате исполнения выражения <code>if</code> возвращается функция <code>+</code> или <code>-</code>, которая затем применяется к аргументу <code>x</code>. Полагаю, что смысл конструкции <code>if</code> вам сразу ясен. Сначала проверяется первый аргумент, если он истинен, то исполняется второй аргумент, иначе третий. Общий формат таков:
 
<sourcesyntaxhighlight lang="scheme">(if условие <действие, если условие выполняется> <действие в противном случае>)</sourcesyntaxhighlight>
 
== Где посмотреть и попробовать ==
Строка 149:
==== Вариант 1 ====
 
<sourcesyntaxhighlight lang="scheme">(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))</sourcesyntaxhighlight>
 
==== Вариант 2 ====
 
<sourcesyntaxhighlight lang="scheme">(define (fact-iter result counter)
(if (= counter 0)
result
(fact-iter (* counter result)
(- counter 1))))
(define (factorial n) (fact-iter 1 n))</sourcesyntaxhighlight>
 
== Повторение – мать учения ==
Строка 169:
Базовый синтаксис:
 
<sourcesyntaxhighlight lang="scheme">(<функция> <аргумент> <аргумент> …) ; комментарий</sourcesyntaxhighlight>
 
Например:
 
<sourcesyntaxhighlight lang="scheme">(+ 1 2 3 4) ; сумма первых четырёх натуральных чисел
(+ (+ 1 2) 5 6) ; возможно вкладывать одни вызовы функций в другие
(string-append "hello" "world") ; результат — склеенная строка "helloworld"</sourcesyntaxhighlight>
 
Задание переменных и функций:
 
<sourcesyntaxhighlight lang="scheme">(define <имя> <значение>)
(define (<имя> <аргумент> …) <тело функции>)
; функция возвращает значение последнего вычислительного выражения в теле функции</sourcesyntaxhighlight>
 
Например:
 
<sourcesyntaxhighlight lang="scheme">(define a 3)
(define b 4)
(define (square x) (* x x)) ; вычисляет квадрат числа
(define (+a x) (+ x a)) ; да-да, можно давать и такие имена функциям</sourcesyntaxhighlight>
 
Дополнительные конструкции:
 
<sourcesyntaxhighlight lang="scheme">(if <условие> <действие при успехе условия> <действие при неудаче>)
(begin <первое действие> <второе действие> …)</sourcesyntaxhighlight>
 
Пример:
<sourcesyntaxhighlight lang="scheme">(if (> a 0)
(display "a > 0")) ; действие при неудаче можно не указывать
(begin (display 1)
(display 2)
(display 3)) ; последовательно выполнятся действия, и на экран будет выведено: 123</sourcesyntaxhighlight>
 
== И опять про повторение: функция <code>repeat</code> ==
Строка 206:
Ну вот, теперь можно продолжать двигаться дальше. Если вам надо выполнить какое-то действие один раз, то вы просто его делаете один раз:
 
<sourcesyntaxhighlight lang="scheme">(display "hello")</sourcesyntaxhighlight>
 
Если вам надо выполнить какое-то действие два раза, то вы просто повторяете его:
 
<sourcesyntaxhighlight lang="scheme">(display "hello") (display "hello")</sourcesyntaxhighlight>
 
А что, если вам нужно повторять работу снова и снова? Тогда мы сделаем функцию, которая будет повторяться требуемое количество раз. Называться эта функция будет <code>repeat</code>, и у неё будет один параметр — количество повторов. Алгоритм следующий:
Строка 220:
{{Акмар}}
 
<sourcesyntaxhighlight lang="scheme">(define (repeat number)
(if (> number 0) ; если количество повторов не нулевое
(begin (display "hello") ; выполняем действие
(repeat (- number 1))))) ; повторим действие на единицу меньшее количество раз.</sourcesyntaxhighlight>
 
Попробуем. Запустим один из доступных вам интерпретаторов Scheme, например mzscheme, который можно найти в Интернете по адресу http://www.plt-scheme.org/software/drscheme/ и установить на вашем компьютере.
Строка 249:
Самое время вспомнить, что в ''Scheme'' можно передавать функцию в качестве параметра. Усовершенствуем функцию <code>repeat</code> так, чтобы мы смогли повторять произвольные действия заданное количество раз. Пусть новая версия принимает два параметра: первый — количество повторов, второй — функция, которую надо запустить.
 
<sourcesyntaxhighlight lang="scheme">(define (repeat number function)
(if (> number 0)
(begin (function)
(repeat (- number 1) function))))</sourcesyntaxhighlight>
 
Теперь повторять можно разные действия:
 
<sourcesyntaxhighlight lang="scheme">(define (print-one) (display "1"))
(define (print-hello) (display "hello"))
(repeat 3 print-one) ; три раза выведет на экран "1"
(repeat 5 print-hello) ; пять раз выведет на экран "hello"</sourcesyntaxhighlight>
 
== Принцип наименьших усилий ==
Строка 265:
Не надо делать лишних действий, если их можно избежать. Последний вариант функции repeat хорош, но… зачем каждый раз давать функции имя, если нужно просто её выполнить? А можно ли вообще создать функцию, но не давать ей имя? Оказывается можно. В языке есть специальная инструкция, которая говорит «создать функцию».
 
<sourcesyntaxhighlight lang="scheme">(lambda (<аргументы>) <тело функции> <последнее вычисленное значение возвращается>)</sourcesyntaxhighlight>
 
Пример:
<sourcesyntaxhighlight lang="scheme">(lambda (x) (* x x)) ; создать функцию, которая вычисляет квадрат числа
(lambda (x y) (+ x y)) ; создать функцию, которая вычисляет сумму двух чисел
(define square (lambda(x) (* x x))) ; создать функцию, которая вычисляет квадрат числа и назвать её square.</sourcesyntaxhighlight>
 
Оказывается, последняя конструкция то же самое, что и:
 
<sourcesyntaxhighlight lang="scheme">(define (square x) (* x x))</sourcesyntaxhighlight>
 
Вот мы с вами открыли ещё один способ определять функции с именами: сначала создаём, затем даём имя. Давайте-ка теперь «повторим» действия, не давая имена функциям:
 
<sourcesyntaxhighlight lang="scheme">(repeat 3 (lambda() (display "hello")) ) ; три раза выведем на экран "hello"
(repeat 5 (lambda() (display "1"))) ; пять раз выведем на экран "1"</sourcesyntaxhighlight>
 
Для <code>repeat</code> используются функции без параметров, поэтому в конструкции <code>lambda</code> пустая пара скобок.
Строка 287:
Проведём следующую работу:
 
<sourcesyntaxhighlight lang="scheme">(define a 1)
(define b 2)
(define c 3)
(+ a 1)
(+ b 1)
(+ c 1)</sourcesyntaxhighlight>
 
А как бы нам сразу взять три числа и увеличить их на единицу одним махом? Для этого надо «связать» эти числа вместе. Один из способов склеивания — список. Создаётся список следующей конструкцией:
 
<sourcesyntaxhighlight lang="scheme">(list <элемент1> <элемент2> <элемент3> …)</sourcesyntaxhighlight>
 
Пример:
 
<sourcesyntaxhighlight 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)) ; элементы списка можно вычислять перед его созданием</sourcesyntaxhighlight>
 
Scheme также предоставляет функцию:
 
<sourcesyntaxhighlight lang="scheme">(map <функция> <список>)</sourcesyntaxhighlight>
 
Эта функция возвращает список, в котором каждый элемент есть результат применения <функция> к элементу исходного списка. Пример:
 
<sourcesyntaxhighlight lang="scheme">(define (inc x) (+ x 1)) ; увеличивает число на единицу
(map inc (list 1 1 1)) ; возвращает список из двоек
(map square (list 1 2 3)) ; возвращает список из квадратов элементов, то есть 1, 4 и 9</sourcesyntaxhighlight>
 
Вспомним про lambda и решим задачу, которую поставили себе в начале этого раздела:
 
<sourcesyntaxhighlight lang="scheme">(define abc (list 1 1 1))
(map (lambda(x) (+ x 1)) abc)</sourcesyntaxhighlight>
 
А можно даже и список не вводить как дополнительную переменную:
 
<sourcesyntaxhighlight lang="scheme">(map (lambda(x) (+ x 1))
(list 1 1 1))</sourcesyntaxhighlight>
 
=== Упражнение 3 ===
Строка 330:
Пользуясь функциями <code>write</code> (для печати списка на экране) и <code>map</code>, напишите функцию, которая будет выводить на экран список, увеличенный на заданное число, например
 
<sourcesyntaxhighlight lang="scheme">(print-it 5 (list 1 2 3)) ; выведет "(6 7 8)"</sourcesyntaxhighlight>
 
== Работа со списками ==
Строка 343:
Пример:
 
<sourcesyntaxhighlight lang="scheme">(car (list 1 2 3)) ; вернёт 1
(cdr (list 1 2 3)) ; вернёт список из 2 и 3, то есть (list 2 3).</sourcesyntaxhighlight>
 
Проще говоря, функция <code>car</code> возвращает голову списка, а <code>cdr</code> — оставшийся хвост списка.
Строка 357:
{{Акмар}}
 
<sourcesyntaxhighlight lang="scheme">(define (print-list lst)
(if (not (null? lst))
(begin (display (car lst))
(newline)
(print-list (cdr lst)))))</sourcesyntaxhighlight>
 
Поэкспериментируем в интерпретаторе: