Главное меню
Мы солидарны с Украиной. Узнайте здесь, как можно поддержать Украину.

ЛІСП: урок перший

Автор Python, февраля 16, 2012, 01:45

0 Пользователи и 1 гость просматривают эту тему.

Python

Загальна інформація
Словом LISP у наш час позначають групу споріднених мов програмування, найпоширенішими з яких є Common Lisp, Scheme та Clojure. Існують і інші лісп-подібні мови. І хоча під словом «Лісп» у вузькому розумінні частіше мається на увазі CL, тут ми будемо розглядати переважно Scheme та Clojure.

Для початку, бажано встановити якийсь лісп-транслятор.
Scheme існує в безлічі реалізацій та діалектів, між якими можуть бути деякі відмінності. Я орієнтуюсь переважно на JVM, тому використовую KAWA і SISC, хоча більшість реалізацій Scheme обходяться без джави. З відомих реалізацій можна згадати, наприклад, GNU Guile(не знаю, правда, наскільки воно кросплатфорне в умовах Win32).

Clojure реалізовано для JVM, також є реалізації для деяких інших віртуальних платформ (CLR(.NET/Mono), JS), якими я не користувався, тому нічого певного сказати не можу. Більшість інформації про Clojure, доступна в мережі, стосується JVM-реалізації.
Spoiler: Встановлення Clojure (для Windows) ⇓⇓⇓

Робота з лісп-подібними мовами передбачає, як правило, можливість використання інтерактивного середовища, також відомого як REPL (Read-Eval-Print Loop — цикл читання-обчислення-друку). Іншими словами, запустивши інтерпритатор, ми можемо безпосередньо в ньому ввести якийсь вираз і отримати результат. Приклад:
   Clojure 1.3.0
   user=> (+ 1 2 3)
   6
   user=> (list 1 2 3)
   (1 2 3)
   user=> (cons 1 (list 2 3))
   (1 2 3)
   user=> (cons 1 (cons 2 (cons 3 ())))
   (1 2 3)
   user=>
(тут і далі дії користувача в інтерактивному середовищі виділятимуться жирним курсивом). Наведені приклади команд дуже прості, тому однаково добре підходять для Clojure, Scheme, CL та інших ліспів. Перша дія — додавання. Особливість синтаксису лісп-подібних мов: у всіх арифметичних операціях використовується префіксна нотація, тобто, знак операції йде перед даними (а не посередині, як ми звикли), знак операції й параметри відокремлені один від одного пробілами, а увесь вираз береться в дужки. Така конструція відома під назвою S-вираз (S-expression). Зверніть увагу: дужки тут обов'язкові, їх не можна опускати чи обгортати вираз у додаткові дужки без необхідності — це змінює значення виразу чи призводить до помилки. Дужки вказують на виклик функції чи макроса — порівняйте це з дужками після імені функції в C-подібних мовах. S-вирази є базовим елементом мови, на їх основі будуються не лише арифметичні обчислення чи виклики функцій, а й алгоритмічні конструкції, описи функцій та ін.

Ще одна особливість — широке використання функцій зі змінною кількістю параметрів. Прикладами таких функцій є всі арифметичні операції, логічні операції (and, or), функція list (але не cons). Дуже зручно, що операції порівняння також можуть мати довільну кількість параметрів — таким чином, ми можемо записати однією дією порівняння одразу трьох-чотирьох чисел (у звичайній мові програмування нам би довелось писати (A=B)and(B=C), тут же ми пишемо (= A B C), тим самим звільняючись від необхідності двічі писати один і той же параметр, який може бути не лише числом чи змінною, а й вкладеним виразом).

Декілька слів про list. Ця функція просто отримує список параметрів і повертає їх як список. Як бачимо, на виводі цей список подається в дужках, подібно до S-виразів. Цей збіг не випадковий: будь-який вираз у дужках при читанні спершу перетворюється на список, і вже потім при обчисленні перший елемент цього списку інтерпритується як ім'я функції, яку треба викликати. Список необов'язково вводити: його можна згенерувати програмно і, наприклад, інтерпритувати його за допомогою функції eval. Сам же список складається з т.зв. cons-елементів. Такий елемент являє собою запис з двома полями, перше з яких (також відоме як car чи, в термінології Clojure, first) може мати довільний тип, а друге (cdr або rest) є посиланням на інший cons чи ознакою кінця списку. (Власне кажучи, у Scheme чи Common Lisp поле cdr також може мати довільний тип, тоді як у Clojure cons-комірка має дещо складнішу архітектуру і є елементом лінивої послідовності, але про це потім). Як бачимо, три останні вирази є повністю еквівалентними: список можна передати і за допомогою list, і за допомогою cons, якщо другий параметр є списком. Окремий випадок — порожній список (), що одночасно виступає як ознака кінця списку. Його неможливо передати за допомогою cons, хоча (list) без параметрів повертає його.

