Scheme/Tutorial/4
Материал из ALT Linux Wiki
(Import from freesource.info) |
м (вычитка) |
||
(6 промежуточных версий не показаны.) | |||
Строка 1: | Строка 1: | ||
- | |||
- | |||
- | |||
Четвёртая часть рассказа. | Четвёртая часть рассказа. | ||
Строка 15: | Строка 12: | ||
Если мы находимся в командном интерпретаторе, то после последнего этапа ещё происходит печать получившегося выражения. | Если мы находимся в командном интерпретаторе, то после последнего этапа ещё происходит печать получившегося выражения. | ||
- | Вот собственно и всё! Теперь разберёмся с каждым шагом. Как и многое другое, процесс интерпретации разделён на несколько отдельных функций, что позволяет контролировать его с точностью до | + | Вот, собственно, и всё! Теперь разберёмся с каждым шагом. Как и многое другое, процесс интерпретации разделён на несколько отдельных функций, что позволяет контролировать его с точностью до миллиметра — если это, конечно, надо. |
Рассмотрим выражения: | Рассмотрим выражения: | ||
Строка 27: | Строка 24: | ||
===== 9.1 Чтение ===== | ===== 9.1 Чтение ===== | ||
- | Прочитать выражение позволяет функция <tt>read</tt>. По умолчанию чтение происходит со стандартного ввода. Глядя на примеры выражений и любовь языка к списковым структурам в круглых скобках, несложно догадаться, что мы получаем на выходе из <tt>read</tt> | + | Прочитать выражение позволяет функция <tt>read</tt>. По умолчанию чтение происходит со стандартного ввода. Глядя на примеры выражений и любовь языка к списковым структурам в круглых скобках, несложно догадаться, что мы получаем на выходе из <tt>read</tt> …правильно — именно списки и получаем, ну или просто константы, если скобок не было. |
- | + | ||
- | + | Первое — константа — число <tt>3</tt><br /> | |
- | + | Второе — константа — строка <tt>"string"</tt><br /> | |
- | * символ <tt>+</tt> (вот они символы-то! | + | Третье — список из констант: |
+ | * символ <tt>+</tt> (вот они, символы-то! неспроста они в языке существуют ;)) | ||
* число <tt>1</tt> | * число <tt>1</tt> | ||
* число <tt>3</tt> | * число <tt>3</tt> | ||
- | + | Четвёртое — сами, наверное, уже догадались.<br /> | |
- | + | Пятое — список из: | |
- | * | + | * константа — символ <tt>some-func</tt> |
- | * | + | * константа — символ <tt>a</tt> |
- | * | + | * константа — логическая не ложь. |
* ещё один список из: символа <tt>+</tt> и чисел <tt>1</tt> и <tt>3</tt>. | * ещё один список из: символа <tt>+</tt> и чисел <tt>1</tt> и <tt>3</tt>. | ||
- | + | Шестое — символ <tt>a</tt>. | |
Вот вам и весь парсер ;) | Вот вам и весь парсер ;) | ||
Строка 46: | Строка 44: | ||
===== 9.2 Исполнение ===== | ===== 9.2 Исполнение ===== | ||
- | Исполнить полученный список | + | Исполнить полученный список можно с помощью функции <tt>eval</tt>, которой передаются два параметра: список и среда исполнения. Что такое второй параметр — не будем пока заморачиваться, а примем как данность. |
- | Исполнитель рекурсивно пробегается по всем вложенным спискам, большинство констант интерпретируются в них самих (строки в строки, числа в числа) | + | Исполнитель рекурсивно пробегается по всем вложенным спискам, большинство констант интерпретируются в них самих (строки в строки, числа в числа) — а вот наткнувшись на символ, производится поиск соответствующей переменной и в результате подставляется то, на что она ссылается<ref>здесь описана так называемая подстановочная модель, она неточная, зато простая и понятная; то, что происходит в реальном интерпретаторе — гораздо сложнее, но суть остаётся примерно та же</ref>. |
- | После того как все переменные разобраны, если в результате остаётся | + | После того, как все переменные разобраны, если в результате остаётся список — то происходит запуск функции. Первый элемент списка — собственно указатель на саму функцию, например, в одном из наших примеров — это стандартная функция <tt>string-append</tt>, а все оставшиеся — это аргументы функции. Вычисляем указанную функцию от данных аргументов (в том же примере — это две константные строки <tt>"str1"</tt> и <tt>"str2"</tt>) …и получаем результат исполнения. |
Пройдёмся по нашим примерам: | Пройдёмся по нашим примерам: | ||
- | <tt>3</tt> | + | |
- | <tt>"string"</tt> | + | <tt>3</tt> — собственно, <tt>3</tt> и получим, никаких функций запускать не надо.<br /> |
- | <tt>(+ 1 3)</tt> | + | <tt>"string"</tt> — получим строку. |
- | <tt>(string-append "str1" "str2")</tt> | + | |
- | <tt>(some-func a #t (+ 1 3))</tt> | + | <tt>(+ 1 3)</tt> — выполним функцию сложения от аргументов <tt>1</tt> и <tt>3</tt>, результат — число <tt>4</tt>. |
- | + | ||
- | + | <tt>(string-append "str1" "str2")</tt> — результат — строка <tt>"str1str2"</tt>. | |
- | + | ||
- | <tt>a</tt> | + | <tt>(some-func a #t (+ 1 3))</tt> — получим результат выполнения функции от аргументов: |
+ | * значение переменной <tt>a</tt> | ||
+ | * логическая не ложь | ||
+ | * результат сложения <tt>1</tt> и <tt>3</tt>, то есть аргумент равен <tt>4</tt>. | ||
+ | <tt>a</tt> — значение переменной <tt>a</tt> | ||
===== 9.3 Вывод результата на экран ===== | ===== 9.3 Вывод результата на экран ===== | ||
- | Для вывода результата на экран есть множество функций | + | Для вывода результата на экран есть множество функций; самая интересная из них это, пожалуй, <tt>write</tt>. |
- | + | ||
- | + | Если в результате вы получили простую структуру, состоящую из списков (возможно, вложенных) и каких-либо констант (строк, чисел, символов), то <tt>write</tt> напечатает их в таком виде, что потом <tt>read</tt> может их обратно съесть. | |
- | Константы будут выведены естественным образом: | + | Те, кто знаком с языками типа Java, узнают в этом знакомые вещи, называемые словами ''сериализация'' и ''маршалинг'' …только сделанные лет за дцать до этого ;) |
- | <tt>7</tt> напечатается как <tt>7</tt> | + | |
- | <tt>"str"</tt> напечатается как <tt>"str"</tt> | + | Константы будут выведены естественным образом:<br /> |
- | Списки опять напечатаются как знакомые выражения, окруженные скобками | + | <tt>7</tt> напечатается как <tt>7</tt><br /> |
+ | <tt>"str"</tt> напечатается как <tt>"str"</tt><br /> | ||
+ | Списки опять напечатаются как знакомые выражения, окруженные скобками; например, список из <tt>1</tt> <tt>2</tt> и <tt>3</tt> будет напечатан как <tt>(1 2 3)</tt> | ||
===== 9.4 ===== | ===== 9.4 ===== | ||
- | Ну а теперь повторим пройденное. Помните про функцию <tt>quote</tt>, которая позволяла заполучить символы? <tt>quote</tt> просто напросто говорит интерпретатору, что не надо исполнять <tt>eval</tt> после <tt>read</tt>. | + | Ну а теперь повторим пройденное. Помните про функцию <tt>quote</tt>, которая позволяла заполучить символы? <tt>quote</tt> просто-напросто говорит интерпретатору, что не надо исполнять <tt>eval</tt> после <tt>read</tt>. |
- | Поэтому <tt>'a</tt> | + | |
- | <tt>'(1 2 3)</tt> | + | Поэтому <tt>'a</tt> — это просто символ <tt>a</tt><br /> |
+ | <tt>'(1 2 3)</tt> — это список из <tt>1</tt> <tt>2</tt> <tt>3</tt> | ||
==== 10 Особые формы ==== | ==== 10 Особые формы ==== | ||
- | Аргументы функции обрабатываются в некотором недокументированном | + | Аргументы функции обрабатываются в некотором недокументированном порядке — зависит от реализации Схемы. |
- | В отдельных случаях хочется заранее знать, как и когда будут аргументы вычислены. Поскольку эти | + | В отдельных случаях хочется заранее знать, как и когда будут аргументы вычислены. Поскольку эти «функции» такие особые, то и называются они «особые формы»; вот основные, которые нам потребуются на практике. |
===== 10.1 Условные выражения ===== | ===== 10.1 Условные выражения ===== | ||
Строка 91: | Строка 95: | ||
<tt>(if условие команда-если-истина команда-если-ложь)</tt> | <tt>(if условие команда-если-истина команда-если-ложь)</tt> | ||
- | присутствие команда-если-ложь традиционно необязательно. | + | присутствие <tt>команда-если-ложь</tt> традиционно необязательно. |
- | Сначала вычисляется условие | + | Сначала вычисляется условие; если оно истинно (то есть не <tt>#f</tt>), то вычисляется <tt>команда-если-истина</tt>, иначе вычисляется <tt>команда-если-ложь</tt>. |
- | Например, | + | Например, |
- | *<tt>(if #f 3 4)</tt> | + | * <tt>(if #f 3 4)</tt> — вернёт <tt>4</tt> |
- | * <tt> (if (+ 1 3) 5 6)</tt> | + | * <tt> (if (+ 1 3) 5 6)</tt> — вернёт <tt>5</tt>, поскольку результат <tt>(+ 1 3)</tt>, то есть <tt>4</tt> — «не ложь». |
- | * <tt>(if (string=? "aaa" "bbb") 3 5)</tt> | + | * <tt>(if (string=? "aaa" "bbb") 3 5)</tt> — вернёт <tt>5</tt>, ибо строки не равны |
- | * <tt>(if (number? 5) "number" "not number")</tt> | + | * <tt>(if (number? 5) "number" "not number")</tt> — вернёт строку <tt>"number"</tt> , ибо <tt>5</tt> — действительно число, а не что другое ;) |
- | * <tt>(if #f 3)</tt> | + | * <tt>(if #f 3)</tt> — поскольку <tt>"команда-если-ложь"</tt> отсутствует, то <tt>if</tt> вернёт некоторое волшебную сущность, которую иногда называют unspecific, иногда unspecified — в общем «то, не знаю что» или «неопределенное значение» — особый вид значения. |
===== 10.2 Множественное ветвление ===== | ===== 10.2 Множественное ветвление ===== | ||
Если в данной точке программы надо исследовать множество различных вариантов, то используйте <tt>cond</tt> | Если в данной точке программы надо исследовать множество различных вариантов, то используйте <tt>cond</tt> | ||
+ | |||
Формат: <tt>(cond вариант1 вариант2 ... )</tt> | Формат: <tt>(cond вариант1 вариант2 ... )</tt> | ||
- | вариант | + | <tt>вариант</tt> оформляется в виде <tt>(тест выражение1 выражение2 ... )</tt> |
- | есть ещё специальный вариант <tt>(else выражение1 выражение2 ... )</tt>, который применяется, если никакой другой вариант не прошёл. <tt>else</tt> может | + | есть ещё специальный вариант <tt>(else выражение1 выражение2 ... )</tt>, который применяется, если никакой другой вариант не прошёл. <tt>else</tt> может отсутствовать и должен быть всегда последним вариантом в случае присутствия. |
- | Например давайте попробуем понять, а что же пришло в функцию: строка или число, символ или что-то ещё? | + | Например, давайте попробуем понять, а что же пришло в функцию: строка или число, символ или что-то ещё? |
<pre>(define (func x) | <pre>(define (func x) | ||
(cond | (cond | ||
Строка 117: | Строка 122: | ||
((number? x) "число") | ((number? x) "число") | ||
((symbol? x) "символ") | ((symbol? x) "символ") | ||
- | (else "не знаю что такое")))</pre> | + | (else "не знаю, что такое")))</pre> |
===== 10.3 Последовательное исполнение ===== | ===== 10.3 Последовательное исполнение ===== | ||
- | Иногда в <tt>if</tt> допустима только одна команда на условие истины и одна на условие лжи. А что делать если хочется исполнить сразу много команд в случае некоторого условия? Иногда можно конечно обойтись имеющимися | + | Иногда в <tt>if</tt> допустима только одна команда на условие истины и одна на условие лжи. А что делать, если хочется исполнить сразу много команд в случае некоторого условия? Иногда можно, конечно, обойтись имеющимися средствами — но гораздо проще воспользоваться особой формой <tt>begin</tt>, которая вычисляет свои аргументы строго последовательно слева направо. В качестве результата возвращается результат работы последнего выражения. |
- | Пример: | + | Пример: |
<pre>(begin (+ 1 2) | <pre>(begin (+ 1 2) | ||
(+ 3 4))</pre> | (+ 3 4))</pre> | ||
сначала сложит <tt>1</tt> и <tt>2</tt>, потом сложит <tt>3</tt> и <tt>4</tt> и вернёт в качестве результата <tt>7</tt>. | сначала сложит <tt>1</tt> и <tt>2</tt>, потом сложит <tt>3</tt> и <tt>4</tt> и вернёт в качестве результата <tt>7</tt>. | ||
- | [[ | + | '''[[Scheme/Tutorial/5|далее>>]]''' |
+ | |||
+ | <references /> | ||
+ | |||
+ | {{Category navigation|title=Scheme|category=Scheme|sortkey=Tutorial}} |
Текущая версия на 08:53, 11 мая 2012
Четвёртая часть рассказа.
Содержание |
9 Открываем капот.
Может быть, из академических соображений так поступать не стоит, однако иногда бывает проще рассказать про причину, чем про её следствия. Давайте разберёмся с тем, как работает интерпретатор Scheme, благо знания нам уже это позволяют сделать. После этого всё, что было до сего момента неясно, уже станет очевидным.
Вот основной цикл работы интерпретатора:
- прочитать очередное выражение
- провести интерпретацию этого выражения
Если мы находимся в командном интерпретаторе, то после последнего этапа ещё происходит печать получившегося выражения.
Вот, собственно, и всё! Теперь разберёмся с каждым шагом. Как и многое другое, процесс интерпретации разделён на несколько отдельных функций, что позволяет контролировать его с точностью до миллиметра — если это, конечно, надо.
Рассмотрим выражения:
3 "string" (+ 1 3) (string-append "str1" "str2") (some-func a #t (+ 1 3)) a
9.1 Чтение
Прочитать выражение позволяет функция read. По умолчанию чтение происходит со стандартного ввода. Глядя на примеры выражений и любовь языка к списковым структурам в круглых скобках, несложно догадаться, что мы получаем на выходе из read …правильно — именно списки и получаем, ну или просто константы, если скобок не было.
Первое — константа — число 3
Второе — константа — строка "string"
Третье — список из констант:
- символ + (вот они, символы-то! неспроста они в языке существуют ;))
- число 1
- число 3
Четвёртое — сами, наверное, уже догадались.
Пятое — список из:
- константа — символ some-func
- константа — символ a
- константа — логическая не ложь.
- ещё один список из: символа + и чисел 1 и 3.
Шестое — символ a.
Вот вам и весь парсер ;)
9.2 Исполнение
Исполнить полученный список можно с помощью функции eval, которой передаются два параметра: список и среда исполнения. Что такое второй параметр — не будем пока заморачиваться, а примем как данность.
Исполнитель рекурсивно пробегается по всем вложенным спискам, большинство констант интерпретируются в них самих (строки в строки, числа в числа) — а вот наткнувшись на символ, производится поиск соответствующей переменной и в результате подставляется то, на что она ссылается[1].
После того, как все переменные разобраны, если в результате остаётся список — то происходит запуск функции. Первый элемент списка — собственно указатель на саму функцию, например, в одном из наших примеров — это стандартная функция string-append, а все оставшиеся — это аргументы функции. Вычисляем указанную функцию от данных аргументов (в том же примере — это две константные строки "str1" и "str2") …и получаем результат исполнения.
Пройдёмся по нашим примерам:
3 — собственно, 3 и получим, никаких функций запускать не надо.
"string" — получим строку.
(+ 1 3) — выполним функцию сложения от аргументов 1 и 3, результат — число 4.
(string-append "str1" "str2") — результат — строка "str1str2".
(some-func a #t (+ 1 3)) — получим результат выполнения функции от аргументов:
- значение переменной a
- логическая не ложь
- результат сложения 1 и 3, то есть аргумент равен 4.
a — значение переменной a
9.3 Вывод результата на экран
Для вывода результата на экран есть множество функций; самая интересная из них это, пожалуй, write.
Если в результате вы получили простую структуру, состоящую из списков (возможно, вложенных) и каких-либо констант (строк, чисел, символов), то write напечатает их в таком виде, что потом read может их обратно съесть.
Те, кто знаком с языками типа Java, узнают в этом знакомые вещи, называемые словами сериализация и маршалинг …только сделанные лет за дцать до этого ;)
Константы будут выведены естественным образом:
7 напечатается как 7
"str" напечатается как "str"
Списки опять напечатаются как знакомые выражения, окруженные скобками; например, список из 1 2 и 3 будет напечатан как (1 2 3)
9.4
Ну а теперь повторим пройденное. Помните про функцию quote, которая позволяла заполучить символы? quote просто-напросто говорит интерпретатору, что не надо исполнять eval после read.
Поэтому 'a — это просто символ a
'(1 2 3) — это список из 1 2 3
10 Особые формы
Аргументы функции обрабатываются в некотором недокументированном порядке — зависит от реализации Схемы.
В отдельных случаях хочется заранее знать, как и когда будут аргументы вычислены. Поскольку эти «функции» такие особые, то и называются они «особые формы»; вот основные, которые нам потребуются на практике.
10.1 Условные выражения
(if условие команда-если-истина команда-если-ложь)
присутствие команда-если-ложь традиционно необязательно.
Сначала вычисляется условие; если оно истинно (то есть не #f), то вычисляется команда-если-истина, иначе вычисляется команда-если-ложь.
Например,
- (if #f 3 4) — вернёт 4
- (if (+ 1 3) 5 6) — вернёт 5, поскольку результат (+ 1 3), то есть 4 — «не ложь».
- (if (string=? "aaa" "bbb") 3 5) — вернёт 5, ибо строки не равны
- (if (number? 5) "number" "not number") — вернёт строку "number" , ибо 5 — действительно число, а не что другое ;)
- (if #f 3) — поскольку "команда-если-ложь" отсутствует, то if вернёт некоторое волшебную сущность, которую иногда называют unspecific, иногда unspecified — в общем «то, не знаю что» или «неопределенное значение» — особый вид значения.
10.2 Множественное ветвление
Если в данной точке программы надо исследовать множество различных вариантов, то используйте cond
Формат: (cond вариант1 вариант2 ... )
вариант оформляется в виде (тест выражение1 выражение2 ... )
есть ещё специальный вариант (else выражение1 выражение2 ... ), который применяется, если никакой другой вариант не прошёл. else может отсутствовать и должен быть всегда последним вариантом в случае присутствия.
Например, давайте попробуем понять, а что же пришло в функцию: строка или число, символ или что-то ещё?
(define (func x) (cond ((string? x) "строка") ((number? x) "число") ((symbol? x) "символ") (else "не знаю, что такое")))
10.3 Последовательное исполнение
Иногда в if допустима только одна команда на условие истины и одна на условие лжи. А что делать, если хочется исполнить сразу много команд в случае некоторого условия? Иногда можно, конечно, обойтись имеющимися средствами — но гораздо проще воспользоваться особой формой begin, которая вычисляет свои аргументы строго последовательно слева направо. В качестве результата возвращается результат работы последнего выражения.
Пример:
(begin (+ 1 2) (+ 3 4))
сначала сложит 1 и 2, потом сложит 3 и 4 и вернёт в качестве результата 7.
- ↑ здесь описана так называемая подстановочная модель, она неточная, зато простая и понятная; то, что происходит в реальном интерпретаторе — гораздо сложнее, но суть остаётся примерно та же