├── .github └── workflows │ └── Docker.yml ├── .gitignore ├── .yamllint ├── Dockerfile ├── Makefile ├── README.md ├── description.ru.yml ├── docker-compose.override.yml ├── docker-compose.yml ├── modules ├── 10-basics │ ├── 10-hello-world │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 15-code-as-data │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 18-lists-as-a-tree │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 20-forms │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 25-order-of-evaluation │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 30-brackets │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ └── description.ru.yml ├── 20-definitions │ ├── 10-definitions-define │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 15-definitions-define-function │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 20-definitions-lambda-call │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 25-function-shorthand │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 30-modules │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 35-let │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ └── description.ru.yml ├── 30-logic │ ├── 10-boolean-type │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 20-if │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 30-guards │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 35-case │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 40-cond │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 50-expressions │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ └── description.ru.yml ├── 40-lists │ ├── 10-intro │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 20-builtin-loops-map │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 30-builtin-filter │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 40-foldl │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 50-list-internals │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ ├── 60-lists-and-recursion │ │ ├── Makefile │ │ ├── index.rkt │ │ ├── ru │ │ │ ├── EXERCISE.md │ │ │ ├── README.md │ │ │ └── data.yml │ │ └── test.rkt │ └── description.ru.yml └── 50-strings │ ├── 10-chars │ ├── Makefile │ ├── index.rkt │ ├── ru │ │ ├── EXERCISE.md │ │ ├── README.md │ │ └── data.yml │ └── test.rkt │ ├── 20-strings │ ├── Makefile │ ├── index.rkt │ ├── ru │ │ ├── EXERCISE.md │ │ ├── README.md │ │ └── data.yml │ └── test.rkt │ ├── 30-predicates │ ├── Makefile │ ├── index.rkt │ ├── ru │ │ ├── EXERCISE.md │ │ ├── README.md │ │ └── data.yml │ └── test.rkt │ ├── 40-operations │ ├── Makefile │ ├── index.rkt │ ├── ru │ │ ├── EXERCISE.md │ │ ├── README.md │ │ └── data.yml │ └── test.rkt │ ├── 50-formatting │ ├── Makefile │ ├── index.rkt │ ├── ru │ │ ├── EXERCISE.md │ │ ├── README.md │ │ └── data.yml │ └── test.rkt │ ├── 60-immutability │ ├── Makefile │ ├── index.rkt │ ├── ru │ │ ├── EXERCISE.md │ │ ├── README.md │ │ └── data.yml │ └── test.rkt │ └── description.ru.yml ├── spec.yml └── src └── tests.rkt /.github/workflows/Docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | main: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: hexlet-basics/exercises-action@release 17 | with: 18 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 19 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | extends: default 4 | 5 | rules: 6 | line-length: disable 7 | empty-lines: disable 8 | trailing-spaces: disable 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hexletbasics/base-image 2 | 3 | RUN apt-get update && apt-get install -y racket libssl-dev 4 | RUN raco pkg install \ 5 | --scope installation --batch \ 6 | --no-docs --no-cache --no-trash \ 7 | rackunit \ 8 | review 9 | 10 | WORKDIR /exercises-racket 11 | 12 | COPY . . 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include /opt/basics/common/common.mk 2 | 3 | compose-setup: compose-build 4 | 5 | compose: 6 | docker-compose up 7 | 8 | compose-build: 9 | docker-compose build 10 | 11 | code-lint: 12 | @(for f in $$(find modules -name '*.rkt'); do raco review $$f || exit 1; done) 13 | 14 | compose-bash: 15 | docker-compose run exercises bash 16 | 17 | compose-test: 18 | docker-compose run exercises make test 19 | 20 | compose-description-lint: 21 | docker-compose run exercises make description-lint 22 | 23 | compose-schema-validate: 24 | docker-compose run exercises make schema-validate 25 | 26 | ci-check: 27 | docker-compose --file docker-compose.yml build 28 | docker-compose --file docker-compose.yml up --abort-on-container-exit -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exercises-racket 2 | 3 | [![Github Actions Status](../../workflows/Docker/badge.svg)](../../actions) 4 | 5 | ## How to contribute 6 | 7 | * Discussions at #hexlet-volunteers [Telegram community](https://t.me/hexletcommunity/12) 8 | 9 | ## Develop 10 | 11 | ```bash 12 | # setup 13 | make 14 | # run 15 | make compose 16 | # check 17 | make ci-check 18 | 19 | # run tests 20 | make compose-test 21 | 22 | # run linters and validators 23 | make compose-code-lint 24 | make compose-description-lint 25 | make compose-schema-validate 26 | ``` 27 | 28 | ## 29 | [![Hexlet Ltd. logo](https://raw.githubusercontent.com/Hexlet/assets/master/images/hexlet_logo128.png)](https://hexlet.io/?utm_source=github&utm_medium=link&utm_campaign=exercises-racket) 30 | 31 | This repository is created and maintained by the team and the community of Hexlet, an educational project. [Read more about Hexlet](https://hexlet.io/?utm_source=github&utm_medium=link&utm_campaign=exercises-racket). 32 | ## 33 | 34 | See most active contributors on [hexlet-friends](https://friends.hexlet.io/). 35 | -------------------------------------------------------------------------------- /description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # todo: обновить header 3 | header: Racket 4 | title: 'Racket: курс программирования на языке Racket' 5 | description: | 6 | Racket относится к семейству Lisp-языков. Эти языки настолько не похожи на все остальное, что даже опытным программистам приходится изучать их с самых основ. Этот модуль посвящен знакомству с синтаксисом и концепциями лежащими в основе любого лиспа 7 | seo_description: | 8 | Язык программирования Racket (Ракета) относится к семейству Lisp-языков. Курс программирования на Racket посвящен знакомству с синтаксисом и концепциями лежащими в основе языка 9 | -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | services: 4 | exercises: 5 | volumes: 6 | - .:/exercises-racket 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | services: 4 | exercises: 5 | build: . 6 | image: hexletbasics/exercises-racket:cached 7 | command: make check 8 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ racket test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (displayln "Hello, World!") 5 | #| END |# 6 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Наберите в редакторе код из задания символ в символ и нажмите «Проверить». 3 | 4 | ```scheme 5 | ; я комментарий 6 | (displayln "Hello, World!") 7 | ``` 8 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Racket — мультипарадигменный язык общего назначения, входящий в Lisp-семейство. Подчеркну, что Lisp — это не название конкретного языка, а обозначение группы языков, обладающих определёнными схожими свойствами. Racket, как и большинство других лиспоподобных языков, не является функциональным языком программирования. На нём можно писать как функционально, так и императивно. 3 | 4 | Lisp-языков много, а первый появился в 1958 году. Его автором был Джон Маккарти — автор термина «искусственный интеллект», один из основоположников функционального программирования, лауреат премии Тьюринга за огромный вклад в область исследований искусственного интеллекта. 5 | 6 | Кроме Racket, в современной разработке относительно популярны Clojure, ClojureScript, Common Lisp, различные варианты Scheme (читается "ским"). Интересный факт: редактор Emacs написан на собственном диалекте, который называется Emacs Lisp. И все программисты, использующие emacs, в какой-то степени являются Lisp-программистами. 7 | 8 | Всё, что дальше будет говориться про Racket, почти всегда справедливо и для остальных Lisp-языков. Да и сам Racket очень похож на другие диалекты Scheme (да, Racket — тоже диалект Scheme). 9 | 10 | По традиции начнём знакомство с языком с написания программы "Hello, World!". Эта программа будет выводить на экран текст _Hello, World!_. Для вывода на экран в Racket используется функция `displayln`: 11 | 12 | ```scheme 13 | #lang racket 14 | 15 | (displayln "Hello, World!") 16 | ``` 17 | 18 |
19 | Hello, World!
20 | 
21 | 22 | В среде программистов на Lisp-языках традиционно говорят "процедура", а не "функция". Исторически, процедура — это блок кода, который не имеет возврата и, как правило, нужен исключительно для выполнения побочных эффектов. А под функцией понимают функцию в её математическом значении. Функция зависит от своих аргументов и вычисляет результат, который затем возвращается наружу. В некоторых языках, таких как Pascal, эти понятия разнесены на уровне синтаксиса и семантики. В большинстве же языков функциями называют в том числе процедуры. В документации Racket встречается и то и другое. Для простоты мы будем всегда говорить о функциях. 23 | 24 | В примере выше есть строчка `#lang racket`, это так называемая "прагма". Прагма `lang` говорит компилятору, что данный файл — это модуль (о модулях мы поговорим позднее), написанный на языке `racket`. Дело в том, что Racket — это не только язык сам по себе, но ещё и платформа для создания других языков! Более того, в одном проекте можно использовать сразу несколько созданных в Racket языков. Именно поэтому нам нужно сообщать компилятору, на каком же языке мы писали код модуля. 25 | 26 | Один из примеров "другого языка на базе Racket" — [Scribble](https://docs.racket-lang.org/scribble/index.html), язык для разметки документации, книг, статей. Программы на Scribble генерируют HTML и PDF (а также и другие форматы). 27 | 28 | Racket требует указания прагмы `#lang` в каждом файле с исходным кодом (есть исключение, но это сейчас не важно), однако в нашей среде оная присутствует не всегда (по техническим причинам). Не забудьте про эту разницу, когда решите запускать Racket программы у себя на компьютере. 29 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Привет, Мир! 3 | tips: 4 | - > 5 | Используйте [готовый 6 | бойлерплейт](https://github.com/hexlet-boilerplates/sicp-racket) для 7 | прохождения SICP на Racket 8 | - > 9 | Посмотрите доклад [Simple Made 10 | Easy](https://www.youtube.com/watch?v=LKtk3HCgTa8) 11 | -------------------------------------------------------------------------------- /modules/10-basics/10-hello-world/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require "../../../src/tests.rkt") 4 | 5 | (assert-output "Hello, World!") 6 | -------------------------------------------------------------------------------- /modules/10-basics/15-code-as-data/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/10-basics/15-code-as-data/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (displayln "winter is coming!") 5 | #| END |# 6 | -------------------------------------------------------------------------------- /modules/10-basics/15-code-as-data/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Напишите программу, которая выводит на экран фразу _winter is coming!_. 3 | 4 |
5 | winter is coming!
6 | 
7 | -------------------------------------------------------------------------------- /modules/10-basics/15-code-as-data/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Первый вопрос, который возникает при чтении кода Racket-программы, — почему такая странная запись вызова функции? Вместо привычного `displayln("eat me")` мы видим `(displayln "eat me")`. 3 | 4 | Причины такой записи лежат в идее, которая стоит за всеми Lisp-языками. Название "Lisp" расшифровывается как LISt Processor (обработчик списков). [Односвязный список](https://en.wikipedia.org/wiki/Linked_list) — основная структура данных в этих языках. Более того, любая программа на Lisp-языке — сама по себе список! Посмотрите еще раз на эту программу: 5 | 6 | ```scheme 7 | (displayln "eat me") ; => eat me 8 | ``` 9 | 10 | С одной стороны, это вызов функции `displayln` со строковым аргументом "eat me". С другой — это список из двух элементов: displayln и "eat me". Рассмотрим несколько примеров. Не пытайтесь их понять как код, мы ещё не готовы к этому. Смотрите на них как на обычные списки. 11 | 12 | ```scheme 13 | (+ 1 2) ; сложение, или список из трёх элементов 14 | (define count 0) ; объявление переменной, или список из трёх элементов 15 | (+ 100 3 8 9) ; сложение, или список из пяти элементов 16 | ``` 17 | 18 | В этом моменте проявляется одна из ключевых особенностей любого Lisp-языка: код на Lisp одновременно является данными Lisp-языка (говорят "код как данные"). Это свойство называется гомоиконичностью и является визитной карточкой данного семейства языков. 19 | 20 | Возможно, вас интересует, зачем это нужно? Гомоиконичность даёт возможность писать макросы, работающие с исходным кодом как со списком. Механизм макросов в Lisp-языках — одна из мощнейших вещей в программировании вообще. 21 | -------------------------------------------------------------------------------- /modules/10-basics/15-code-as-data/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Код как данные 3 | definitions: 4 | - name: Гомоиконичность 5 | description: >- 6 | свойство некоторых языков программирования, в которых текст программы 7 | одновременно может рассматриваться как структура данных этого же языка. 8 | tips: [] 9 | -------------------------------------------------------------------------------- /modules/10-basics/15-code-as-data/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require "../../../src/tests.rkt") 4 | 5 | (assert-output "winter is coming!") 6 | -------------------------------------------------------------------------------- /modules/10-basics/18-lists-as-a-tree/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/10-basics/18-lists-as-a-tree/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (displayln (- 128 37)) 5 | #| END |# 6 | -------------------------------------------------------------------------------- /modules/10-basics/18-lists-as-a-tree/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Напишите программу, которая выводит на экран разность чисел 128 и 37. 3 | -------------------------------------------------------------------------------- /modules/10-basics/18-lists-as-a-tree/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Списки могут быть вложенными. В такой форме вместо конкретного значения подставляется новый список `()`: 3 | 4 | ```scheme 5 | (- 1 8) ; вычитание, или список из трёх элементов 6 | (+ 1 (- 3 2)) ; сложение, или список из трёх элементов, в котором третий элемент — список из трёх элементов 7 | (- (* 3 3) (- 2 3) (+ 5 1)) ; вычитание, или список из четырёх элементов 8 | ``` 9 | 10 | *Вопрос для самоконтроля. Сколько элементов в списке ((3) 8 (7 9))?* 11 | 12 | Список — рекурсивная структура данных. Любой элемент может быть списком и содержать внутри себя элементы-списки. Ниже — пример полноценной Racket-программы. 13 | 14 | ```scheme 15 | ; В конце этого курса вы сможете не только понять этот код, но и написать его самостоятельно 16 | (define (square x) (expt x 2)) 17 | 18 | (define (squarer xs) 19 | (if (empty? xs) 20 | empty 21 | (cons (square (first xs)) (squarer (rest xs))))) 22 | 23 | (squarer '(1 2 3 4 5)) 24 | ``` 25 | 26 | Код выглядит непривычно и не очень понятно, но попробуйте поменять угол зрения. Посмотрите на этот код как на структуру данных. Проследите за тем, как списки вкладываются друг в друга и форматируются. 27 | 28 | Фактически получается, что исходный код на Racket — это древовидная структура. 29 | -------------------------------------------------------------------------------- /modules/10-basics/18-lists-as-a-tree/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Списки как дерево 3 | definitions: [] 4 | tips: [] 5 | -------------------------------------------------------------------------------- /modules/10-basics/18-lists-as-a-tree/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require "../../../src/tests.rkt") 4 | 5 | (assert-output "91") 6 | -------------------------------------------------------------------------------- /modules/10-basics/20-forms/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/10-basics/20-forms/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (displayln (- 10 100 12 18)) 5 | #| END |# 6 | -------------------------------------------------------------------------------- /modules/10-basics/20-forms/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Выведите на экран значение выражения `10 - 100 - 12 - 18`. 3 | -------------------------------------------------------------------------------- /modules/10-basics/20-forms/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Про Lisp-подобные языки говорят, что у этих языков нет синтаксиса. Синтаксис у них, конечно, есть, но максимально примитивный, фактически состоящий из списков и значений. Кроме того, в лиспах отсутствуют ключевые слова и соответствующие им конструкции. В обычных языках существует множество управляющих конструкций, таких как условия, циклы, возврат, присвоение переменных и многие другие. В лиспоподобных языках таких конструкций нет. (Это не значит, что на Racket нельзя реализовать цикл или написать условие — можно!) 3 | 4 | Каким же образом Racket понимает, с чем сейчас он работает и что нужно делать? Все дело в _формах_. Любая корректная программа на Lisp называется _формой_. Например: 5 | 6 | ```scheme 7 | ; форма 8 | (displayln "i am from form") 9 | ; форма 10 | (+ 1 2) 11 | ; и это формы 12 | 8 13 | "hello" 14 | 15 | ; а это не форма, так как такой код завершится с ошибкой 16 | (1 2 3) 17 | ``` 18 | 19 | Форм всего две — нормальная и составная. Нормальной соответствуют все значения (и определения, с которыми мы познакомимся позже), так как они вычисляются сами в себя, например число _8_ или строка "hello". Составная форма — это список, который нужно обработать тем или иным способом (вычислить). 20 | 21 | Когда код представлен как список, появляется простор для интерпретации. Возьмём сложение двух чисел, например 3 и 2. В виде списка такое сложение можно представить тремя разными способами: 22 | 23 | * `(3 + 2)` 24 | * `(3 2 +)` 25 | * `(+ 3 2)` 26 | 27 | В Lisp-языках используется префиксная нотация, то есть первый элемент формы определяет поведение (семантику). Такой способ обладает рядом преимуществ. Например, он позволяет естественным образом выполнять действие с любым набором элементов: 28 | 29 | ```scheme 30 | ; 3 + 2 + 8 + 3 + 9 31 | (+ 3 2 8 3 9) ; 25 32 | 33 | ; 3 - 2 - 8 - 3 - 9 34 | (- 3 2 8 3 9) ; -19 35 | ``` 36 | 37 | Другое преимущество — простота реализации динамической диспетчеризации по сравнению с другими языками. Этому способствуют и макросы. 38 | -------------------------------------------------------------------------------- /modules/10-basics/20-forms/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Формы 3 | definitions: [] 4 | tips: [] 5 | -------------------------------------------------------------------------------- /modules/10-basics/20-forms/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require "../../../src/tests.rkt") 4 | 5 | (assert-output "-120") 6 | -------------------------------------------------------------------------------- /modules/10-basics/25-order-of-evaluation/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/10-basics/25-order-of-evaluation/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (displayln 5 | (- 100 34 22 6 | (- (+ 5 3) 7 | 10))) 8 | #| END |# 9 | -------------------------------------------------------------------------------- /modules/10-basics/25-order-of-evaluation/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Выведите на экран значение выражения `100 - 34 - 22 - (5 + 3 - 10)`. 3 | -------------------------------------------------------------------------------- /modules/10-basics/25-order-of-evaluation/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Попробуем превратить выражение `5 - 3 + 1` в программу на Racket. С точки зрения арифметики, порядок вычисления элементов этого составного выражения строго определен. Сначала вычисляется `5 - 3`, затем к получившемуся результату прибавляется единица. 3 | 4 | Начнем с того, что вычисляется первым: `5 - 3` превращается в `(- 5 3)`. Затем сложим получившийся результат с единицей: `(+ (- 5 3) 1)`. Так как сложение — это коммутативная операция (Помните "от перемены мест слагаемых сумма не меняется"?), то же самое можно записать и в другом порядке: `(+ 1 (- 5 3))`. Неизменным остается то, что в начале каждого списка находится операция. 5 | 6 | Заметьте, выражения, вычисляемые первыми, находятся в глубине дерева. Такое поведение свойственно большинству обычных языков. Сначала вычисляются аргументы функций, затем вызывается сама функция. 7 | 8 | Попробуем другой вариант: `5 - (3 + 1)`. В этом выражении скобки устанавливают другой приоритет. Это значит, что сначала вычислится сумма единицы и тройки. Так и запишем: `(+ 3 1)` (или так: `(+ 1 3)`). Теперь возьмём пятерку и вычтем из неё получившийся результат: `(- 5 (+ 1 3))`. 9 | 10 | В такие моменты проявляется ещё одна отличительная особенность Lisp-языков. Древовидная структура программы сама определяет последовательность вычисления поддеревьев. Отпадает необходимость использовать дополнительные скобки. 11 | 12 | Ещё один пример: `5 + 7 + (8 - 3) - (8 * 5)`. Действуем по привычной схеме: 13 | 14 | * `(* 5 8)` 15 | * `(- 8 3)` 16 | * `(+ 5 7 (- 8 3))` 17 | * `(- (+ 5 7 (- 8 3)) (* 5 8))` 18 | 19 | В некоторых ситуациях порядок вычисления элементов списка не соответствует порядку их следования. Такое происходит при использовании специальных форм и макросов. Об этом поговорим позже. 20 | -------------------------------------------------------------------------------- /modules/10-basics/25-order-of-evaluation/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Порядок вычисления 3 | definitions: [] 4 | tips: [] 5 | -------------------------------------------------------------------------------- /modules/10-basics/25-order-of-evaluation/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require "../../../src/tests.rkt") 4 | 5 | (assert-output "46") 6 | -------------------------------------------------------------------------------- /modules/10-basics/30-brackets/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/10-basics/30-brackets/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (displayln 5 | (+ 4 6 | (- 2 7 | (* 3 5) 8 | (/ 8 7)))) 9 | #| END |# 10 | -------------------------------------------------------------------------------- /modules/10-basics/30-brackets/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Выведите на экран значение выражения: `4 + 2 - 3 * 5 - 8 / 7`. Выполните форматирование кода так, чтобы он легче воспринимался (код можно разбить по-разному). 3 | -------------------------------------------------------------------------------- /modules/10-basics/30-brackets/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Скобки настолько пугают новичков, что весь интернет завален вопросами: "Почему в лиспе так много скобок?". Такие вопросы возникают не просто так: при обычном способе редактирования текста Lisp-программы с трудом поддаются модификации. Забытая скобка может стать причиной долгой отладки. Посмотрите на этот код: 3 | 4 | ```scheme 5 | (define (GET/hash #:rconn [rconn (current-redis-connection)] key 6 | #:map-key [fkey identity] 7 | #:map-val [fval identity]) 8 | (let loop ([lst (HGETALL #:rconn rconn key)] [h (hash)]) 9 | (if (null? lst) h 10 | (loop (cddr lst) (hash-set h (fkey (car lst)) (fval (cadr lst))))))) 11 | ``` 12 | 13 | В самом конце очень много скобок. Представьте, что будет, если придётся обернуть какую-то часть кода в новый список или удалить ненужный список? Похожие проблемы возникают при редактировании HTML-файлов, когда нужно удалить как открывающий, так и закрывающий тег (либо, наоборот, добавить). 14 | 15 | Очевидно, что обычный способ работы в редакторе не слишком подходит для модификации Lisp-программ. Поэтому в этом мире приняты другие подходы, о которых начинающие Lisp-программисты узнают случайно. 16 | 17 | Секрет приятной работы с Lisp-кодом состоит в изменении точки зрения на этот код. В то время как в обычных языках мы модифицируем текст, в Lisp мы оперируем деревом. Для удобной работы с этим деревом нам понадобятся операции, которые помогают легко вставлять и удалять узлы, объединять их и разъединять. А еще неплохо было бы никогда не нарушать "баланс скобок". 18 | 19 | Такой способ работы с кодом существует и называется "структурное редактирование". Исторически сложилось так, что расширения для редакторов принято называть "Paredit". Попробуйте загуглить: "<имя редактора> paredit lisp". Скажем, для продуктов компании JetBrains разработано специальное расширение [Cursive](https://cursive-ide.com/). В документации этого расширения [наглядно показаны](https://cursive-ide.com/userguide/paredit.html) возможности Paredit. Обязательно посмотрите эти гифки, они помогут понять принципы управления Lisp-кодом. У Paredit есть альтернатива [Parinfer](https://shaunlebron.github.io/parinfer/). 20 | 21 | При правильном подходе в какой-то момент вы вдруг обнаружите, что структурное редактирование эффективнее обычного. Скобки перестанут быть проблемой, а при возврате в обычные языки вы начнёте испытывать неудобства. 22 | 23 | Другой важный аспект — правильное форматирование. Длинные операции принято разбивать так, что операнды оказываются друг под другом: 24 | 25 | ```scheme 26 | (+ 234 27 | 88 28 | 123423) 29 | ``` 30 | 31 | Более сложный пример: 32 | 33 | ```scheme 34 | (- 100 35 | (+ 4 100) 36 | (- 1000 37 | 50)) 38 | ``` 39 | 40 | Такая запись тоже требует привыкания, но взамен вы сможете с лёгкостью ориентироваться в аккуратно оформленном коде. 41 | -------------------------------------------------------------------------------- /modules/10-basics/30-brackets/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Скобки 3 | definitions: [] 4 | tips: [] 5 | -------------------------------------------------------------------------------- /modules/10-basics/30-brackets/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require "../../../src/tests.rkt") 4 | 5 | (assert-output "-71/7") 6 | -------------------------------------------------------------------------------- /modules/10-basics/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Основы 4 | description: | 5 | 6 | Racket относится к семейству Lisp-языков. Эти языки настолько не похожи на всё остальное, что даже опытным программистам приходится изучать их с самых основ. Этот модуль посвящен знакомству с синтаксисом и концепциями, лежащими в основе любого лиспа. 7 | -------------------------------------------------------------------------------- /modules/20-definitions/10-definitions-define/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/20-definitions/10-definitions-define/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (define members-count 100) 5 | 6 | (displayln members-count) 7 | #| END |# 8 | -------------------------------------------------------------------------------- /modules/20-definitions/10-definitions-define/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Создайте объявление, обозначающее "количество участников" (имя соорудите сами), присвойте ему значение 100 и распечатайте на экран. 3 | -------------------------------------------------------------------------------- /modules/20-definitions/10-definitions-define/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Racket — не функциональный язык программирования. В нем есть настоящие переменные, которые можно изменять. Переменные создаются с помощью конструкции `define` и называются объявлениями. 3 | 4 | ```scheme 5 | (define id expr) 6 | ; id — идентификатор 7 | ; expr — выражение 8 | ``` 9 | 10 | Например: 11 | 12 | ```scheme 13 | ; define создаёт "объявление". 14 | (define lang "racket") 15 | (displayln lang) ; => racket 16 | ``` 17 | 18 | Значением объявления может быть как нормальная форма (значение), так и составная: 19 | 20 | ```scheme 21 | (define result (+ 7 (- 4 6))) 22 | (displayln result) ; => 5 23 | ``` 24 | 25 | define связывает имя (идентификатор) и значение следующего за ним выражения. 26 | 27 | Имена объявлений, состоящие из нескольких слов, соединяют с помощью дефиса. В Lisp-языках повсеместно принят так называемый "kebab-case". 28 | 29 | ```scheme 30 | (define dangerous-year 1984) 31 | (displayln dangerous-year) ; => 1984 32 | ``` 33 | 34 | Для изменения значения объявления используется функция `set!`: 35 | 36 | ```scheme 37 | (set! lang "scheme") 38 | (displayln lang) ; => scheme 39 | ``` 40 | 41 | В общем случае использовать `set!` не рекомендуется. Racket отлично поддерживает функциональную парадигму и всячески её поощряет. Код с переменными практически всегда легко заменяется на код с константами. 42 | -------------------------------------------------------------------------------- /modules/20-definitions/10-definitions-define/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Объявление символов 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/20-definitions/10-definitions-define/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require "../../../src/tests.rkt") 4 | 5 | (assert-output "100") 6 | -------------------------------------------------------------------------------- /modules/20-definitions/15-definitions-define-function/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/20-definitions/15-definitions-define-function/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define cube (lambda (x) (* x x x))) 7 | #| END |# 8 | -------------------------------------------------------------------------------- /modules/20-definitions/15-definitions-define-function/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Создайте функцию с именем `cube`, которая вычисляет куб переданного числа. 3 | 4 | ```scheme 5 | (cube 3) ; 27 6 | ``` 7 | -------------------------------------------------------------------------------- /modules/20-definitions/15-definitions-define-function/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Функции в Racket обладают следующими свойствами: 3 | 4 | * У функций нет имен. Во многих языках такие функции также существуют и называются анонимными функциями или лямбда-функциями. 5 | * Функции являются объектами первого рода. Их можно присваивать переменным, передавать в другие функции и возвращать из функций. 6 | 7 | Примеры: 8 | 9 | ```scheme 10 | ; определение функции, вычисляющей сумму двух чисел 11 | (lambda (x y) (+ x y)) 12 | ``` 13 | 14 | В примере выше определяется функция с двумя аргументами. Определение функции начинается со слова `lambda`. Вторым элементом в форме определения функции идёт список аргументов. Третий и последующие элементы — тело функции. То есть тело может состоять из нескольких форм (как минимум из одной): 15 | 16 | ```scheme 17 | (lambda () 18 | (displayln "one") 19 | (displayln "two")) 20 | ``` 21 | 22 | Обратите внимание на отсутствие инструкции `return`. В отличие от большинства других языков, в Lisp-языках инструкций практически нет. Всё есть выражение. А выражения всегда возвращают результат. Если хорошо подумать, то такое поведение следует из самой структуры Lisp-программы. Фактически мы имеем дерево, которое должно вычисляться в какое-то значение, значит на каждом уровне должен создаваться возврат, поднимающийся выше по дереву, и так до самого корня. Возвращается всегда *последнее вычисленное выражение*. 23 | 24 | Пара примеров для закрепления: 25 | 26 | ```scheme 27 | ; печать на экран 28 | (lambda () (displayln "hello!")) 29 | ; квадрат числа 30 | (lambda (n) (* n n)) 31 | 32 | ; среднее между двумя числами 33 | (lambda (num1 num2) (/ (+ num1 num2) 2)) 34 | ``` 35 | 36 | Определение функции само по себе мало полезно, особенно если мы захотим использовать её несколько раз. Для повторного использования нужно создать объявление, в которое запишется функция. Такое возможно благодаря тому, что форма определения функции — это выражение, возвращающее саму функцию. 37 | 38 | ```scheme 39 | (define square (lambda (n) (* n n))) 40 | ``` 41 | 42 | Теперь попробуем вызвать. 43 | 44 | ```scheme 45 | (square 7) ; 49 46 | (square 5) ; 25 47 | ``` 48 | -------------------------------------------------------------------------------- /modules/20-definitions/15-definitions-define-function/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Создание и вызов функций 3 | definitions: 4 | - name: Объект первого рода 5 | description: >- 6 | Сущность в языке, которая рассматривается как данные. Это значит, что её 7 | можно записывать в переменную, передавать в функции и возвращать из 8 | функций. 9 | tips: [] 10 | -------------------------------------------------------------------------------- /modules/20-definitions/15-definitions-define-function/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (cube 3) 27) 8 | (check-equal? (cube 2) 8) 9 | (check-equal? (cube 1) 1)) 10 | -------------------------------------------------------------------------------- /modules/20-definitions/20-definitions-lambda-call/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/20-definitions/20-definitions-lambda-call/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (define result 5 | ((lambda (num1 num2) (/ (+ num1 num2) 2)) 6 | 2 4)) 7 | 8 | (displayln result) 9 | #| END |# 10 | -------------------------------------------------------------------------------- /modules/20-definitions/20-definitions-lambda-call/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | 1. Определите (без создания переменной) и вызовите функцию, которая находит среднее арифметическое между двумя числами. В качестве чисел подставьте 2 и 4. 3 | 2. Запишите результат в переменную. 4 | 3. Выведите переменную на экран. 5 | -------------------------------------------------------------------------------- /modules/20-definitions/20-definitions-lambda-call/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Вспомним функцию `square` из прошлого урока: 3 | 4 | ```scheme 5 | (define square (lambda (n) (* n n))) 6 | (square 5) ; 25 7 | ``` 8 | 9 | Попробуйте ответить на вопрос, можно ли вызывать функцию сразу после объявления без использования `define`? 10 | 11 | Конечно можно, как и во всех других языках с поддержкой анонимных функций. Для этого достаточно в форме вызова подставить вместо имени саму функцию: 12 | 13 | ```scheme 14 | ((lambda (n) (* n n)) 5) 15 | ``` 16 | 17 | Необычность этой структуры в том, что здесь первый элемент не идентификатор, а выражение, возвращающее функцию (а определение анонимной функции возвращает саму функцию). Из-за этого возникает двойная скобка. Проще всего это понять, если всегда воспринимать скобки как вызов. В дальнейшем при работе со списками, как с данными, мы увидим, что это не всегда так, но на данном этапе такое упрощение полезно. 18 | 19 | В форме вызова сразу после функции перечисляются параметры. В данном примере параметр только один: это 5. Так будет выглядеть пример вызова функции с двумя аргументами: 20 | 21 | ```scheme 22 | ((lambda (x y) (+ x y)) 23 | 8 7) ; 15 24 | ``` 25 | 26 | Вызов функции - обычное выражение, это значит что его можно использовать во всех местах, где возможно появление выражения. В Lisp-языках это почти любые части программы: 27 | 28 | ```scheme 29 | (displayln ((lambda (x y) (+ x y)) 30 | 8 7)) ; => 15 31 | ``` 32 | -------------------------------------------------------------------------------- /modules/20-definitions/20-definitions-lambda-call/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Вызов функции без define 3 | definitions: [] 4 | -------------------------------------------------------------------------------- /modules/20-definitions/20-definitions-lambda-call/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require "../../../src/tests.rkt") 4 | 5 | (assert-output "3") 6 | -------------------------------------------------------------------------------- /modules/20-definitions/25-function-shorthand/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/20-definitions/25-function-shorthand/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (sum-of-squares x y) (+ (* x x) (* y y))) 7 | #| END |# 8 | -------------------------------------------------------------------------------- /modules/20-definitions/25-function-shorthand/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Создайте функцию с именем `sum-of-squares` (используя короткий синтаксис), которая находит сумму квадратов двух чисел. 3 | 4 | ```scheme 5 | (sum-of-squares 2 3) ; 13 6 | ``` 7 | -------------------------------------------------------------------------------- /modules/20-definitions/25-function-shorthand/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Создание функций - настолько частая операция, что в Racket была добавлена сокращенная версия определения (с одновременным объявлением) функции с помощью `define`. Возьмем для примера определение функции возведения в квадрат: 3 | 4 | ```scheme 5 | (define square (lambda (n) (* n n))) 6 | ``` 7 | 8 | А теперь посмотрим сокращенную версию этого же определения: 9 | 10 | ```scheme 11 | (define (square n) (* n n)) 12 | (square 3) ; 9 13 | ``` 14 | 15 | Первое что бросается в глаза - отсутствие слова `lambda`. Вместо него, после `define` указывается список, в котором первый элемент это имя функции, остальные - параметры. Затем идет тело функции. Объявленная выше функция возведения в квадрат принимает один аргумент - `n`. Пример объявления функции с двумя аргументами: 16 | 17 | ```scheme 18 | (define (sum x y) (+ x y)) 19 | (sum 3 5) ; 8 20 | ``` 21 | 22 | Несмотря на наличие такого вида записи объявлений функций, нужно не забывать, что в Racket нет именованных функций. `define`, это всегда сопоставление имени со значением, но в роли последнего может выступать и функция. 23 | -------------------------------------------------------------------------------- /modules/20-definitions/25-function-shorthand/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Сокращенный синтаксис создания функции 3 | definitions: [] 4 | -------------------------------------------------------------------------------- /modules/20-definitions/25-function-shorthand/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (sum-of-squares 2 3) 13) 8 | (check-equal? (sum-of-squares 6 5) 61)) 9 | -------------------------------------------------------------------------------- /modules/20-definitions/30-modules/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/20-definitions/30-modules/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | #| BEGIN |# 4 | (provide telephone) 5 | #| END |# 6 | 7 | (define telephone "iphone") 8 | -------------------------------------------------------------------------------- /modules/20-definitions/30-modules/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Экспортируйте объявление, которое есть в загруженном модуле. 3 | -------------------------------------------------------------------------------- /modules/20-definitions/30-modules/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Система модулей в Racket похожа на подобные системы в других языках. Каждый файл обычно содержит ровно один модуль: 3 | 4 | ```scheme 5 | ; math.rkt 6 | #lang racket 7 | 8 | (define (sum a b) (+ a b)) 9 | ``` 10 | 11 | По умолчанию все объявления, сделанные в модуле, остаются внутри модуля. Импорт объявлений из других модулей (читай: файлов) происходит с помощью формы `require`: 12 | 13 | ```scheme 14 | (require "math.rkt") 15 | ``` 16 | 17 | Однако вы не сможете использовать объявления другого модуля, если модуль не экспортирует их явно. Для экспорта объявлений используется форма `provide`: 18 | 19 | ```scheme 20 | ; math.rkt 21 | #lang racket 22 | 23 | (provide sum) 24 | 25 | (define (sum a b) (+ a b)) 26 | ``` 27 | 28 | В `provide` перечисляются имена объявлений, которые нужно экспортировать. Любой другой модуль автоматически получает доступ ко всем экспортируемым объявлениям при импорте: 29 | 30 | ```scheme 31 | #lang racket 32 | 33 | (require "math.rkt") 34 | 35 | (define result (sum 5 3)) 36 | ``` 37 | 38 | При экспорте можно так же указать форму `(provide (all-defined-out))`. Таким образом мы экспортируем *все объявления модуля*. 39 | 40 | Обратите внимание на следующие детали: 41 | 42 | * `require` работает с путями, ей можно передать как абсолютный так и относительный путь. В примере выше предполагается что оба модуля лежат в одной директории. 43 | 44 | * `require` автоматически делает доступным всё, что в импортируемом модуле указано в `provide`. Это бывает неудобно: во-первых, возможны конфликты имен, во-вторых, хочется явно понимать что откуда берется. Поэтому существует альтернативный способ вызова `require`: 45 | 46 | ```scheme 47 | (require (only-in "math.rkt" sum)) 48 | ``` 49 | 50 | `only-in` говорит что надо включить из модуля `"math.rkt"` переменную `sum` и больше ничего. Для включения дополнительных объявлений достаточно добавить их имена в конец списка. 51 | -------------------------------------------------------------------------------- /modules/20-definitions/30-modules/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Модули 3 | definitions: [] 4 | tips: [] 5 | -------------------------------------------------------------------------------- /modules/20-definitions/30-modules/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? telephone "iphone")) 8 | -------------------------------------------------------------------------------- /modules/20-definitions/35-let/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/20-definitions/35-let/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (square-of-sum x y) 7 | (let ([sum (+ x y)]) 8 | (* sum sum))) 9 | #| END |# 10 | -------------------------------------------------------------------------------- /modules/20-definitions/35-let/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `square-of-sum`, которая сначала складывает числа, а затем возводит в квадрат. Воспользуйтесь локальными объявлениями для хранения промежуточных результатов вычисления. 3 | 4 | ```scheme 5 | (square-of-sum 2 3) ; 25 6 | ``` 7 | -------------------------------------------------------------------------------- /modules/20-definitions/35-let/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | `define` в Racket может использоваться как на уровне модуля, так и внутри функций: 3 | 4 | ```scheme 5 | (define (f) 6 | (define text "lorem") 7 | (displayln text)) 8 | 9 | (f) ; => "lorem" 10 | ; у `define text` локальная область видимости 11 | (displayln text) ; 12 | ``` 13 | 14 | Но с ним связано несколько тонких моментов: 15 | 16 | * Хотя лиспы похожи между собой, конкретно `define` ведет себя совершенно по-разному в разных диалектах Lisp. В некоторых объявления останутся локальными для текущей области видимости, в других же объявление всегда будет глобальным. 17 | * Объявления переменных должны идти в самом начале функции, до любых других выражений. 18 | 19 | Существует и другой способ объявить локальные переменные, гораздо более популярный и предсказуемый: 20 | 21 | ```scheme 22 | (let ([text "lorem"]) (displayln text)) ; => lorem 23 | ``` 24 | 25 | Каждое объявление в `let` - это список из имени и выражения, вычисленное значение которого будет ассоциировано с именем. В наших примерах такие объявления записываются в квадратных скобках исключительно для удобства. Интерпретатору практически всегда понятно, что мы имеем в виду, вне зависимости от того, какие скобки мы использовали (Да-да, в Racket практически везде можно использовать квадратные скобки вместо круглых!). Перепишем уже знакомую нам функцию `sum`, используя локальные объявления: 26 | 27 | ```scheme 28 | ; форма записи без локальных объявлений: 29 | (define sum (lambda (x y) (+ x y))) 30 | (sum 8 7) ; вызов глобальной функции 31 | 32 | ; форма записи с локальными объявлениями: 33 | (let ([sum (lambda (x y) (+ x y))]) 34 | (sum 8 7)) ; вызов локальной функции 35 | ``` 36 | 37 | Все объявления внутри `let` доступны только в выражениях, которые вызываются внутри самого `let` *после списка объявлений* (Объявления не видят друг друга! Подробнее - ниже). Вот ещё несколько более сложных примеров, с несколькими объявлениями: 38 | 39 | ```scheme 40 | (let ([x 2] 41 | [y (+ 4 3)]) 42 | (+ x y)) ; 9 43 | ``` 44 | 45 | ```scheme 46 | (define (sum-of-squares x y) 47 | (let ([x-square (* x x)] 48 | [y-square (* y y)]) 49 | (+ x-square y-square))) 50 | ``` 51 | 52 | Можно пойти еще дальше - вызывать локальную функцию внутри глобальной: 53 | 54 | ```scheme 55 | (define (sum-of-squares x y) 56 | (let ([square (lambda (n) (* n n))]) 57 | (+ (square x) (square y)))) 58 | 59 | (sum-of-squares 8 7) ; => 113 60 | ``` 61 | 62 | Объявления, созданные в рамках одного использования `let`, не видны друг другу. Поэтому мы не можем сделать так: 63 | 64 | ```scheme 65 | (let ([x 2] 66 | [y (* x 10)]) ; здесь - ошибка, потому что 67 | ; переменная x ещё не объявлена. 68 | (+ x y)) 69 | ``` 70 | 71 | Форма `let` так устроена, что каждое объявление из списка она создаёт "с чистого листа", поэтому объявления не зависят друг от друга, что иногда удобно. Скажем, мы можем свободно менять местами объявления в пределах одного списка. Если же нам непременно нужно использовать в последующих объявлениях предыдущие, мы можем воспользоваться формой `let*`: 72 | 73 | ```scheme 74 | (let* ([x 2] 75 | [y (* x 20)] 76 | [z (+ x y)]) 77 | z) ; 42 78 | ``` 79 | -------------------------------------------------------------------------------- /modules/20-definitions/35-let/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Локальные объявления 3 | definitions: [] 4 | tips: [] 5 | -------------------------------------------------------------------------------- /modules/20-definitions/35-let/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (square-of-sum 2 3) 25) 8 | (check-equal? (square-of-sum 5 8) 169)) 9 | -------------------------------------------------------------------------------- /modules/20-definitions/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Объявления 4 | description: | 5 | 6 | В этом модуле рассматривается объявление переменных и функций. 7 | -------------------------------------------------------------------------------- /modules/30-logic/10-boolean-type/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/30-logic/10-boolean-type/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (same-parity? x y) (equal? (even? x) (even? y))) 7 | #| END |# 8 | -------------------------------------------------------------------------------- /modules/30-logic/10-boolean-type/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `same-parity?`, которая принимает на вход два числа и возвращает `#t` в том случае, если их четность совпадает. В ином случае возвращается `#f`. 3 | 4 | > Воспользуйтесь логическими операторами и встроенными функциями [odd?](https://docs.racket-lang.org/reference/number-types.html#%28def._%28%28quote._~23~25kernel%29._odd~3f%29%29) и [even?](https://docs.racket-lang.org/reference/number-types.html#%28def._%28%28quote._~23~25kernel%29._even~3f%29%29). 5 | 6 | ```scheme 7 | (same-parity? 3 7) ; #t 8 | (same-parity? 4 8) ; #t 9 | (same-parity? 4 7) ; #f 10 | (same-parity? 3 10) ; #f 11 | ``` 12 | -------------------------------------------------------------------------------- /modules/30-logic/10-boolean-type/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | True и False в Racket представлены значениями `#t` и `#f`. Запись непривычная, но в языках, созданных много лет назад, встречается и не такое. Большинство операций в Racket рассматривают как ложь только `#f`. Всё остальное считается истиной. Пара примеров проверки на равенство: 3 | 4 | ```scheme 5 | (equal? 42 42) ; #t 6 | (equal? 42 24) ; #f 7 | ``` 8 | 9 | Равенство значений проверяется через функцию `equal?`. Иногда может потребоваться сравнение по ссылке, в таком случае используют `eq?`. 10 | 11 | Напишем функцию `gt?`, которая возвращает `#t`, если первое число больше второго и `#f` в другом случае. В Racket имена предикатов заканчиваются вопросительным знаком. При этом к ним не добавляется префикс "is". 12 | 13 | ```scheme 14 | (define (gt? x y) (> x y)) 15 | (gt? 3 2) ; #t 16 | (gt? 10 15) ; #f 17 | ``` 18 | 19 | _Вот так разработчики на Ruby узнали, почему в их языке предикаты выглядят как вопросы :)_ 20 | 21 | Теперь напишем предикат, определяющий четность числа. Для этого нам понадобится функция `remainder`, которая вычисляет остаток от деления. 22 | 23 | ```scheme 24 | (define (even? n) (= (remainder n 2) 0)) 25 | (even? 3) ; #f 26 | (even? 4) ; #t 27 | ``` 28 | 29 | > Строго говоря, такая функция уже есть в языке. Здесь мы её реализуем заново только ради примера. 30 | 31 | Логические операторы в Racket не имеют символьных обозначений, вместо этого используются функции `and`, `or`, `not` и другие. 32 | 33 | ```scheme 34 | (not "moon") ; #f 35 | (and (odd? 3) (even? 4)) ; #t 36 | ``` 37 | 38 | Как и в случае с арифметическими операциями, мы получаем два бонуса: 39 | 40 | 1. Префиксная нотация позволяет комбинировать любое число условий: `(and <...>)`. 41 | 1. Благодаря древовидной структуре исходного кода приоритет всегда точно определен. 42 | -------------------------------------------------------------------------------- /modules/30-logic/10-boolean-type/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Логические операторы 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/30-logic/10-boolean-type/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-true check-false test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-true (same-parity? 3 7)) 8 | (check-true (same-parity? 4 8)) 9 | (check-false (same-parity? 4 7)) 10 | (check-false (same-parity? 3 10))) 11 | -------------------------------------------------------------------------------- /modules/30-logic/20-if/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/30-logic/20-if/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (sentence-type text) (if (equal? (string-upcase text) text) 7 | "cry" 8 | "common")) 9 | #| END |# 10 | -------------------------------------------------------------------------------- /modules/30-logic/20-if/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `sentence-type`, которая возвращает строку `"cry"`, если переданый текст написан заглавными буквами, и возвращает строку `"common"` в остальных случаях. 3 | 4 | ```scheme 5 | (sentence-type "HOW ARE YOU?") ; "cry" 6 | (sentence-type "Hello, world!") ; "common" 7 | ``` 8 | 9 | Для перевода строки в верхний регистр используйте функцию `string-upcase`. 10 | -------------------------------------------------------------------------------- /modules/30-logic/20-if/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | > (if test-expr then-expr else-expr) 3 | 4 | Условная конструкция в Racket — это специальная форма, в которой 4 элемента. Первый — `if`, затем — выражение-предикат (`test-expr`). Если предикат вернул истину, то выполняется `then-expr`, иначе `else-expr`. В Racket `if` всегда содержит ветку `else`. 5 | 6 | ```scheme 7 | (if (> 3 2) "yes" "no") ; "yes" 8 | ``` 9 | 10 | Подчеркну, что форма `if` — это выражение, а значит у неё есть результат. Это резко отличается от большинства привычных языков, в которых `if` – это специальная инструкция. Выражения делают код проще, а его возможности – шире. Все дальнейшие формы, которые мы рассмотрим, также будут выражениями. 11 | 12 | Почему `if` называется особой формой? Давайте разберем на примере. 13 | 14 | ```scheme 15 | (if (> 3 2) (displayln "yes") (displayln "no")) ; => yes 16 | ``` 17 | 18 | Что напечает на экран эта программа? В нормальных формах сначала вычисляются все элементы формы, а затем уже сама форма. Это так называемый аппликативный порядок вычисления. Он используется в большинстве языков и привычен для большинства программистов. Мы ожидаем, что аргументы функции вычисляются до того как попадут в функцию. 19 | 20 | Но в случае с примером выше поведение другое. Оно зависит от результата предиката. То есть в форме `if` выполняется ровно то выражение, которое должно выполняться по логике формы `if`: первое, если предикат вернул истину, и второе, если ложь. Именно поэтому форма является особой. Подобную форму невозможно реализовать на самом языке без механизма макросов (а с макросами — можно). 21 | 22 | _Существует и другой порядок, так называемый нормальный порядок вычисления. Самый известный язык, использующий его – Haskell. В этом языке ничто не вычисляется до тех пор, пока не понадобится результат вычисления._ 23 | -------------------------------------------------------------------------------- /modules/30-logic/20-if/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Условная конструкция 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/30-logic/20-if/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin )) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (sentence-type "HOW?") "cry") 8 | (check-equal? (sentence-type "HoW?") "common")) 9 | -------------------------------------------------------------------------------- /modules/30-logic/30-guards/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/30-logic/30-guards/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (say-boom v) (when (equal? v "go") "Boom!")) 7 | #| END |# 8 | -------------------------------------------------------------------------------- /modules/30-logic/30-guards/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `say-boom`, которая возвращает строку _Boom!_, если её вызвали с параметром `"go"`. 3 | 4 | ```scheme 5 | (say-boom "hey") 6 | (say-boom "go") ; "Boom!" 7 | ``` 8 | -------------------------------------------------------------------------------- /modules/30-logic/30-guards/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Обычного `if` без `else` в Racket нет, но есть две специальные формы: `when` и `unless`, предназначенные для этой цели. 3 | 4 | ### When 5 | 6 | > `(when test-expr body ...+)` 7 | 8 | Если результат `test-expr` истина, то вычисляется тело. 9 | 10 | ```scheme 11 | (when (positive? -5) 12 | (display "hi")) 13 | 14 | (when (positive? 5) 15 | (display "hi") 16 | (display " there")) 17 | ``` 18 | 19 | ### Unless 20 | 21 | > То же, что и `(when (not test-expr) body ...+)`. 22 | 23 | Форма `unless` работает наоборот. Тело вычисляется в том случае, если `test-expr` – ложь. Использование `unless` хоть и бывает удобно, но код резко становится нечитаемым, когда в `test-expr` появляются составные условия. 24 | 25 | ```scheme 26 | (unless (positive? 5) 27 | (display "hi")) 28 | (unless (positive? -5) 29 | (display "hi") 30 | (display " there")) 31 | ``` 32 | -------------------------------------------------------------------------------- /modules/30-logic/30-guards/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: When и Unless 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/30-logic/30-guards/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (say-boom "HOW?") (void)) 8 | (check-equal? (say-boom "go") "Boom!")) 9 | -------------------------------------------------------------------------------- /modules/30-logic/35-case/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/30-logic/35-case/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (humanize-permission v) 7 | (case v 8 | [("x") "execute"] 9 | [("w") "write"] 10 | [("r") "read"])) 11 | #| END |# 12 | -------------------------------------------------------------------------------- /modules/30-logic/35-case/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `humanize-permission`, которая принимает на вход символьное обозначение прав доступа в Unix-системах и возвращает текстовое описание. 3 | 4 | ```scheme 5 | (humanize-permission "x") ; "execute" 6 | (humanize-permission "r") ; "read" 7 | (humanize-permission "w") ; "write" 8 | ``` 9 | -------------------------------------------------------------------------------- /modules/30-logic/35-case/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Вместо `switch` в Racket ипользуется `case`. В общем случае `case` по своим возможностям шире, чем `switch` в большинстве языков программирования. Его использование в качестве `switch` — это наиболее простой способ познакомиться с ним. 3 | 4 | ```scheme 5 | (let ([v 0]) 6 | (case v 7 | [(0) "zero"] 8 | [(1) "one"] 9 | [(2) "two"] 10 | [(3 4 5) "many"])) 11 | ; "zero" 12 | ``` 13 | 14 | Каждая ветка в `case` описывается квадратными скобками, где в левой части — список из одного или нескольких элементов. Эти элементы и есть ожидаемые значения. В правой части ветки находится возвращаемое значение. 15 | 16 | Для поведения по умолчанию в самом конце используется часть `else`: 17 | 18 | ```scheme 19 | (case 6 20 | [(0) "zero"] 21 | [(1) "one"] 22 | [(2) "two"] 23 | [else "many"]) 24 | ; "many" 25 | ``` 26 | -------------------------------------------------------------------------------- /modules/30-logic/35-case/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Case 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/30-logic/35-case/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin )) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (humanize-permission "r") "read") 8 | (check-equal? (humanize-permission "w") "write") 9 | (check-equal? (humanize-permission "x") "execute")) 10 | -------------------------------------------------------------------------------- /modules/30-logic/40-cond/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/30-logic/40-cond/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (programmer-level points-count) 7 | (cond 8 | [(< points-count 10) "junior"] 9 | [(and (>= points-count 10) (< points-count 20)) "middle"] 10 | [else "senior"])) 11 | #| END |# 12 | -------------------------------------------------------------------------------- /modules/30-logic/40-cond/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `programmer-level`, которая принимает на вход количество баллов и возвращает уровень разработчика на основе этого числа. Если баллов от 0 до 10, то это junior, от 10 до 20 – middle, от 20 и выше – senior. 3 | 4 | ```scheme 5 | (programmer-level 10) ; "middle" 6 | (programmer-level 0) ; "junior" 7 | (programmer-level 40) ; "senior" 8 | ``` 9 | -------------------------------------------------------------------------------- /modules/30-logic/40-cond/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Для самых сложных случаев, там, где обычно применяется _if-else_if_, в Racket есть еще одна форма — `cond`. 3 | 4 | ```scheme 5 | (cond 6 | [(positive? -5) "first return"] 7 | [(zero? -5) "second return"] 8 | [(positive? 5) "third return"] 9 | [else "boom!"]) 10 | ``` 11 | 12 | Эта форма напоминает case, только в левой части внутри квадратных скобок находится предикат. Если его результат — истина, то выполняется правая часть и её результат возвращается из `cond`. 13 | 14 | Если необходимо, в конце добавляется `else`, который ведет себя аналогично `else` в других языках. 15 | -------------------------------------------------------------------------------- /modules/30-logic/40-cond/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Cond 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/30-logic/40-cond/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin )) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (programmer-level 3) "junior") 8 | (check-equal? (programmer-level 18) "middle") 9 | (check-equal? (programmer-level 40) "senior")) 10 | -------------------------------------------------------------------------------- /modules/30-logic/50-expressions/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/30-logic/50-expressions/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (do-today day-of-week) 7 | (cond 8 | [(and (integer? day-of-week) 9 | (<= 1 day-of-week 7)) 10 | (case day-of-week 11 | [(6 7) "rest"] 12 | [else "work"])] 13 | [else "???"])) 14 | #| END |# 15 | -------------------------------------------------------------------------------- /modules/30-logic/50-expressions/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `do-today`, которая принимает порядковый номер дня недели (целое число) в качестве аргумента и вычисляется в: 3 | 4 | - строку `"work"` для дней с понедельника (`1`) по пятницу (`5`), 5 | - строку `"rest"` для субботы (`6`) и воскресенья (`7`), 6 | - строку `"???"` для всех остальных значений, в том числе и для нечисловых! 7 | 8 | Попробуйте использовать в решении различные комбинации `if`, `cond` и `case`. 9 | -------------------------------------------------------------------------------- /modules/30-logic/50-expressions/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Во многих языках, для того чтобы внутри вычисляемых выражений использовать логическое ветвление, приходится использовать специфические варианты конструкции `if` (как это сделано в Python) или же вовсе особые *тернарные операторы*. 3 | 4 | Здесь лиспоподобные языки — и Racket в частности — обладают одним важным преимуществом: в этих языках всё есть выражение. Поэтому отдельные варианты условных конструкций не нужны, вместо этого можно использовать `if`, `case`, `cond` как часть любого другого выражения! Вот парочка примеров: 5 | 6 | ```scheme 7 | (displayln (if #t "Ok" "Oops")) ; => Ok 8 | (displayln (when #f "Ok")) ; => # 9 | ``` 10 | 11 | Заметьте, во втором случае условие для `when` было ложным, поэтому всё выражение `when` вычислилось в специальное "пустое" значение, но тем не менее вычислилось! И функция `displayln` вывела это значение на экран, пусть даже и в таком своеобразном виде. 12 | 13 | Если помнить об этом свойстве языка, то можно писать довольно-таки сложные выражения, не выделяя промежуточные вычисления в переменные. 14 | 15 | ```scheme 16 | (define (classify x) 17 | (cond 18 | [(< x 0) "Negative"] 19 | [(case x 20 | [(13 42 100500) #t] 21 | [else #f]) "Special"] 22 | [else "Boring"])) 23 | ``` 24 | 25 | В этом примере используются сильные стороны `cond` и `case`: первый хорошо справляется с выбором по условию, а второй хорошо проверяет на совпадение с конкретными значениями, выступая при этом в качестве того самого условия для `cond`. 26 | 27 | И раз уж речь зашла о выносе подвыражений в переменные, то тут у Racket тоже всё хорошо. В других языках иной раз приходится делать присваивание значений переменным из условной конструкции или обходиться тернарным оператором. В Racket же можно использовать обычный `let`: 28 | 29 | ```scheme 30 | (define (classify x) 31 | (let ([is-special 32 | (case x 33 | [(13 42 100500) #t] 34 | [else #f])]) 35 | (cond 36 | [(< x 0) "Negative"] 37 | [is-special "Special"] 38 | [else "Boring"]))) 39 | ``` 40 | 41 | > Этот вариант читается лучше, чем предыдущий, но имеет один недостаток: значение `is-special` вычисляется в любом случае, тогда как в предыдущем варианте его вычислять не нужно, если выполнилось условие, проверяющее аргумент на отрицательность. 42 | -------------------------------------------------------------------------------- /modules/30-logic/50-expressions/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Логические выражения 3 | tips: 4 | - > 5 | Используйте функцию-предикат `integer?` чтобы проверить, что аргумент — 6 | целое число. 7 | -------------------------------------------------------------------------------- /modules/30-logic/50-expressions/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (do-today 1) "work") 8 | (check-equal? (do-today 2) "work") 9 | (check-equal? (do-today 3) "work") 10 | (check-equal? (do-today 4) "work") 11 | (check-equal? (do-today 5) "work") 12 | (check-equal? (do-today 6) "rest") 13 | (check-equal? (do-today 7) "rest") 14 | (check-equal? (do-today 0) "???") 15 | (check-equal? (do-today -1) "???") 16 | (check-equal? (do-today 10) "???") 17 | (check-equal? (do-today #f) "???") 18 | (check-equal? (do-today "oops") "???")) 19 | -------------------------------------------------------------------------------- /modules/30-logic/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Логика 4 | description: | 5 | 6 | Булева алгебра, условные выражения, конструкции ветвления и выбора — то, без чего невозможна ни одна сколько-нибудь сложная программа. Racket имеет свои интересные особенности, когда речь заходит о программировании булевой логики: об этом и рассказывает данный модуль. 7 | -------------------------------------------------------------------------------- /modules/40-lists/10-intro/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/40-lists/10-intro/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (triple x) 7 | (list x x x)) 8 | #| END |# 9 | -------------------------------------------------------------------------------- /modules/40-lists/10-intro/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `triple`, которая должна принимать один аргумент любого типа и возвращать список, в котором содержатся три копии аргумента. 3 | 4 | ```scheme 5 | (triple "a") ; '("a" "a" "a") 6 | (triple 0) ; '(0 0 0) 7 | ``` 8 | -------------------------------------------------------------------------------- /modules/40-lists/10-intro/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Функция `list` и цитирование 3 | 4 | В языке, призванном работать со списками, просто обязан существовать синтаксис для объявления литералов списков. Проще всего воспользоваться функцией с именем `list`: 5 | 6 | ```scheme 7 | (list 1 2 3) ; '(1 2 3) 8 | (list "apple" "orange") ; '("apple" "orange") 9 | (list 1 (list 2 3) 4) ; '(1 (2 3) 4) 10 | ``` 11 | 12 | Заметьте, что результаты вычисления показываются без упоминания имени функции "list", но зато с одинарной кавычкой перед открывающей скобкой. 13 | 14 | Дело в том, что в Racket можно взять любое выражение в скобках и сказать интерпретатору: "Не вычисляй его, а воспринимай как список". Такая операция называется quotation или "цитирование", а одинарная кавычка означает начало цитаты. Заканчивается же цитата там, где стоит закрывающая скобка, парная для той, что стоит после кавычки. 15 | 16 | Подробнее про цитирование вы узнаете позже. Пока же рекомендуем вам использовать именно функцию `list`, потому что это *функция*, а не специальный синтаксис. 17 | 18 | Если ещё раз посмотреть на примеры выше, то последний может навести на мысль: списки в Racket *гетерогенные*, то есть любой список может одновременно содержать значения разных типов. В упомянутом примере в одном списке содержатся и числа, и вложенный список. 19 | 20 | ### Функция-предикат `list?` 21 | 22 | Чтобы узнать, является ли какое-то значение списком, используют функцию `list?`. Это распространённый приём — для каждого типа иметь функцию-тест с тем же именем и знаком вопроса в конце! Для строк — это `string?`, для целых чисел — это `integer?` и так далее. 23 | -------------------------------------------------------------------------------- /modules/40-lists/10-intro/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Объявление списков 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/40-lists/10-intro/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? 8 | (triple 0) 9 | (list 0 0 0)) 10 | 11 | (check-equal? 12 | (triple "a") 13 | (list "a" "a" "a")) 14 | 15 | (check-equal? 16 | (triple (triple 0)) 17 | (list (list 0 0 0) 18 | (list 0 0 0) 19 | (list 0 0 0)))) 20 | -------------------------------------------------------------------------------- /modules/40-lists/20-builtin-loops-map/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/40-lists/20-builtin-loops-map/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (maps fs as) (map map fs as)) 7 | #| END |# 8 | -------------------------------------------------------------------------------- /modules/40-lists/20-builtin-loops-map/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `maps`, которая должна принимать два списка — список функций и *список списков* аргументов — и возвращать список результатов применения функций к наборам аргументов. Вот как использование `maps` должно выглядеть: 3 | 4 | ```scheme 5 | (maps 6 | (list 7 | add1 8 | string?) 9 | (list 10 | (list 10 20) 11 | (list "a" 0))) 12 | ; '((11 21) (#t #f)) 13 | ``` 14 | 15 | Здесь: 16 | 17 | - `'(11 21)` — это результат применения `add1` к `(list 10 20)`; 18 | - `'(#t #f)` — это результат применения `string?` к `(list "a" 0)`. 19 | -------------------------------------------------------------------------------- /modules/40-lists/20-builtin-loops-map/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Любой список рано или поздно захочется *обойти* (*traverse*), то есть поработать с отдельными элементами. В процедурных языках используются циклы, но многие языки имеют и декларативные средства работы с коллекциями: `map`, `filter`, `reduce`. А ведь сами эти функции пришли в программирование через Lisp! 3 | 4 | Racket тоже предоставляет полный набор таких функций. Ближайшие несколько уроков будут посвящены им. 5 | 6 | ### `map` 7 | 8 | Итак, `map` в Racket используется так: 9 | 10 | ```scheme 11 | (map add1 (list 1 2 3)) ; '(2 3 4) 12 | ``` 13 | 14 | Здесь `add1` — встроенная функция, добавляющая к числу единицу. Всё максимально предсказуемо: `map` превращает старый список в новый, применяя функцию к каждому элементу. Обход получается *функциональный*, потому что мы получаем **новый** список, не меняя старый. 15 | 16 | Может `map` обходить и несколько списков одновременно: если применить `map` к нескольким спискам, то функция-аргумент будет применена ко всем первым элементам, затем ко всем вторым и так далее. Но `map` потребует от входных списков иметь одинаковую длину, иначе вы получите ошибку. 17 | 18 | Вот так можно поэлементно просуммировать три списка: 19 | 20 | ```scheme 21 | (map + 22 | (list 1 2 3) 23 | (list 10 20 30) 24 | (list 100 200 300)) 25 | ; '(111 222 333) 26 | ``` 27 | 28 | Заметьте, не потребовалось даже использовать анонимную функцию, которая складывала бы три числа, ведь функция "`+`" принимает произвольное количество аргументов! 29 | -------------------------------------------------------------------------------- /modules/40-lists/20-builtin-loops-map/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Встроенные средства обхода списков, map 3 | tips: 4 | - > 5 | [map](https://docs.racket-lang.org/reference/pairs.html#%28def._%28%28lib._racket%2Fprivate%2Fmap..rkt%29._map%29%29) 6 | 7 | [list](https://docs.racket-lang.org/reference/pairs.html#%28def._%28%28quote._~23~25kernel%29._list%29%29) 8 | -------------------------------------------------------------------------------- /modules/40-lists/20-builtin-loops-map/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? 8 | (maps 9 | (list) 10 | (list)) 11 | (list)) 12 | 13 | (check-equal? 14 | (maps (list add1) 15 | (list (list 0))) 16 | (list (list 1))) 17 | 18 | (check-equal? 19 | (maps 20 | (list add1 string?) 21 | (list (list 0 100) 22 | (list "foo" 42))) 23 | (list (list 1 101) 24 | (list #t #f)))) 25 | -------------------------------------------------------------------------------- /modules/40-lists/30-builtin-filter/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/40-lists/30-builtin-filter/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (increment-numbers xs) 7 | (map add1 (filter number? xs))) 8 | #| END |# 9 | -------------------------------------------------------------------------------- /modules/40-lists/30-builtin-filter/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `increment-numbers`, которая берёт из списка-аргумента значения, являющиеся числами (`number?`), и возвращает список этих чисел, увеличив предварительно каждое число на единицу (`add1`). Пример: 3 | 4 | ```scheme 5 | (increment-numbers (list 10 "foo" #f (list 2 3) 3/5)) ; '(11 8/5) 6 | ``` 7 | 8 | > Заметьте, Racket умеет работать с дробями вроде `3/5` и `8/5`! 9 | -------------------------------------------------------------------------------- /modules/40-lists/30-builtin-filter/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Функция `map` может менять элементы списка, но не может менять их количество: сколько элементов было в исходном списке, столько же останется и в результирующем. Функция `filter` же, напротив, не может менять сами элементы, но может решать, какие из них попадут в выходной список, а какие будут отброшены. Так `map` и `filter`, выполняя каждая свою задачу, дополняют друг друга! 3 | 4 | ### `filter` 5 | 6 | Функция `filter` принимает два аргумента: 7 | 8 | 1. Функцию-предикат, которая должна вернуть `#t` для тех элементов, которые нужно оставить. 9 | 2. Список элементов для последующего отбора. 10 | 11 | Результатом вызова `filter` будет *новый* список, содержащий элементы, одобренные аргументом-предикатом. 12 | 13 | Благодаря тому, что в Racket доступно великое множество функций-предикатов, использовать `filter` легко и удобно: 14 | 15 | ```scheme 16 | (filter integer? (list 1 2.5 "foo" 7)) ; '(1 7) 17 | (filter positive? (list -1 5 42 0 -100 3)) ; '(5 42 3) 18 | ``` 19 | -------------------------------------------------------------------------------- /modules/40-lists/30-builtin-filter/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Фильтрация списков, filter 3 | tips: 4 | - > 5 | [filter](https://docs.racket-lang.org/reference/pairs.html#%28def._%28%28lib._racket%2Fprivate%2Flist..rkt%29._filter%29%29) 6 | 7 | [number?](https://docs.racket-lang.org/reference/number-types.html#%28def._%28%28quote._~23~25kernel%29._number~3f%29%29) 8 | -------------------------------------------------------------------------------- /modules/40-lists/30-builtin-filter/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? 8 | (increment-numbers (list)) 9 | (list)) 10 | 11 | (check-equal? 12 | (increment-numbers (list "a" "b" #f)) 13 | (list)) 14 | 15 | (check-equal? 16 | (increment-numbers (list 1/2)) 17 | (list 3/2)) 18 | 19 | (check-equal? 20 | (increment-numbers (list 1 1/2 "foo")) 21 | (list 2 3/2))) 22 | -------------------------------------------------------------------------------- /modules/40-lists/40-foldl/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/40-lists/40-foldl/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (max-delta xs ys) 7 | (foldl (lambda [x y m] (max m (abs (- x y)))) 8 | 0 xs ys)) 9 | #| END |# 10 | -------------------------------------------------------------------------------- /modules/40-lists/40-foldl/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `max-delta`, которая должна принимать два списка чисел и вычислять максимальную разницу (абсолютное значение разницы) между соответствующими парами элементов. 3 | 4 | Пример использования: 5 | 6 | ```scheme 7 | (max-delta 8 | (list 10 -15 35) 9 | (list 2 -12 42)) ; 8 10 | ``` 11 | 12 | Вам пригодятся функции `abs` и `max`: 13 | 14 | ```scheme 15 | (abs 42) ; 42 16 | (abs -13) ; 13 17 | (max 1 5 3) ; 5 18 | ``` 19 | -------------------------------------------------------------------------------- /modules/40-lists/40-foldl/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Функции `map` и `filter` обрабатывают списки, сохраняя саму структуру. Но иногда нужно избавиться от этой самой структуры, вычислив какое-то итоговое значение. Простейший пример — сумма всех чисел в списке. Или текст, собранный из списка строк. 3 | 4 | В процедурных языках для получения итоговых значений по списку проходят с использованием цикла и промежуточный результат хранят в отдельной переменной — в так называемом *аккумуляторе*. 5 | 6 | Декларативным же аналогом такого цикла будет операция *сворачивания* (*folding*) или, как ещё говорят, получение *свёртки* (*fold*). Суть сворачивания списка заключается в последовательном применении некоторой *бинарной операции* к очередному элементу списка и текущему значению аккумулятора у с целью получить новое значение аккумулятора. Давайте рассмотрим процесс сворачивания списка `(list 1 2 3 4)` в сумму чисел. Начальным значением аккумулятора будет `0`, а операцией — `+`. Сложить числа можно как минимум двумя способами: 7 | 8 | 1. Двигаясь от первого элемента к последнему, **слева** направо. 9 | 10 | ``` 11 | (((0 + 1) + 2) + 3) + 4 12 | ``` 13 | 14 | 2. Двигаясь от последнего элемента к первому, **справа** налево. 15 | 16 | ``` 17 | 1 + (2 + (3 + (4 + 0))) 18 | ``` 19 | 20 | Для операции сложения не имеет значения то, какой из вариантов мы выберем, потому что операция сложения [ассоциативна](https://ru.wikipedia.org/wiki/%D0%90%D1%81%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F). Но далеко не все операции таковы: например, при конкатенации строк важно, последнюю мы будем с первой складывать или наоборот! 21 | 22 | Поэтому в Racket существуют оба способа сворачивания в виде функций `foldl` и `foldr` — левая и правая свёртки (способы "1" и "2" соответственно). 23 | 24 | ```scheme 25 | (foldr + 0 (list 1 2 3)) ; 6 26 | (foldl + 0 (list 1 2 3)) ; 6 27 | ``` 28 | 29 | Тут всё понятно: сложение ассоциативно, вот и суммы сошлись. Усложним задачу: будем выводить каждое новое значение на экран, игнорируя аккумулятор: 30 | 31 | ```scheme 32 | (define (f x acc) 33 | (displayln x)) 34 | 35 | (foldr f (void) (list 1 2 3)) 36 | ; => 3 37 | ; => 2 38 | ; => 1 39 | 40 | (foldl f (void) (list 1 2 3)) 41 | ; => 1 42 | ; => 2 43 | ; => 3 44 | ``` 45 | 46 | Разница налицо! 47 | 48 | В большинстве случаев используют левую свёртку (`foldl`), потому что она более интуитивна — двигается от первого элемента к последнему — и работает *эффективнее*. Однако иногда полезна именно правая `foldr`, поэтому в стандартной библиотеке есть и она. 49 | 50 | Стоит напоследок упомянуть, что `foldl` и `foldr` могут обходить несколько списков одновременно — так же, как это делает `map`. Списки при этом должны иметь одинаковую длину, а функция-аргумент должна принимать `n+1` аргументов для `n` списков: по одному элементу от каждого списка плюс аккумулятор. Вот пример такого использования левой свёртки: 51 | 52 | ```scheme 53 | (define (f greeting name acc) 54 | (string-append acc greeting ", " name "!\n")) 55 | 56 | (display 57 | (foldl f "" 58 | (list "Hello" "Hi" "Good day") 59 | (list "Bob" "Alice" "Tom"))) 60 | ; => Hello, Bob! 61 | ; => Hi, Alice! 62 | ; => Good day, Tom! 63 | ; => 64 | ``` 65 | -------------------------------------------------------------------------------- /modules/40-lists/40-foldl/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Сворачивание списков, функции свёртки foldl и foldr 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/40-lists/40-foldl/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? 8 | (max-delta (list) (list)) 9 | 0) 10 | 11 | (check-equal? 12 | (max-delta 13 | (list -5) 14 | (list -15)) 15 | 10) 16 | 17 | (check-equal? 18 | (max-delta 19 | (list 0) 20 | (list 42)) 21 | 42) 22 | 23 | (check-equal? 24 | (max-delta 25 | (list 10 -15 35) 26 | (list 2 -12 42)) 27 | 8)) 28 | -------------------------------------------------------------------------------- /modules/40-lists/50-list-internals/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/40-lists/50-list-internals/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (lookup key pairs) 7 | (let* ([same-key? (lambda (kv) (equal? key (car kv)))] 8 | [found-pairs (filter same-key? pairs)]) 9 | (if (empty? found-pairs) #f 10 | (first found-pairs)))) 11 | #| END |# 12 | -------------------------------------------------------------------------------- /modules/40-lists/50-list-internals/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `lookup`, которая должна принимать аргумент-ключ и список пар "ключ-значение" и возвращать либо пару "ключ-значение", где ключ равен первому аргументу, либо `#f`, если подходящих пар в списке не нашлось. Если подходящих пар найдётся несколько, вернуть нужно *первую*. 3 | 4 | Примеры: 5 | 6 | ```scheme 7 | (define user-ages 8 | (list (cons "Tom" 31) 9 | (cons "Alice" 22) 10 | (cons "Bob" 42))) 11 | 12 | (lookup "Bob" user-ages) ; '("Bob" . 42) 13 | (lookup "Tom" user-ages) ; '("Tom" . 31) 14 | (lookup "Moe" user-ages) ; #f 15 | ``` 16 | -------------------------------------------------------------------------------- /modules/40-lists/50-list-internals/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Все списки, с которыми вы имели дело до этого момента, либо создавались с помощью литералов и функции `list`, либо являлись результатами вычисления каких-то выражений. Во всех этих случаях список выглядит как нечто неделимое, цельное. Именно так работает абстракция: для работы со списками *достаточно* думать о списке, как о самостоятельной и самодостаточной сущности! 3 | 4 | Тем из вас, кто сталкивался в других языках с массивами, могло показаться, что в Racket списки — это те же массивы: отдельные участки памяти компьютера, хранящие все значения рядом. 5 | 6 | На самом же деле во всех языках семейства Lisp, да и в большинстве других языков, располагающих к функциональному подходу, списки — это *цепочки вложенных пар*. А вот *пара* уже является неделимой структурой. И чтобы понять списки, нужно сначала освоить пары. 7 | 8 | ### Пары 9 | 10 | Неделимость пары означает, что пара не может стать тройкой, или, наоборот, стать короче. Пару можно только собрать из двух значений и в дальнейшем её структура меняться не будет. Создаётся пара с помощью функции `cons`: 11 | 12 | ```scheme 13 | (cons 1 2) ; '(1 . 2) 14 | (pair? (cons 1 2)) ; #t 15 | ``` 16 | 17 | Обратите внимание на то, как отображается полученное значение: элементы пары разделяет точка ("."). Такое отображение не даёт спутать пару со списком `'(1 2)`. 18 | 19 | > Как и в случае со списками, кавычка здесь означает цитирование. Вы можете попробовать по-создавать пары с помощью такого синтаксиса, но мы рекомендуем использовать функцию `cons`, как более простой в использовании вариант. 20 | 21 | Для доступа к элементам пары используются функции `car` и `cdr` (читаются как "кар" и "кадр"): 22 | 23 | ```scheme 24 | (define p (cons "Bob" 42)) 25 | 26 | (car p) ; "Bob" 27 | (cdr p) ; 42 28 | ``` 29 | 30 | Имена этих функций таковы по историческим причинам (подробнее вы можете почитать [в Википедии](https://ru.wikipedia.org/wiki/%D0%9B%D0%B8%D1%81%D0%BF#%D0%91%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B5_%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D1%8B,_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80%D1%8B_%D0%B8_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8)), но большинство лиспоподобных языков предоставляют их для единообразия. 31 | 32 | ### cons + null 33 | 34 | Итак, у нас есть пары, но когда же они становятся списками? Список — пара из первого сохраняемого значения, называемого *головой* (*head*), и *другого списка*, называемого *хвостом* (*tail*). Вот как можно собрать список вручную: 35 | 36 | ```scheme 37 | (cons 1 (cons 2 (cons 3 null))) ; '(1 2 3) 38 | ``` 39 | 40 | В последней паре справа располагается специальное значение `null`, означающее список нулевой длины. Пустой список можно обозначить и другими способами, функция проверки на пустоту `empty?` вернёт `#t` для любого варианта: 41 | 42 | ```scheme 43 | (empty? null) ; #t 44 | (empty? (list)) ; #t 45 | (empty? '()) ; #t 46 | ``` 47 | 48 | Стоит ещё раз подчеркнуть, что не любая пара является списком. Второй элемент пары *должен сам быть списком*, пусть даже пустым — только тогда эта пара тоже будет считаться списком! Вот несколько примеров разных пар: 49 | 50 | ```scheme 51 | (pair? (cons 1 2)) ; #t 52 | (list? (cons 1 2)) ; #f - справа не список 53 | 54 | (pair? (cons 1 null)) ; #t 55 | (list? (cons 1 null)) ; #t - тут всё понятно 56 | 57 | (pair? (cons 1 (cons 2 3))) ; #t 58 | (list? (cons 1 (cons 2 3))) ; #f - хвост не является списком! 59 | 60 | (list? (cons 1 (cons 2 null))) ; #t - всё хвосты являются списками 61 | ``` 62 | 63 | ### car/cdr и first/rest 64 | 65 | Для списков, как и для любых других пар, можно использовать функции `car` и `cdr`: они будут возвращать "голову" и "хвост" списка. Однако в Racket есть более подходящие средства для работы именно со списками: функции `first` и `rest`. 66 | 67 | `first` и `rest` проще читать уже благодаря их именам, но самое важное свойство этих функций заключается в том, что они отказываются работать с не-списками: вы получите ошибку, если примените любую из этих функций к паре, не содержащей список справа. 68 | 69 | ```scheme 70 | (first (cons 1 2)) 71 | ; first: contract violation 72 | ; expected: (and/c list? (not/c empty?)) 73 | ; ... 74 | (rest (cons 1 2)) 75 | ; rest: contract violation 76 | ; expected: (and/c list? (not/c empty?)) 77 | ; ... 78 | ``` 79 | 80 | Обе ошибки говорят о том, что функции ожидают аргумент, удовлетворяющий условию `(and/c list? (not/c empty?))`, которое можно прочитать как "list? И НЕ empty?" или "список И НЕ пустой". Весьма логично! 81 | 82 | > Такая разборчивость помогает в отладке, а если использовать Typed Racket — вариант языка Racket, использующий проверку типов, — то вы получите *ошибку типизации* вместо ошибки во время исполнения. 83 | -------------------------------------------------------------------------------- /modules/40-lists/50-list-internals/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Внутреннее устройство списков, пары, cons, null 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/40-lists/50-list-internals/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (lookup "foo" null) #f) 8 | 9 | (check-equal? 10 | (lookup "foo" (list (cons "foo" "bar"))) 11 | (cons "foo" "bar")) 12 | 13 | (check-equal? 14 | (lookup "foo" (list (cons "bar" "foo"))) 15 | #f) 16 | 17 | (check-equal? 18 | (lookup 42 (list (cons 42 0) 19 | (cons 30 0) 20 | (cons 42 100500))) 21 | (cons 42 0))) 22 | -------------------------------------------------------------------------------- /modules/40-lists/60-lists-and-recursion/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/40-lists/60-lists-and-recursion/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide (all-defined-out)) 4 | 5 | #| BEGIN |# 6 | (define (skip n l) 7 | (if (or (<= n 0) (empty? l)) l 8 | (skip (sub1 n) (rest l)))) 9 | #| END |# 10 | -------------------------------------------------------------------------------- /modules/40-lists/60-lists-and-recursion/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `skip`, которая должна принимать два аргумента — целое число n и список — и возвращать новый список, содержащий все элементы из первого списка за исключением n первых элементов. Если n окажется больше, чем количество элементов во входном списке, результатом должен быть пустой список. 3 | 4 | Примеры: 5 | 6 | ```scheme 7 | (skip -5 (list 1 2 3)) ; '(1 2 3) 8 | (skip 0 (list 1 2 3)) ; '(1 2 3) 9 | (skip 1 (list 1 2 3)) ; '(2 3) 10 | (skip 10 (list 1 2 3)) ; '() 11 | ``` 12 | -------------------------------------------------------------------------------- /modules/40-lists/60-lists-and-recursion/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Универсальные функции вроде `map` и `filter` — основные инструменты по обработке списков. Но их выразительности хватает не всегда, и тут уже на помощь приходят функции свёртки `foldl` и `foldr`. Умение использовать этот готовый инструментарий в теории позволит решать *любые* задачи, возникающие при работе со списками. 3 | 4 | Однако умение работать со списками "вручную" полезно само по себе, так как учит работе с произвольными *рекурсивными структурами данных*. Вспомните: список — это пара из головы и списка-хвоста, простейшая рекурсивная структура данных! 5 | 6 | В функциональном программировании рекурсивные структуры данных принято обрабатывать… рекурсивными функциями! Так, функция, работающая со списком, отделяет голову от хвоста и формирует результат на основе значения головы и *результата применения себя* к хвосту — сама структура списка диктует то, как с ним нужно работать. 7 | 8 | Рассмотрим простейший пример обработки списка на примере функции вычисления суммы его элементов. 9 | 10 | Функция будет рекурсивной, а значит нам нужно определиться с *условием останова* (*terminating case*) — без него вычисление функции никогда не завершится. Для большинства функций, работающих со списками, останов наступает тогда, когда функция применяется к пустому списку: становится нечего делить на голову и хвост. Вот как функция будет выглядеть в коде: 11 | 12 | ```scheme 13 | (define (sum l) 14 | (if (empty? l) 15 | 0 ; список пустой, сумма равна нулю 16 | (let ([head (first l)] ; голова 17 | [tail (rest l)]) ; хвост 18 | (+ head 19 | (sum tail))))) ; обрабатываем хвост рекурсивно 20 | ``` 21 | 22 | Стоит заменить значение для пустого списка на `null`, а последнее выражение на что-то вроде `(cons (f head) (map f tail))`, и мы получим свой вариант `map`! Здесь `cons` собирает новый список по мере разбора старого. Если образовывать пару по условию, то получится `filter`! А вот так будут выглядеть функции свёртки (если опустить возможность работы с несколькими списками): 23 | 24 | ```scheme 25 | (define (foldr op init l) 26 | (if (empty? l) 27 | init 28 | (let ([head (first l)] 29 | [tail (rest l)]) 30 | (op head 31 | (foldr op init tail))))) 32 | 33 | (define (foldl op init l) 34 | (if (empty? l) 35 | init 36 | (let ([head (first l)] 37 | [tail (rest l)]) 38 | (foldl op (op head init) 39 | tail)))) 40 | ``` 41 | 42 | ### Хвостовой вызов 43 | 44 | Обратите внимание на то, как выглядят рекурсивные вызовы в примере со свёртками: 45 | 46 | - `(... (foldr ... tail))` 47 | - `(foldl ... tail)` 48 | 49 | В первом случае результат вычисления `foldr` используется для вычисления результата и интерпретатору нужно *дождаться* результата обработки хвоста. 50 | 51 | Во втором же случае сам результат *и есть* вызов "самое-себя" функции `foldl`. Результат просто "пробрасывается выше". И тут интерпретатор может упростить себе и вам жизнь! Поскольку дополнительно обрабатывать результат рекурсивного вызова не нужно, можно *закончить* вычисление текущего вызова функции, *заменив* вычисление изначального вызова на вычисление рекурсивного. Технически это означает, что интерпретатор *не будет* увеличивать стек вызовов, а значит появится возможность выполнять рекурсивные вызовы бесконечно долго! Именно поэтому использование `foldl` предпочтительнее, чем использование `foldr`, когда обрабатываемые списки достаточно велики. 52 | 53 | > Умение интерпретатора видеть, когда обычный вызов функции с приращением стека вызовов можно заменить на возврат по стеку выше и подстановку нового вызова, называется *оптимизацией хвостового вызова* (*tail call optimization*). Некоторые интерпретаторы и компиляторы могут оптимизировать только рекурсивные вызовы (это уже так называемая *оптимизация хвостовой рекурсии*), другие (Racket в том числе!) — любые хвостовые вызовы. 54 | -------------------------------------------------------------------------------- /modules/40-lists/60-lists-and-recursion/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Обход списков и рекурсия 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/40-lists/60-lists-and-recursion/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (skip -5 (list 1 2 3)) (list 1 2 3)) 8 | 9 | (check-equal? (skip 0 (list 1 2 3)) (list 1 2 3)) 10 | 11 | (check-equal? (skip 1 (list 1 2 3)) (list 2 3)) 12 | 13 | (check-equal? (skip 2 (list 1 2 3)) (list 3)) 14 | 15 | (check-equal? (skip 3 (list 1 2 3)) (list)) 16 | 17 | (check-equal? (skip 10 (list 1 2 3)) null)) 18 | -------------------------------------------------------------------------------- /modules/40-lists/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Списки 4 | description: | 5 | 6 | Основная структура данных в языках семейства Lisp — это список. Не зря же LISP расшифровывается как LISt Processor, то есть "обработчик списков". Списки используются в языках и для хранения данных, и для написания кода: сама программа на Lisp-языке состоит из списка списков списков (…). 7 | 8 | Этот модуль учит объявлять списки и обрабатывать их содержимое различными способами. 9 | -------------------------------------------------------------------------------- /modules/50-strings/10-chars/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/50-strings/10-chars/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide 4 | next-char 5 | prev-char) 6 | 7 | #| BEGIN |# 8 | (define (next-char c) (integer->char (add1 (char->integer c)))) 9 | (define (prev-char c) (integer->char (sub1 (char->integer c)))) 10 | #| END |# 11 | -------------------------------------------------------------------------------- /modules/50-strings/10-chars/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте две функции, `next-char` и `prev-char`, которые вычисляют для символа-аргумента следующий и предыдущий символы (с точки зрения десятичного кода). 3 | -------------------------------------------------------------------------------- /modules/50-strings/10-chars/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Любой текст в Racket представлен типом `string`, который представляет собой *список фиксированной длины*, состоящий из *символов* — значений типа `char`. 3 | 4 | > И вот тут придётся сразу же предупредить: в русском языке слова "character" (если речь идёт о части строки) и "symbol" имеют один и тот же перевод — "символ". Вот только в Racket и других языках семейства lisp есть отдельная сущность `symbol` и именно её принято называть "символом". Поэтому далее в тексте вы будете встречать либо "строковый символ", либо имя типа, то есть "char". Так будет проще избежать подмены понятий. 5 | 6 | Итак, строки состоят из строковых символов. Как же выглядят эти символы сами по себе в виде *литералов*? Когда речь идёт о *печатных символах* (*printable characters*), то любой из оных можно представить в виде символов (ага, опять синоним!) "#\" плюс самого желаемого символа: 7 | 8 | ```scheme 9 | (displayln #\a) ; => a 10 | (displayln #\1) ; => 1 11 | (displayln #\,) ; => , 12 | (displayln #\)) ; => ) 13 | (displayln #\\) ; => \ 14 | (displayln #\#) ; => # 15 | ``` 16 | 17 | Как видите, нет проблем с закрывающими скобками и самими `#` и `\` — любой печатный символ может быть закодирован таким образом. 18 | 19 | Но как же указать в виде литерала пробел, табуляцию, или перевод строки? Для символа "пробела" ("space") и большинства *управляющих символов* (*control characters*) можно указать их имя после `#\`: 20 | 21 | ```scheme 22 | #\space ; это пробел 23 | #\newline ; a это — перевод строки 24 | ``` 25 | 26 | Кроме того, любой из символов Unicode можно указать, используя его код в восьмеричном или шестнадцатеричном представлении: 27 | 28 | ```scheme 29 | (displayln #\λ) ; => λ 30 | (displayln (integer->char 955)) ; => λ 31 | (displayln #\u3BB) ; => λ 32 | (displayln #\n) ; => n 33 | (displayln #\156) ; => n 34 | ``` 35 | 36 | Здесь функция `integer->char` преобразует десятичное число в строковый символ с кодом 955 (греческая буква "лямбда"). Запись `3BB` — это то же, что 955, только в шестнадцатеричной форме. 156 — восьмеричный код ASCII-символа "n". 37 | 38 | А вот `#\955` использовать не получится: восьмеричным числом можно задавать только коды в диапазоне 0...377, то есть символы таблицы ASCII. Но символы Unicode повсеместно принято указывать в шестнадцатеричном виде, поэтому данное ограничение не вызывает больших неудобств. 39 | 40 | В дополнение к `integer->char` Racket предоставляет и функцию `char->integer`, которая вычисляет десятичный код символа. Такие функции преобразования одного типа в другой можно встретить очень часто, и во многих случаях функции идут подобными парами. 41 | -------------------------------------------------------------------------------- /modules/50-strings/10-chars/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Символы 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/50-strings/10-chars/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (next-char #\0) #\1) 8 | 9 | (check-equal? (prev-char #\1) #\0) 10 | 11 | (check-equal? 12 | (prev-char (next-char #\a)) 13 | (next-char (prev-char #\a)))) 14 | -------------------------------------------------------------------------------- /modules/50-strings/20-strings/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/50-strings/20-strings/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide next-chars) 4 | 5 | #| BEGIN |# 6 | (define (next-char c) (integer->char (add1 (char->integer c)))) 7 | 8 | (define (next-chars s) 9 | (list->string (map next-char (string->list s)))) 10 | #| END |# 11 | -------------------------------------------------------------------------------- /modules/50-strings/20-strings/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `next-chars`, которая создаёт новую строку на основе строки-аргумента таким образом, что каждый символ новой строки является "следующим" (с точки зрения кода) по отношению к соответствующему символу исходной строки. 3 | 4 | Примеры: 5 | 6 | ```scheme 7 | (next-chars "") ; "" 8 | (next-chars "abc") ; "bcd" 9 | (next-chars "12345") ; "23456" 10 | ``` 11 | -------------------------------------------------------------------------------- /modules/50-strings/20-strings/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | Вы раньше встречались со строковыми литералами: `"hello"`, `"foo\nbar"`. Здесь Racket ничем не отличается от большинства других языков: строковые литералы могут содержать специальные символы вроде `\n`, символы Unicode вроде `\u03BB` и *экранированные* двойные кавычки `\"`. 3 | 4 | ```scheme 5 | (displayln "Bond, James\nCode name: \"007\"") 6 | ; => Bond, James 7 | ; => Code name: "007" 8 | ``` 9 | 10 | Если же у нас есть строковые символы, то объединить их в строку можно с помощью функции `string`: 11 | 12 | ```scheme 13 | (define l #\l) 14 | (string #\H #\e l l #\o #\!) ; "Hello" 15 | (string) ; "" 16 | ``` 17 | 18 | Когда нужно создать строку заданной длины, заполненную копиями некоторого символа, то подойдёт функция `make-string`: 19 | 20 | ```scheme 21 | (make-string 10 #\.) ; ".........." 22 | ``` 23 | 24 | Длину созданной строки можно узнать с помощью функции `string-length`: 25 | 26 | ```scheme 27 | (string-length (string)) ; 0 28 | (string-length (make-string 10 #\!)) ; 10 29 | ``` 30 | 31 | ### Строки и списки 32 | 33 | Как вы уже знаете, в Lisp многое строится вокруг списков. Вот и строки часто удобно обрабатывать именно как списки. Для этого используется пара функций `string->list` и `list->string`: 34 | 35 | ```scheme 36 | (string->list "ab") ; '(#\a #\b) 37 | (list->string null) ; "" 38 | (list->string (rest (string->list "Hello"))) ; "ello" 39 | ``` 40 | -------------------------------------------------------------------------------- /modules/50-strings/20-strings/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Создание строк 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/50-strings/20-strings/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (next-chars "") "") 8 | 9 | (check-equal? (next-chars "abc") "bcd") 10 | 11 | (check-equal? (next-chars "123") "234") 12 | 13 | (check-equal? (next-chars (string #\0 #\100)) (string #\1 #\101))) 14 | -------------------------------------------------------------------------------- /modules/50-strings/30-predicates/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/50-strings/30-predicates/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide 4 | password-valid? 5 | password-good?) 6 | 7 | #| BEGIN |# 8 | (define (char-alphanumeric? c) 9 | (or (char-alphabetic? c) 10 | (char-numeric? c))) 11 | 12 | (define (password-valid? password) 13 | (and 14 | (positive? (string-length password)) 15 | (andmap char-alphanumeric? (string->list password)))) 16 | 17 | (define (password-good? password) 18 | (let ([chars (string->list password)]) 19 | (and 20 | (password-valid? password) 21 | (<= 8 (length chars)) 22 | (ormap char-alphabetic? chars) 23 | (ormap char-numeric? chars)))) 24 | #| END |# 25 | -------------------------------------------------------------------------------- /modules/50-strings/30-predicates/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте два предиката: `password-valid?` и `password-good?`. Оба должны оценивать строку. 3 | 4 | `password-valid?` должен возвращать `#t`, если строка состоит **только из букв и цифр** (`char-alphabetic?` и `char-numeric?`) и при этом имеет **ненулевую** длину. 5 | 6 | `password-good?` должен возвращать `#t`, если строка содержит **и буквы, и цифры**, а длина строки **не меньше** восьми символов. И, разумеется, хороший пароль должен быть valid! 7 | -------------------------------------------------------------------------------- /modules/50-strings/30-predicates/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Сравнение строк и строковых символов 3 | 4 | Пусть заголовок вас не смущает: сравнить `char` и `string` не получится, ведь это разные типы! 5 | 6 | Но и пару строк или пару строковых символов сравнить с помощью функций "`=`" и `"<"` не получится: в Racket эти функции работают только с числами. Чтобы соотносить между собой значения `string` и `char` в коде на Racket, вам нужно использовать функции с именами вида `string=?` и `char? #\c #\b #\a) ; #t 11 | (char=? #\a #\b) ; #f 12 | (string? "c" "b" "a") ; #t 14 | (string=? "a" "b") ; #f 15 | ``` 16 | 17 | Глядя на этот пример, вы и сами сможете догадаться, как работают функции `string<=?` и `char>=?` :) 18 | 19 | ### Регистронезависимость 20 | 21 | Упомянутые функции сравнивают строки и строковые символы *буквально* или, как говорят, *лексикографически*. Однако при работе с текстом часто нужно сопоставлять строки без *учёта регистра*, или производить *регистронезависимое сравнение* (*case-insensitive comparison*). В Racket для каждой функции сравнения есть регистронезависимый вариант, имеющий суффикс `-ci` ("case-insensitive"): 22 | 23 | ```scheme 24 | (string=? "Apple" "apple") ; #f 25 | (string-ci=? "Apple" "apple") ; #t 26 | (char>? #\C #\b) ; #f 27 | (char-ci>? #\C #\b) ; #t 28 | ``` 29 | 30 | ### Группы символов 31 | 32 | Строковые символы могут быть буквами, цифрами, знаками препинания. Более того, буквы могут принадлежать разным алфавитам, одни из которых имеют разные регистры, к другим же данная концепция в принципе не применима. Получается, что некоторый строковый символ может принадлежать к одной или сразу к нескольким *группам символов*. Проверить принадлежность к основным группам символов можно с помощью набора предикатов с именами вида `char-alphabetic?` и `char-lower-case?`: 33 | 34 | ```scheme 35 | (char-alphabetic? #\a) ; #t 36 | (char-alphabetic? #\u3BB) ; #t — "λ" это буква, пусть и греческая 37 | (char-lower-case? #\a) ; #t 38 | (char-lower-case? #\A) ; #f 39 | ``` 40 | 41 | Предикатов такого вида в Racket больше десятка, почитать обо всех вы сможете в [документации](https://docs.racket-lang.org/reference/characters.html#%28part._.Classifications%29). 42 | 43 | Отдельных функций, которые бы проверяли, что все символы некоторой строки принадлежат к некоторой группе, Racket не предоставляет. Впрочем, организовать такую проверку довольно просто, если взять функцию `andmap`, работающую со списками, и объединить со `string->list`: 44 | 45 | ```scheme 46 | (andmap char-alphabetic? (string->list "asd")) ; #t 47 | (andmap char-alphabetic? (string->list "r2d2")) ; #f 48 | ``` 49 | 50 | Если заменить `andmap` на `ormap`, то вместо проверки на "все символы…" получится проверка на "хотя бы один символ…"! 51 | -------------------------------------------------------------------------------- /modules/50-strings/30-predicates/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Сравнение строк и символов, предикаты 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/50-strings/30-predicates/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-true check-false test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-false (password-valid? "")) 8 | (check-false (password-valid? "***")) 9 | (check-false (password-valid? "a b")) 10 | (check-false (password-valid? "a1?")) 11 | 12 | (check-true (password-valid? "god")) 13 | (check-true (password-valid? "LOVE")) 14 | (check-true (password-valid? "123456")) 15 | (check-true (password-valid? "U2"))) 16 | 17 | (test-begin 18 | (check-false (password-good? "")) 19 | (check-false (password-good? "***")) 20 | (check-false (password-good? "a b")) 21 | (check-false (password-good? "a1?")) 22 | 23 | (check-false (password-good? "god")) 24 | (check-false (password-good? "LOVE")) 25 | (check-false (password-good? "123456")) 26 | (check-false (password-good? "U2")) 27 | 28 | (check-false (password-good? "9876543210")) 29 | (check-false (password-good? "Antananarivo")) 30 | 31 | (check-true (password-good? "1cat1dog")) 32 | (check-true (password-good? "R2D2andC3PO"))) 33 | -------------------------------------------------------------------------------- /modules/50-strings/40-operations/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/50-strings/40-operations/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide scroll-left) 4 | 5 | #| BEGIN |# 6 | (define (scroll-left s) 7 | (if (zero? (string-length s)) s 8 | (string-append (substring s 1) 9 | (substring s 0 1)))) 10 | #| END |# 11 | -------------------------------------------------------------------------------- /modules/50-strings/40-operations/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `scroll-left`, которая "прокручивает" строку-аргумент влево так, что в строке-результате все символы, начиная со второго, оказываются сдвинуты на одну позицию влево, а первый символ оказывается в конце строки. В пустой строке прокручивать нечего, поэтому пустая строка должна оставаться неизменной. 3 | 4 | Примеры: 5 | 6 | ```scheme 7 | (scroll-left "") ; "" 8 | (scroll-left "a") ;"a" 9 | (scroll-left "abc") ; "bca" 10 | (scroll-left "*----") ; "----*" 11 | (scroll-left (scroll-left "*----")) ; "---*-" 12 | ``` 13 | -------------------------------------------------------------------------------- /modules/50-strings/40-operations/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Получение отдельных символов и подстрок 3 | 4 | Получить элемент строки, зная его индекс, можно с помощью функции `string-ref`. Если указанный индекс выйдет за границы строки, то вычисление завершится с ошибкой. 5 | 6 | ```scheme 7 | (string-ref "apple" 3) ; #\l 8 | (string-ref "apple" 5) 9 | ; string-ref: index is out of range 10 | ; index: 5 11 | ; valid range: [0, 4] 12 | ; string: "apple" 13 | ; context... 14 | ``` 15 | 16 | Взятие же *подстроки* выглядит так: 17 | 18 | ```scheme 19 | ; индексы: 01234 20 | (substring "Apple" 2) ; "ple" 21 | (substring "Apple" 1 3) ; "pp" 22 | (substring "Apple" 10 20) 23 | ; substring: starting index is out of range 24 | ; starting index: 10 25 | ; valid range: [0, 5] 26 | ; string: "Apple" 27 | ; context... 28 | ``` 29 | 30 | Заметьте, что `(substring s n)` выделяет подстроку от символа с индексом `n` и до конца исходной строки `s`. А вот `(substring s n m)` выделяет подстроку от индекса `n` до индекса `m`, не включая последний! Такое исключение правой границы встречается довольно часто в программировании. И, как и в случае `string-ref`, Racket следит, чтобы индексы не вышли за границы. 31 | 32 | ### Конкатенация строк 33 | 34 | Соединить несколько строк в одну или, как ещё говорят, *сконкатенировать*, можно с помощью функции `string-append`: 35 | 36 | ```scheme 37 | (string-append "Hello, " "World!") ; "Hello, World!" 38 | (string-append "foo") ; "foo" 39 | (string-append "b" "a" "r") ; "bar" 40 | ``` 41 | 42 | ### Модуль `racket/string` 43 | 44 | В поставку Racket входит модуль `racket/string`, предоставляющий большое количество полезных, но более специфических, чем простая конкатенация, функций. 45 | 46 | > Обычно этот модуль подключать не приходится, потому что `#lang racket` делает это автоматически. 47 | 48 | Например, функция `string-join` из этого модуля может просто сконкатенировать все строки из заданного списка, добавив между строками строку-разделитель, а ещё способна добавить что-нибудь в начало получаемого текста, в его конец и даже перед последним присоединяемым элементом! На это стоит посмотреть: 49 | 50 | ```scheme 51 | (string-join (list "a" "b" "c")) ; "a b c" - разделитель, это пробел 52 | 53 | (define (greet names) 54 | (string-join 55 | names ", " 56 | #:before-first "Hello, " 57 | #:before-last " and " 58 | #:after-last "!")) 59 | 60 | (greet (list "Bob")) ; "Hello, Bob!" 61 | (greet (list "Bob" "Tom")) ; "Hello, Bob and Tom!" 62 | (greet (list "Bob" "Tom" "Alice")) ; "Hello, Bob, Tom and Alice!" 63 | ``` 64 | 65 | Узнать больше об этой функции и познакомиться с другими функциями модуля вы сможете в [документации](https://docs.racket-lang.org/reference/strings.html#%28mod-path._racket%2Fstring%29). 66 | -------------------------------------------------------------------------------- /modules/50-strings/40-operations/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Оперирование строками 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/50-strings/40-operations/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-equal? (scroll-left "") "") 8 | 9 | (check-equal? (scroll-left "a") "a") 10 | 11 | (check-equal? (scroll-left "abc") "bca") 12 | 13 | (check-equal? (scroll-left 14 | (scroll-left 15 | (scroll-left "abc"))) "abc")) 16 | -------------------------------------------------------------------------------- /modules/50-strings/50-formatting/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/50-strings/50-formatting/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide add) 4 | 5 | #| BEGIN |# 6 | (define (add x y) 7 | (let* ([result (~a (+ x y))] 8 | [width (string-length result)]) 9 | (format 10 | "+~a~n ~a~n ~a~n ~a" 11 | (~a x #:min-width width #:align 'right) 12 | (~a y #:min-width width #:align 'right) 13 | (make-string width #\-) 14 | result))) 15 | #| END |# 16 | -------------------------------------------------------------------------------- /modules/50-strings/50-formatting/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `add`, которая должна для двух чисел вычислить сумму в виде "сложения в столбик" (в виде строки). Ширина текста должна подстраиваться под количество разрядов в сумме. Сами аргументы считайте всегда целыми и неотрицательными. 3 | 4 | Примеры: 5 | 6 | ```scheme 7 | (displayln (add 1 2)) 8 | ; => +1 9 | ; => 2 10 | ; => - 11 | ; => 3 12 | (displayln (add 1 9)) 13 | ; => + 1 14 | ; => 9 15 | ; => -- 16 | ; => 10 17 | (displayln (add 1223234 56)) 18 | ; => +1223234 19 | ; => 56 20 | ; => ------- 21 | ; => 1223290 22 | ``` 23 | -------------------------------------------------------------------------------- /modules/50-strings/50-formatting/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В программировании часто возникает задача собрать строку из значений, которые не являются строками сами по себе. Для решения подобных задач применяют *форматирование* (*formatting*) строк (оно же — "строковая интерполяция"). В Racket средств для форматирования присутствует довольно много, но мы рассмотрим самые популярные. 3 | 4 | ### Функции `format` и `printf` 5 | 6 | Функции `format` и `printf` ожидают в качестве первого аргумента строку-шаблон и несколько значений, которые будут в этот шаблон подставлены. Разница же между функциями в том, что вызов `format` вычисляется в строку, а `printf` сразу выводит значение на экран (результатом же вычисления будет `#`). 7 | 8 | Шаблон может содержать произвольный текст, в нужные места которого вставлены следующие последовательности: 9 | 10 | * `~a`, означающая "подставить в читаемом виде"; 11 | * `~v`, означающая "вывести как в REPL"; 12 | * `~n`, означающая "вставить перевод строки"; 13 | * `~~`, означающая "просто вывести ~". 14 | 15 | Есть и другие последовательности, о которых можно почитать [здесь](https://docs.racket-lang.org/reference/Writing.html#%28def._%28%28quote._~23~25kernel%29._fprintf%29%29), но перечисленные выше используются чаще всего. 16 | 17 | `~a` используется всегда, когда нужно подставить число, строку или строковый символ. При этом в текст не попадут кавычки, обрамляющие строковый литерал, и символы будут подставлены в печатаемом виде, а не в виде кода (`a` вместо `#\a`). 18 | 19 | `~v` пригодится тогда, когда нужно вывести некое комплексное значение в таком виде, в каком его отображает интерпретатор в интерактивном режиме. Этот вариант вывода очень полезен при отладке: выведенные значения часто можно скопировать в REPL или в редактор и повторно вычислить. 20 | 21 | Назначение `~n` и `~~` довольно очевидно, согласитесь? 22 | 23 | Вот несколько примеров применения `printf`: 24 | 25 | ```scheme 26 | (printf "This is a list: ~v~n" '(1 2 3)) 27 | ; => This is a list: '(1 2 3) 28 | (printf "This is a string: ~v~n" "hello") 29 | ; => This is a string: "hello" 30 | (printf "~a + ~a = ~a~n" 40 2 (+ 40 2)) 31 | ; => 40 + 2 = 42 32 | (printf "~v is \"~a\"~n" #\! #\!) 33 | ; => #\! is "!" 34 | (printf "~v prints as \"~a\"~n" "abc" "abc") 35 | ; => "abc" prints as "abc" 36 | (printf "~v prints as \"~a\"~n" '(42) '(42)) 37 | ; => '(42) prints as "(42)" 38 | ``` 39 | 40 | ### Функции `~a` и `~v` 41 | 42 | Для большинства последовательностей, работающих в шаблонах, есть аналоги в виде одноимённых функций, таких как `~a` и `~v`. Рассмотрим подробнее `~a`, остальные же вы сможете изучить сами. 43 | 44 | `~a` принимает одно или несколько значений, которые будет преобразованы в строки и объединены в результирующую строку. Кроме перечня значений функция принимает десяток опциональных *именованных аргументов*, таких как `#:separator`, `#:min-width` и `#:align` (полный список, как обычно, имеется в [документации](https://docs.racket-lang.org/reference/strings.html#%28def._%28%28lib._racket%2Fformat..rkt%29._~7ea%29%29)). 45 | 46 | Вот как работают само преобразование аргументов и подстановка разделителя: 47 | 48 | ```scheme 49 | (~a 1) ; "1" 50 | (~a 1 2 3) ; "123" 51 | (~a 1 "+" 3) ; "1+3" 52 | (~a 1 2 3 #:separator ", ") ; "1, 2, 3" 53 | ``` 54 | 55 | `#:min-width` и `#:align` удобны для вывода чисел в столбик: 56 | 57 | ```scheme 58 | (~a 42 #:min-width 6) ; "42 " 59 | (~a 42 #:min-width 6 #:align 'center) ; " 42 " 60 | (~a 42 #:min-width 6 #:align 'right) ; " 42" 61 | ``` 62 | 63 | > Слова с одинарной кавычкой в начале — это те самые символы, которые по-английски называются "symbols". Symbols будут рассмотрены позднее, пока можете воспринимать их как константы, означающие сами себя — более компактная и быстрая альтернатива строковым константам. 64 | 65 | Среди аргументов `~a` есть и такие, которые ограничивают максимальную длину результирующей строки, задающие то, чем заполняются отступы слева и справа. Словом, возможности широки! 66 | -------------------------------------------------------------------------------- /modules/50-strings/50-formatting/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Форматирование строк 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/50-strings/50-formatting/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (displayln (add 0 0)) 8 | (check-equal? 9 | (add 0 0) 10 | "+0 11 | 0 12 | - 13 | 0") 14 | 15 | (displayln "") 16 | (displayln (add 1 9)) 17 | (check-equal? 18 | (add 1 9) 19 | "+ 1 20 | 9 21 | -- 22 | 10") 23 | 24 | (displayln "") 25 | (displayln (add 999 99001)) 26 | (check-equal? 27 | (add 999 99001) 28 | "+ 999 29 | 99001 30 | ------ 31 | 100000")) 32 | -------------------------------------------------------------------------------- /modules/50-strings/60-immutability/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @ raco test test.rkt 2>&1 3 | -------------------------------------------------------------------------------- /modules/50-strings/60-immutability/index.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (provide scroll-left!) 4 | 5 | #| BEGIN |# 6 | (define (scroll-left! s) 7 | (let ([length (string-length s)]) 8 | (unless (zero? length) 9 | (let ([head (substring s 0 1)]) 10 | (string-copy! s 0 s 1 length) 11 | (string-copy! s (sub1 length) head))))) 12 | #| END |# 13 | -------------------------------------------------------------------------------- /modules/50-strings/60-immutability/ru/EXERCISE.md: -------------------------------------------------------------------------------- 1 | 2 | Реализуйте функцию `scroll-left!`, которая должна "прокручивать" изменяемую строку-аргумент так, чтобы первый символ попадал в конец строки, а остальные символы, начиная со второго, сдвигались на одну позицию влево. Пустую строку модифицировать не нужно. Возвращать изменённую строку тоже не следует — функция должна только модифицировать свой аргумент! 3 | 4 | Примеры: 5 | 6 | ```scheme 7 | (define s (string-copy "abc")) 8 | (scroll-left! s) 9 | s ; "bca" 10 | (scroll-left! s) 11 | s ; "cab" 12 | ``` 13 | -------------------------------------------------------------------------------- /modules/50-strings/60-immutability/ru/README.md: -------------------------------------------------------------------------------- 1 | 2 | В большинстве языков программирования высокого уровня строки неизменяемы. Это свойство уже практически стало стандартом. В Racket же строки *по умолчанию* изменяемые! Но неизменяемые строки также существуют. Разберёмся же, как с этой двойственностью работать. 3 | 4 | ### Изменяемость как свойство 5 | 6 | Изменяемые и неизменяемые строки не относятся к разным типам. Устойчивость к изменению — это свойство каждого конкретного строкового значения. Узнать, какая перед нами строка, можно с помощью предиката `immutable?`. Посмотрим его в действии: 7 | 8 | ```scheme 9 | (immutable? "a") ; #t 10 | (immutable? (string #\a)) ; #f 11 | (immutable? (make-string 1 #\a)) ; #f 12 | 13 | (string? "a") ;#t 14 | (string? (string #\a)) ;#t 15 | ``` 16 | 17 | Видно, что строковые литералы неизменяемы, а строки, созданные с помощью функций, таковыми не являются, но при этом и те и другие являются строками! 18 | 19 | Большинство операций над строками работают с любыми строками вне зависимости от свойства неизменяемости, однако возвращают почти все функции изменяемые строки. В большинстве случаев возвращается новая строка, так что беспокоиться о потенциальном изменении существующих строк практически не приходится. Кроме того, функции, изменяющие строку-аргумент, обычно имеют восклицательный знак (`!`) в конце имени, что облегчает чтение кода с подобными побочными эффектами. 20 | 21 | ### Преобразование строк в неизменяемые и обратно 22 | 23 | Любую изменяемую строку можно сделать неизменяемой, создав её неизменяемую *копию* с помощью функции `string->immutable-string`. Обратное преобразование не требует отдельной функции, вместо неё используется функция `string-copy`, которая создаёт изменяемую копию *любой строки*. 24 | 25 | Неизменяемые строки без потери неизменяемости можно только конкатенировать, используя отдельную функцию `string-append-immutable`. Любые другие виды обработки потребуют работы с изменяемыми строками с преобразованием в неизменяемую строку результата. 26 | 27 | ### Предназначение неизменяемых строк 28 | 29 | Неизменяемые строки ценны… своей неизменяемостью: строковая константа, содержащая строковый литерал, всегда будет иметь одно и то же значение. Ключ хеш-таблицы тоже должен быть неизменяемым, чтобы хеш-функция вычисляла для него одно и то же значение при обращении к таблице по ключу. Даже в памяти неизменяемые строки хранятся более компактно! 30 | 31 | Но есть у неизменяемости и обратная сторона: неизменяемые строки очень невыгодно обрабатывать: даже конкатенация потребует выделения памяти в количестве, равном суммарной длине всех объединяемых строк. И если в неизменяемой строке нужно поменять ровно один символ, то потребуется полная копия. 32 | 33 | А вот изменяемые строки интерпретатор может использовать более эффективно. Особенно хорошо ему работать с изменяемой строкой фиксированной длины и заменять её части посимвольно или целиком, не меняя эту самую длину — в этом случае новая память выделяться не будет! 34 | 35 | ### Модификация изменяемой строки 36 | 37 | Рассмотрим две функции, которые модифицируют содержимое изменяемой строки "по месту", не создавая изменённой копии. Это будут функции `string-set!` и `string-copy!`. Как уже было отмечено выше, восклицательный знак в имени означает оказание эффекта на изменяемый аргумент. 38 | 39 | `string-set!` заменяет символ по указанному индексу на заданный: 40 | 41 | ```scheme 42 | (define s (make-copy "Cat")) ; изменяемая копия! 43 | (string-set! s 0 #\B) 44 | (string-set! s 1 #\o) 45 | (displayln s) ; => Bot 46 | ``` 47 | 48 | `string-copy!` копирует одну строку **в середину** другой, размещая копию в некоторой позиции. Опционально можно указать, какой участок копируемой строки будет вставлен в модифицируемую. Вот так функция применяется: 49 | 50 | ```scheme 51 | (define names "Bob,Tom") 52 | 53 | (define s (make-string 10 #\.)) 54 | (displayln s) ; => .......... 55 | 56 | (string-copy! s 3 " VS ") 57 | (displayln s) ; => ... VS ... 58 | 59 | (string-copy! s 0 names 0 3) 60 | (displayln s) ; => Bob VS ... 61 | 62 | (string-copy! s 7 names 4 7) 63 | (displayln s) ; => Bob VS Tom 64 | ``` 65 | 66 | Строку можно копировать и саму в себя: 67 | 68 | ```scheme 69 | (define s (string-copy ".Tod")) 70 | (string-copy! s 0 s 1 4) 71 | s ; "Todd" 72 | ``` 73 | -------------------------------------------------------------------------------- /modules/50-strings/60-immutability/ru/data.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Строки и неизменяемость 3 | tips: [] 4 | -------------------------------------------------------------------------------- /modules/50-strings/60-immutability/test.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | 3 | (require (only-in rackunit check-equal? check-pred test-begin)) 4 | (require "index.rkt") 5 | 6 | (test-begin 7 | (check-pred 8 | void? 9 | (scroll-left! (string)) 10 | "Function should not return anything!") 11 | 12 | (check-pred 13 | void? 14 | (scroll-left! (make-string 3 #\a)) 15 | "Function should not return anything!") 16 | 17 | (check-equal? 18 | (let ([s (string-copy "")]) 19 | (scroll-left! s) 20 | s) 21 | "") 22 | 23 | (check-equal? 24 | (let ([s (string-copy "!")]) 25 | (scroll-left! s) 26 | s) 27 | "!") 28 | 29 | (check-equal? 30 | (let ([s (string-copy "abc")]) 31 | (scroll-left! s) 32 | s) 33 | "bca")) 34 | -------------------------------------------------------------------------------- /modules/50-strings/description.ru.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Строки 4 | description: | 5 | 6 | Любой язык программирования умеет работать с текстом, строками, отдельными символами. Модуль показывает, как работает с текстом Racket. 7 | -------------------------------------------------------------------------------- /spec.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | language: 4 | docker_image: "hexletbasics/exercises-racket" 5 | extension: rkt 6 | exercise_filename: index.rkt 7 | exercise_test_filename: test.rkt 8 | learn_as: second_language 9 | progress: completed 10 | name: Racket 11 | -------------------------------------------------------------------------------- /src/tests.rkt: -------------------------------------------------------------------------------- 1 | #lang racket 2 | (require racket/enter) 3 | 4 | (provide assert-output) 5 | 6 | (define message-template #<