Ми можемо витягнути окремий елемент cons-комірки, використавши функції car та cdr чи first та rest:
   user=> (first (list 1 2 3))
   1
   user=> (rest (list 1 2 3))
   (2 3)
   
   SISC (1.16.6)
   #;> (car (list 1 2 3))
   1
   #;> (cdr (list 1 2 3))
   (2 3)
Крім того, існує можливість запису cons-структур з використанням крапки, що відокремлює cdr-частину. Цей спосіб не використовується в Clojure, але належить до базових можливостей синтаксису традиційних ліспів. Все, що записується як (a b c d), можна записати як (a . (b . (c . (d . ())))) — це одне й те ж. Замість (+ 2 2) можна написати (+ . (2 2)) чи (+ 2 . (2)) — результат буде той же. Також можуть існувати cons-структури, другий елемент яких не є cons чи порожнім списком — в цьому випадку, результат буде відображено в з використанням крапки:
   #;> (cons 1 2)
   (1 . 2)
Однак, ми не можемо писати (1 . 2) замість (cons 1 2) — це буде помилкою, оскільки першим елементом виразу має бути функція. Помилковим буде й запис (list 1 . 2) — вираз має бути правильним списком. Втім, подібна конструкція використовується, наприклад, при описі функцій зі змінною кількістю параметрів, тому деяка користь з неї є.
Пролетареві ніколи вчити європейських мов, бодай би свою знати добре і на ній принести до своєї хати світло знання (Гнат Хоткевич)
ÆC CASALI NAXI PRASQURI: AHOV CÆRU, MERTVÆRI TÆ SLAVUTÆT!
Вони просили його: «Скажи: кетум», а він говорив: «сатем», і не міг вимовити правильно.
Хотелось бы также отметить, что "Питон" - это "мышиный язык" : "пи+тон". © АБР-2

Python

Ще один спосіб запису списку — цитування:
   user=> '(1 2 3)
   (1 2 3)
Зверніть увагу на апостроф перед списком. Якщо його пропустити, ми отримаємо ось що:
   user=> (1 2 3)
   ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn  user/eval10 (NO_SOURCE_FILE:6)
В перекладі на людську мову, ми спробували обробити цей список як S-вираз, але його перший елемент є не функцією, а числом, тому сталась помилка. Механізм цитат потрібен, щоб вказати, що список є безпосередньо списком, а не виразом, який треба обчислити. Окрім цитати, також існує псевдоцитата — шаблон, в який можна вставити обчислювані вирази:
   user=> `(1 2 ~(+ 1 2))
   (1 2 3)
У Scheme та Common Lisp синтаксис псевдоцитат дещо відрізняється:
   #;> `(1 2 ,(+ 1 2))
   (1 2 3)
