Pull to refresh
347.09
TINKOFF
IT’s Tinkoff — просто о сложном

Углубление в Scheme

Reading time5 min
Views19K

Если единственный инструмент, которым вы располагаете, это молоток, то множество различных предметов покажутся вам гвоздями.
Марк Твен


Часть 1 Введение в Scheme
Часть 2 Углубление в Scheme
Часть 3 Практика IronScheme

Знакомимся ближе


Настало время изучить основные конструкции языка Scheme. Самый лучший способ научиться использовать новый язык это начать на нем писать. Начнем постепенное погружение с разбора самых базовых элементов языка.

Предлагаю запустить интерпретатор IronScheme в REPL режиме и вводить приведенные ниже команды.

Однострочный комментарий начинается с точки с запятой и действует до конца строки:
; эта строка будет игнорирована интерпретатором

Программа на Scheme состоит из списков заключенных в круглые скобки и разделенных пробелом(s-выражение). Вызов функции записывается как (f x y z …), где f имя функции, а x, y, z, … операнды.
(+ 2 2)  ;  => 4

Здесь мы выполнили операцию сложения двух чисел.

Выражения могут быть вложенными:
(+ 2 (+ 1 1))  ; => 4

Таким образом, выражения могут состоять из атомов или других выражений. В приведенном выше примере числа «1» и «2» атомы, а «(+ 2 (+ 1 1))» и «(+ 1 1)» выражения.

Примитивы


Числа:
9999999999999999999999 ; integers
#b111                  ; binary => 7
#o111                  ; octal => 73
#x111                  ; hexadecimal => 273
3.14                   ; reals
6.02e+23
1/2                    ; rationals
1+2i                   ; complex numbers

Чтобы создать список каких-либо значений, например из тех же примитивов, следует использовать функцию list, аргументы которой соберутся в список. Можно поступить другим способом, подавить вычисление. Подавление достигается функцией quote или ее сахарным аналогом одиночной кавычкой перед списком “ ’ ”. Важно помнить, что list не подавляет вычисления.
(list 1 2 3 4 5 (+ 1 2 3))    ; => (1 2 3 4 5 6)
(quote (1 2 3 4 5 (+ 1 2 3))) ; => (1 2 3 4 5 (+ 1 2 3))
'(1 2 3 4 5 (+ 1 2 3))        ; => (1 2 3 4 5 (+ 1 2 3))

Некоторые арифметические операции


(+ 1 1)              ; => 2
(- 8 1)              ; => 7
(* 10 2)             ; => 20
(expt 2 3)           ; => 8
(quotient 5 2)       ; => 2
(remainder 5 2)      ; => 1
(/ 35 5)             ; => 7
(/ 1 3)              ; => 1/3
(exact->inexact 1/3) ; => 0.3333333333333333
(+ 1+2i  2-3i)       ; => 3-1i


Булева алгебра


