Честно скажу, беру в руки эту книгу уже в третий раз.

На этот раз я буду намного серьезнее и хочу поделиться с вами, дорогой читатель, тем, что узнал. Это первая статья из серии, в которой я суммирую главы SICP.

Почему эта книга?

Я просто хочу кратко рассказать о том, почему я выбрал эту книгу.

Эта книга научит вас искусству программирования. Язык не зависит от языка: язык, который используется в книге, называется Scheme, и его можно выучить за 1 час. В этой статье я научу вас основным понятиям этого языка.

Эта книга также больше всего повлияла на мой мозг.

Я помню, что программирование было трудным, у меня в голове ничего не ладилось. Затем меня познакомили с этой книгой на сайте teachyourselfcs.com. Это было очень сложно, поэтому я выбрал более простую версию под названием HDTP (Как создавать программы).

Я прочитал первые несколько глав этой книги, и мой мозг изменился. Я наконец понял рекурсию. Все стало намного проще.

Это одна из главных причин, почему я возвращаюсь к этой книге. Я верю, что он еще многому меня научит.

Как все настроить

Это довольно просто: просто установите DrRacket.

DrRacket — это IDE для языка программирования Racket, основанная на языке программирования Scheme, используемом в книге. Чтобы использовать Scheme, просто добавьте его как заголовок с атрибутом #lang scheme.

ПС. У меня так, что я использую Neovim и установил ракетку на свой терминал. Я пишу свой код на Neovim и запускаю его с помощью racket filename.scheme.

Элементы языков программирования

Язык программирования — это не просто средство, позволяющее инструктировать компьютер выполнять задачи.

Он служит основой того, как мы думаем о процессах. Таким образом, когда мы описываем язык, нам следует также обращать внимание на средства, которые язык предоставляет для объединения простых идей в более сложные идеи.

В каждом мощном языке есть три механизма для достижения этой цели:

  • Примитивы — простейшие объекты языка.
  • Средства комбинирования –способ создания составных элементов из более простых.
  • Средства абстракции. Способ, с помощью которого вы можете называть составные элементы и использовать их как единое целое.

В программировании мы имеем дело с двумя видами элементов:

  • Процедуры — описания правил манипулирования данными.
  • Данные — данные, которыми мы хотим манипулировать.

Короче говоря, любой мощный язык программирования позволит нам определять примитивные данные и процедуры, а также комбинировать и абстрагировать их.

Схема скоростного бега

Я говорил вам, что сегодня мы собираемся изучить большую часть Scheme, так что давайте ускорим это.

Выражения

(+ 4 5)

(- 5 5)

(* 6 4)

(/ 4 6)

(+ 4.4 5.4)

Вот как вы используете выражения в языке (в других терминах программирования это то, что вы бы назвали функциями).

В нем используются префиксные обозначения, поэтому оператор (процедура, принимающая аргументы) находится первым слева, а его аргументы, называемые операндами, — справа.

Вы можете спросить, почему мы используем префиксную запись.

Причина в том, что это довольно просто и устраняет двусмысленность. Рассмотрим уравнение:

5 + 5 – 2

Мне сначала добавить 5 + 5 или сначала 5 -2? Это неочевидно. Мы бы написали это в Scheme следующим образом:

(+ 5 (- 5 2 ))

Как видите, это гораздо более явно.

Переменные и процедуры

(define x 3)

(define squared (* x x))

(define (multiply_by_x arg1)(* arg1 x))

(define squared_squared (* squared squared))

squared

(multiply_by_x 3) // returns 9 

// Both should returns 9 

squared_squared // returns 81 

Для определения переменной и процедур мы используем примитивное выражение define.

Как вы можете видеть в приведенных выше примерах, мы определили переменную и два метода: один без аргументов и один с ними.

Да, язык такой простой.

Условные предложения

Мы используем примитивное выражение cond для определения условных операторов.

(define (abs x) 
  (cond 
    ((> x 0) x) 
    ((= x 0) 0)
    ((< x 0) (- x))
))

Если вы запутались, мы просто определили процедуру с именем abs, которая принимает аргумент и возвращает разные значения в зависимости от некоторых условий.

Мы также можем написать это в виде if-else:

(define (abs x)
  (cond ((< x 0) (- x))
    (else x)))

Оценка комбинаций

Язык — это нечто большее, чем то, что мы рассмотрели, но при необходимости мы рассмотрим это позже.

А сейчас давайте быстро рассмотрим, как оцениваются комбинации.

ПС. Комбинация — это просто процедура, состоящая из других процедур.

Возьмем для примера эту процедуру:

(* (+ 2 (* 4 6))
   (+ 3 5 7))

Чтобы оценить это, мы сначала должны:

  • Оцените подвыражения комбинации.
  • Примените процедуру самого левого (корневого) выражения с аргументами, являющимися результатом подвыражений.

Это будет выглядеть примерно так:

Вы можете сказать: «О, это довольно просто».

Это так, но это показывает нам важную концепцию в информатике — рекурсию. Правило оценки является рекурсивным по своей природе. То есть в качестве одного из шагов оно включает необходимость вызова самого правила.

Хотя это не всегда так

Существуют специальные примитивы, называемые специальными формами, которые не следуют правилам вычисления, одним из них является примитив define.

В оценке определения нет второго шага.

Define — единственный пример специальной формы, который мы видели до сих пор, но вскоре мы познакомимся и с другими. Каждая специальная форма имеет свое собственное правило оценки.

Аппликативный порядок против нормального порядка

Помните, как мы говорили ранее о правилах оценки Scheme?

Этот метод, при котором мы сначала оцениваем подвыражения, называется аппликативным порядком.

Существует альтернативный метод, называемый нормальным порядком, при котором вы пытаетесь полностью расшириться, а затем уменьшиться. Это также называется ленивой оценкой.

Это может показаться немного сложным, но давайте рассмотрим пример:

Представьте, что у вас определены две процедуры с именами sum-of-squares и square. То, что они делают, довольно понятно: сумма квадратов принимает два аргумента и суммирует их квадраты. Square просто принимает один аргумент и возвращает его квадрат.

Имея в виду эти два, давайте посмотрим на следующую комбинацию:

(sum-of-squares (+ 5 1) (* 5 2))

Попробуем максимально расширить эту комбинацию:

(sum-of-squares (+ 5 1) (* 5 2))

-> (+ (square (+ 5 1)) (square (* 5 2)))

-> (+ (* (+ 5 1) (+ 5 1))) (* (+ 5 2) (+ 5 2))))

Как видите, мы не можем расширить +, потому что это примитивное значение. Но мы сделали все возможное и максимально расширили это сочетание.

Следующий шаг — сокращение, также называемое оценкой наших выражений. Это будет выглядеть примерно так:

Но они оба дают один и тот же ответ.

Да, но это не всегда так.

Обычная оценка порядка весьма полезна, и мы рассмотрим это в главах 3 и 4 книги.

Абстракция черного ящика

В начале этой статьи я сказал, что одним из механизмов мощного языка программирования является способность абстрагировать.

Как мы видели в Scheme, мы можем сделать это, скрывая сложность путем определения процедур. Поэтому в будущем, когда нам придется решить большую и сложную проблему, разбейте ее на более мелкие части.

Решите эту конкретную крошечную проблему и абстрагируйте ее, рассматривая ее как черный ящик. Вас действительно волнует не то, как это работает, а то, что это выводит.

Спасибо за прочтение.