Загалом, те ж саме, але замість тильди — кома. Також псевдоцитата дозволяє вставляти списки в списки як фрагменти:
   #;> `(1 ,@(list 2 3) 4)
   (1 2 3 4)

   user=> `(~@(list 1 2) 3 4)
   (1 2 3 4)
Дуже часто цитати використовуються для передачі фрагментів програмного коду. Наприклад, можна зацитувати вираз:
   user=> '(* 2 2)
   (* 2 2)
Як бачимо, результатом такого цитування є список. Такий список можна, наприклад, обчислити як вираз:
   user=> (eval '(* 2 2))
   4
Функція eval розцитовує цитату, хоча замість цитати може бути будь-який списковий вираз, у т.ч. й згенерований програмно:
   user=> (eval (list '* 2 2))
   4
   user=> (eval (cons '* (list 2 2)))
   4
Зацитоване '* є ім'ям (англ. symbol, не плутати з character; щоб уникнути неоднозначностей зі словом «символ», symbol будемо називати ім'ям чи ідентифікатором, character — літерою чи знаком). Іменний тип не має прямих аналогів у більшості алгоритмічних мов. Його роль — позначати якийсь елемент синтаксису чи назву програмного об'єкта (функції, змінної). Саме по собі ім'я, однак, не є еквівалентом об'єкта, який воно позначає:
   user=> '*
   *
   user=> *
   #<core$_STAR_ clojure.core$_STAR_@bef361>
В першому випадку, ми зацитували ідентифікатор і отримали на виході той же ідентифікатор. У другому — ідентифікатор, переданий без цитати, перетворився на посилання на функцію, символьне представлення якої ми бачимо на виході (отриманий текст #<...> ми не можемо ніде застосувати в програмі — це просто інформація про об'єкт, а не його адреса, ім'я чи двійковий код). Зверніть увагу: +, -, * є такими ж іменами, як cons чи list. Імена в ліспах можуть складатися з найрізноманітніших знаків — це можуть бути не лише літери та цифри, а й +-*/<>= та ін. Наприклад, дозволеними ідентифікаторами є abc,   +,   ->,   *out*,   fluid-let, --1 (але не -1 — це вже числовий літерал), Apollo-13, zero?, $100 тощо. Власне, синтаксис CL та Scheme дозволяє використовувати всі можливі символи — для цього ідентифікатор обрамлюється в ||, між якими може бути що-завгодно, включно з пробілами та символами нового рядка. Clojure дає менше свобод(навіть / тут використовується обмежено, оскільки має спеціальне призначення). На практиці ж, типовий ліспівський ідентифікатор може складатися з літер, цифр та мінусів, які використовуються для розділення слів у складному ідентифікаторі.

Трохи про числа й математику. Як я вже згадував вище, арифметичні операції є функціями з довільною кількістю параметрів.
   #;> (+ 3)
   3
   #;> (+ -3 3)
   0
   #;> (+ 3 3 3 3 3 3 3 3 3 3 3 3 3)
   39
   #;> (- 1 2 3)
   -4
   #;> (- 3)
   -3
   #;> (* 1)
   1
   #;> (* 1 2 3)
   6
   #;> (/ 6 3)
   2
   #;> (/ 1 2 3)
   1/6
   #;> (/ 3)
   1/3
В останніх двох випадках ми бачимо ще одну цікаву можливість ліспу: звичайні дроби. Їх можна задати або дробовим літералом (наприклад, просто написати 2/3 чи 147/1024), або отримати — наприклад, при діленні цілих чисел, що не діляться. Операція ділення з одним аргументом ділить одиницю на цей аргумент. Також є числа з плаваючими комами:
в результаті арифметичних дій з ними отримуються лише числа з плаваючими комами:
   #;> (* 1.0 1/2)
   0.5
   #;> (+ 5.5 11/2)
   11.0
   #;> (/ 1.0 2.0 3.0)
   0.16666666666666666

Для початку, мабуть, досить. Тепер ви можете використовувати LISP як примітивний калькулятор з нестандартним синтаксисом та додатковими непотрібними можливостями :)
Пролетареві ніколи вчити європейських мов, бодай би свою знати добре і на ній принести до своєї хати світло знання (Гнат Хоткевич)
ÆC CASALI NAXI PRASQURI: AHOV CÆRU, MERTVÆRI TÆ SLAVUTÆT!
Вони просили його: «Скажи: кетум», а він говорив: «сатем», і не міг вимовити правильно.
Хотелось бы также отметить, что "Питон" - это "мышиный язык" : "пи+тон". © АБР-2

Demetrius

Offtop
Слава великим роботам! Зараз я мушу йти, але сьогодні почитаю. Хінти додати сьогодні не обіцяю, але додам потім.

arseniiv

Про псевдоцитаты не знал и ещё про что-то.

Хороший урок! := Спасибо за труд.

Demetrius

Питання про запуск Clojure. Ось в Kaw'і можна зробити java kawa.repl -w і сидіти з графічним інтерфейсом замість консолі.

В Clojure щось таке є?

Бо консоль в Windows не дуже...

Python

У стандартному комплекті графічного середовища нема. Однак, існують IDE для Clojure, що включають у себе REPL. Часто рекомендують Emacs з плагіном для Clojure (під Windows це середовище найпростіше встановити за допомогою Clojure Box). Хоча, як на мій смак, стандартна консоль симпатичніша :)
Пролетареві ніколи вчити європейських мов, бодай би свою знати добре і на ній принести до своєї хати світло знання (Гнат Хоткевич)
ÆC CASALI NAXI PRASQURI: AHOV CÆRU, MERTVÆRI TÆ SLAVUTÆT!
Вони просили його: «Скажи: кетум», а він говорив: «сатем», і не міг вимовити правильно.
Хотелось бы также отметить, что "Питон" - это "мышиный язык" : "пи+тон". © АБР-2

Python

Ще одне середовище — Clooj.
Виглядає достатньо легким, але так само чомусь справляє враження чогось глючного й ненадійного (дратує, що в ньому не можна просто працювати в REPL — треба, щоб обов'язково був відкритий якийсь проект, інакше всі інструкції, введені в REPL input, просто копіюються в REPL output, замість того, щоб виконуватись).
Пролетареві ніколи вчити європейських мов, бодай би свою знати добре і на ній принести до своєї хати світло знання (Гнат Хоткевич)
ÆC CASALI NAXI PRASQURI: AHOV CÆRU, MERTVÆRI TÆ SLAVUTÆT!
Вони просили його: «Скажи: кетум», а він говорив: «сатем», і не міг вимовити правильно.
Хотелось бы также отметить, что "Питон" - это "мышиный язык" : "пи+тон". © АБР-2

Быстрый ответ

Обратите внимание: данное сообщение не будет отображаться, пока модератор не одобрит его.

Имя:
Имейл:
Проверка:
Оставьте это поле пустым:
Наберите символы, которые изображены на картинке
Прослушать / Запросить другое изображение

Наберите символы, которые изображены на картинке:

√36:
ALT+S — отправить
ALT+P — предварительный просмотр