Для обозначения истины имеется примитив “#t”, ложь обозначается как “#f”, кроме того все другие значения отличные от “#f” трактуются как истина:
(not #t)   ; => #f
(and 0 #f) ; => #f
(or #f 0)  ; => 0


Символы, строки


Согласно стандарту RnRs, символы можно представить в коде двумя способами, символом который обозначает самого себя или кодом символа:
#\A     ; => #\A
#\x03BB ; => #\λ

Строки являются массивами символов фиксированной длинны и заключаются в двойные кавычки:
"Hello, world!"

Кавычки внутри строки можно экранировать обратным слешем:
"Benjamin \"Bugsy\" Siegel" 

Для печати строки в стандартный вывод можно использовать функцию «display» принимающую в качестве аргумента строку:
(display "Some string")

Строки можно объединять:
(string-append "Hello " "world!") ; => "Hello world!"

Получить доступ к символу строки по индексу можно так:
(string-ref "Apple" 0) ; => #\A

Для форматирования строк удобно использовать функцию форматирования:
(format  "~a can be ~a" "strings" "formatted") ; => "strings can be formatted"

Чтобы результат форматирования не был потерян можно присвоить строку переменной:
(define str (format "~a can be ~a" "strings" "formatted"))


Переменные


Вы можете объявлять переменные, используя функцию «define», в которой первый аргумент имя функции, второй не обязательный аргумент значение которым переменная будет инициализирована. Имя переменной может содержать любые символы за исключением: ()[]{}”,’`;#/\
Например:
(define some-var 5)
some-var ; => 5

Переменные в Lisp без строгой типизации и могут указывать на атомы или функции.
Операция «set!» сохраняет значение в переменной, по сути, является аналогом оператора присвоения «=» из других языков. Не забываем сначала объявить переменную операцией «define»:
(define my-name "unknown")
my-name ; => "unknown"

(set! my-name "NalaGinrut")
my-name ; => " NalaGinrut "

Попытка доступа к прежде необъявленной переменной вызовет исключение.
Удобно объявлять сразу группу локальных переменных при помощи конструкции (let …).
(let 
  (
    (a "My") 
    (b "name") 
    (c "is") 
    (d "Bob")
  ) 
  (set! d "Sara")
  (format "~a ~a ~a ~a" a b c d)
)

; => My name is Sara


Функции


Для создания функции используется конструкция (lambda (a b c) body), где a, b, c аргументы, body последовательность команд.
(lambda () "Hello World")
(lambda (x) (+ x x)) 

Созданная выше функция не имеет имени, поэтому обратиться к ней нет возможности. Чтобы получить доступ к созданной функции ее можно присвоить переменной
(define hello-world (lambda () "Hello World"))
(hello-world) ; => "Hello World"

Или так:
(define hello-world)
(set! Hello-world (lambda () "Hello World"))
(hello-world) ; => "Hello World"

Обычно используется более удобная конструкция (define (function-name arg1 arg2) body)
(define (mul a b) (* a b))
(mul 2 3); => 6

Функция всегда возвращает последнее значение.
(define (last-string) “one” “two” “three”)
(last-string) ; => “three”


Управление потоком


Для ветвления в Scheme существуют, различные конструкции наиболее привычной, но не всегда самой удобной может показаться конструкция типа if-then-else
(if #t               ; условие
  "this is true"     ; если истина
  "this is false")   ;  ели ложь
; => "this is true"

Если в ветви необходимо выполнить несколько команд подряд, то их следует заключить в блок «begin»
(if (< 1 3) 
  (begin
    (display “one line”)
    (newline)
    (display “two line”)
    (- 1 3)
  )
)

Когда нужно проверить несколько условий удобна конструкция «cond»
(cond 
  ((> 2 2) "wrong!")
  ((< 2 2) "wrong again!")
  ((= 2 2) "ok")
  (else "wrong also")
)

Если код необходимо выполнить только в случае истины прекрасно подходит «when»
(when (< 1 3) “true”)

Организовать цикл можно двумя способами, рекурсией
(define (lp i)
  (when (< i 10)
    (display (format  "i=~a\n" i))
    (lp (+ 1 i))
  )
)
(lp 5) ; => i=5, i=6, ...

Или при помощи именованного «let»
(let loop ((i 0))                 ; definition
  (when (< i 10)                  ; condition
    (display (format "i=~a\n" i)) ; body
    (loop (+ 1 i))                ; next iteration
  )
) ; => i=0, i=1, ...


Макросы


Макросы Scheme довольно мощный инструмент, который позволяет расширять синтаксис языка, создавая новые конструкции. Однако не следует слишком увлекаться и применять макросы только там, где это действительно необходимо. Определение макроса начинается с команды «define-syntax»
(define-syntax macro
  (syntax-rules (<keywords>)
    ((<pattern>) <template>)
    ...
    ((<pattern>) <template>)
  )
)

<keywords> — Ключевые слова, которые можно будет использовать в описании шаблона. Например, можно написать макрос для конструкции «(forech (item in items) …)», в данном случае ключевым словом будет «in», которое обязательно должно присутствовать.

<pattern> — Шаблон, описывающий, что на входе у макроса.

<template> — Шаблон, описывающий, во что должен быть трансформирован В макросе многоточие «…» означает, что тело может содержать одну или более форм.

Рассмотрим применение макросов для создания циклов while и for.
(define-syntax while
  (syntax-rules ()
    ((while condition body ...)
      (let loop ()
        (when condition
          body ...
          (loop)
        )
      )
    )
  )
)

проверим созданный макрос
(define iter 0)
(while (< iter 10) (set! iter (+ iter 1)) (displayln iter )) ; => 1 2 3 …

Определим макрос для цикла for:
(define-syntax for
  (syntax-rules ()
    ((for (iterator from to) body ...)
      (let loop((iterator from))
        (when (< iterator to)
          body ...
          (loop (+ 1 iterator))
        )
      )
    )
  )
)

Проверим:
(for (i 0 15) (displayln i)) ; => 1 2 3 ...


Исключения


В жизни не очень редко появляется необходимость использовать нестабильный код, который во время исполненя может вызвать исключение. В Lisp и в частности Scheme развитая система обработки исключений, ниже приводится простой пример, как можно обрабатывать исключения не опасаясь, что программа потерпит крах.
(guard
  (cond ; переменная c информацией об исключении
    (display (condition-message cond)) ; тело обработчика
  )
  (/ 1 0) ; код вызывающий исключение
)

Мы изучили некоторые из основ языка Scheme описанные стандартом. Конечно, в данной статье приведены далеко не все возможности языка иначе статья получилась бы слишком объемной, по сути это был бы перевод стандарта. Однако, то что мы узнали, вполне достаточно для разработки практически полезных приложений на Scheme.
Tags:
Hubs:
+16
Comments10

Articles

Information

Website
www.tinkoff.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия