Про мову програмування scheme
Про мову програмування Scheme
0. Невелике вступ.
Дана замітка присвячена мові програмування (ЯП) Scheme.
Scheme як і Lisp є мультіпарадігменним мовою програмування "майже без синтаксису", розроблений в Массачуссетського Інституті Технологій. Деякі називають Scheme мовою функціонального програмування (ФП), прирівнюючи таким чином його до чисто функціональним мовам на подобі Haskell, однак, незважаючи на те, що Scheme дозволяє писати код в стилі ФП, таке прирівнювання некоректно. Точніше і докладний опис мови ви можете знайти в Вікіпедії, там же знайдете і ще деякі корисні посилання.
Оскільки до написання цієї замітки я вже залишив кілька подібних на парі форумів в мережі, то ця, перша, замітка в даному блозі буде відносно довшою, ніж наступні і складатися з вищезазначених замітках на форумах.
1. Трохи про "майже відсутньому синтаксисі".
На відміну від більш поширених і звичних ЯП, таких як C, C ++, Pascal, Java, PHP і т. Д. В Scheme і Lisp використовується префиксная запис функцій і операторів, а також величезна кількість дужок, що може відлякати новачків від вивчення мови, однак чим далі ви зайдете в вивченні Scheme, тим більше ви будете розуміти, що це дуже зручна форма запису, що відкриває великі можливості для маніпулювання кодом і даними. Втім не виключений ефект ще більшого відторгнення =). В цьому випадку погляньте на Forth, де використовується польська, Постфіксний нотація, вона вам здасться ще більш незвичній, і відразливою =).
Отже, ось кілька прикладів програм на Scheme:
(* 1 2 3 4 5); Твір чисел від 1 до 5
Як видно з прикладу, синтаксис дійсно дуже регулярний і майже відсутня, будь-яка функція має наступну форму виклику:
(Funcname arg1 arg2 arg3. ArgN)
званої S-виразом. Межами форми служать круглі дужки, а роздільниками імен - порожній простір (символи пробілу, табуляції і нового рядка). Перший символ інтерпретується як виклик функції з ним пов'язаної, для інших обчислюються значення, з ними пов'язані, при чому немає ніякої різниці, що є значенням - функція або, наприклад, числова константа, тому передача функцій як параметрів інших функцій є простою і не псує синтаксису, наприклад:
2. Трохи про списках.
Основною структурою даних в Scheme, як і в Lisp, є список. Основні функції роботи зі списками:
(Cons 1 2); повертає пару значень: (1. 2)
(Cons 1 # 039 ()); повертає список з одного елемента: (1)
(List 1 2 3); Повертає список значень: (1 2 3)
(Cons 1 (cons 2 (cons 3 # 039 ()))); повертає список значень: (1 2 3)
(Car (list 1 2 3)); повертає значення першого елемента (голови) списку (1 2 3): 1
(Cdr (list 1 2 3)); повертає список, що починається з другого елементу (хвіст) списку (1 2 3): (2 3)
конструкція # 039 () позначає порожній список, символ # 039 ж позначає цитування та є синтаксичним цукром для особливої форми (quote.). Наприклад виклик функції
(List 1 2 3); можна замінити на конструкцію:
# 039 (1 2 3)
Однак не варто їх плутати, щодо констант-значень типу цифр, чисел, рядків, взятих в подвійні ковичкі, обидва вирази рівноправні, однак по відношенню до символів, пов'язаних з будь-яким значенням, дають різний результат, наприклад:
(Let ((x 1) (y 2) (z 3)); Нехай x = 1, y = 2, z = 3, тоді:
(List x y z)); поверне список значень змінних x, y і z: (1 2 3)
(Let ((x 1) (y 2) (z 3))
# 039 (x y z)); поверне список символів x y і z: (x y z)
3. Про макросах.
Макроси дозволяють створювати нові особливі форми, розширюючи тим самим синтаксис мови. наприклад:
(Define-syntax my-if; Оголошуємо ім'я синтаксичної конструкції
(Syntax-rules (then else); описуємо правила: в першому списку - (then else) -
; перераховуємо слова, які будуть невичісляемимі, а ля "зарезервовані слова"
; в наступних списках описуються шаблони, які буде обробляти макросом
; і власне обробники:
((_ Condition then on-true else on-false); тут описуємо шаблон конструкції: _ - сюди підставляється ім'я макросу,
; можна написати my-if замість _. без різниці
; then і else як ми описали раніше просто слово,
; condition, on-true і on-false - так би мовити локальні змінні макросу,
; вираження, передані в них - обчислювані
(If condition on-true on-false)); а це обробник шаблону. шаблонів може бути кілька, наприклад можна додати ще 2:
((_ Condition then on-true)
(If condition on-true))
((_ Condition else on-false)
(If (not condition) on-false))
)); тут закриваємо списки винятків форм syntax-rules і define-syntax.
; тепер ми можемо користуватися придуманим макросом:
(My-if (> 2 4) then (display "Y \ n") else (display "N \ n"))
(My-if (> 4 2) then (display "Y \ n"))
(My-if (> 4 2) else (display "Y \ n"))
Скористатися просто визначенням функції тут не вдалося б, так як значення аргументів функції обчислюються перед передачею в функцію, т. Е. У нас б в будь-якому випадку програма виконала б оператори обох гілок, незалежно від значення аргументу-умови і щоб уникнути цього, нам довелося б кожен раз явно передавати аргументи-гілки через функцію (delay.). відкладати обчислення свого аргументу.
Ще один приклад, який показує реалізацію операторів i ++, i--, x + = y, x - = y, x * = y і x / = y, що містить в тому числі і елементи метапрограмування: макрос make-op сам створює для нас макроси , що визначають чотири останніх оператора:
(Define-syntax make
(Syntax-rules ()
((_ Op func)
(Define-syntax op
(Syntax-rules ()
((_ X y)
(Begin (set! X (func x y)) x)))))))
(Define-syntax ++
(Syntax-rules ()
((_ I)
(Begin (set! I (+ i 1)) i))))
(Define-syntax -
(Syntax-rules ()
((_ I)
(Begin (set! I (- i 1)) i))))
(Define x1 2)
(Define x2 3)
(Message "x1 =" x1 "; x2 =" x2)
(++ x1)
(+ = X2 (- x2))
(Message "x1 =" x1 "; x2 =" x2)
(+ = X1 x2) (message "x1 =" x1)
(* = X1 x2) (message "x1 =" x1)
(- = x1 x2) (message "x1 =" x1)
(/ = X1 x2) (message "x1 =" x1)
4. Про каррінг, замиканнях і функціях вищого порядку.
Тепер прімерчік з каррінг (приведення функції n аргументів до функції одного аргументу, що повертає функцію n-1 аргументів) і функцій вищого порядку (функції, які оперують іншими функціями):
(Define (make-val-list v0 dv vN)
(Define (make-list v)
(If (= v vN)
(Cons v # 039 ())
(Cons v (make-list (+ v dv)))))
(Make-list v0))
(Define A-list (make-val-list 1 + 1 3))
(Define x-list (make-val-list 0.2 0.1 1.2))
(Define (y A)
(Lambda (x) (- (* A x) (tan (* pi (/ x 4))))))
(Define y-list (map (lambda (A)
(Map (y A) x-list))
A-list))
(Display (map (lambda (ls) (apply max ls)) y-list))
В даному коді функція-від (x, A) зведена до функції y (A), яка повертає функцію-від (х), тобто її можна було б застосовувати так:
(Let ((A1 1) (x1 0.2))
((Y A1) x1))
Каррінг дуже схожий на замикання як я зрозумів, тільки замикання проявляється в наступних діях:
(Define y1 (y 1)); створить функцію (y1 x) = (- (* 1 x) (tan (* pi (/ x 4))))
(Define y2 (y 2)); створить функцію (y1 x) = (- (* 2 x) (tan (* pi (/ x 4))))
(Y1 x1)
(Y2 x1)
У прикладі застосовувалися функції вищого порядку map і apply, вони працюють таким чином:
1) map застосовує передані їй в якості переданого аргументу функцію до кожного елементу списку, переданого другим аргументом, і повертає список значень-результаттов застосування, наприклад
(Define test-list # 039 (-2 1 7 0 -5 4))
(Map abs test-list); поверне список абсолютних величин значень списку test-list: (2 1 7 0 5 4)
2) apply застосовує передану їй як перший аргумент функцію до всіх елементів списку, переданого другим аргументом,
як буд-то вони (елементи списку) є параметрами переданої функції, наприклад функції
(Max -2 1 7 0 -5 4); \
(+ -2 1 7 0 -5 4); _вернут максимальне значення і суму значень переданих в якості параметрів відповідно.
проте спроба викликати їх таким чином:
(Max test-list). \
(+ Test-list); _видаст помилку.
Це не дуже зручно, коли, наприклад, в ході роботи програми генерується якийсь список значень, з якого потім потрібно вибрати максимальне значення, або суму підрахувати. Тут-то і приходить на допомогу функція apply, яка ніби розкриває список, т. Е. Вираження
(Apply max test-list); \
(Apply + test-list); _еквівалентни виразами
(Max -2 1 7 0 -5 4); \
(+ -2 1 7 0 -5 4); _соответственно.
І в завершенні повернемося до каррінг. Як видно з опису функції map, вона може оперувати тільки функціями однієї змінної. Ось тут-то до нас і приходять на допомогу каррінг і замикання =).
5. Трохи про функції і макроси з будь-якою кількістю аргументів.
Як ви вже помітили, деякі функції та форми можуть приймати будь-яку кількість аргументів, наприклад:
(+ 1 2 3)
(+ 1 2 3 4)
(And exp1 exp2)
(And exp1 exp2 exp3 exp4)
Щоб створити подібну функцію, необхідно використовувати точкову запис аргументів, наприклад:
(Define (message head. Tail)
(Display head)
(For-each display tail)
(Newline))
У такому визначенні функції перший аргумент буде передаватися в неї безпосередньо, параметром head, а всі інші - списком tail. Тут використовується ще одна функція вищого порядку for-each, яка застосовує функцію, передану в якості першого аргументу до кожного елементу списку, переданого другим аргументом, але на відміну від функції map не повертає ніякого значення. тепер можна так застосовувати функцію message:
(Message "Hello World!")
(Let ((world "World"))
(Message "Hello" world "!"))
Визначити форму, приймаючу довільну кількість аргументу, можна подібним чином:
(Define-syntax and
(Syntax-rules ()
((_) #t)
((_ E) e)
((_ E1 e2 e3.)
(If e1 (and e2 e3.) #f))))
Тоді можна буде використовувати нову форму таким чином:
На цьому я завершую свій огляд. Сподіваюся він виявився / виявиться для когось корисним, для кого-то цікавим. =)