├── .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 | [](../../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 | [](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` или, проще говоря, "тип"+"оператор"+"?". И запомнить, и использовать эти функции довольно просто:
7 |
8 | ```scheme
9 | (char #\a #\b) ; #t
10 | (char>? #\c #\b #\a) ; #t
11 | (char=? #\a #\b) ; #f
12 | (string "a" "b") ; #t
13 | (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 #<