Lisp без скобок
От: Lazy Cjow Rhrr Россия lj://_lcr_
Дата: 04.12.06 19:20
Оценка: 319 (34) +1

Parentheses? What parentheses? I haven't noticed any parentheses since my first month of Lisp programming. I like to ask people who complain about parentheses in Lisp if they are bothered by all the spaces between words in a newspaper...
-- Kenny Tilton <tilt@liii.com>

Похоже что время от времени интерес к Лиспу просыпается не только у меня. Я думаю (надеюсь) что сия подборка материала буде интересна многим. (Заранее прошу извинения у Схеме-ров, в некоторых случаях я под словом Лисп буду понимать и Лисп, и Схему).

Не секрет, что подавляющее число людей, впервые (или не впервые) увидев Лисп, испытывают самые разные эмоции от крайнего скептицизма ("Никто не использует Лисп! Бессмысленная куча скобок! Мёртвый язык! Не для настоящей работы! Какой смысл изучать?"), ступора ("Ах**ть, ничего не понятно!") и крайнего удивления: "Неужели есть перцы, которые в этом чего-то разбирают?" Некоторые люди по-человечески жалеют лисперов: "Эхх, должно быть тяжело разбираться со всеми этими скобками..."

К счастью находятся особо любознательные, которые и сами решают разобраться, чего же такого особенного в Лиспе, что можно терпеть эту кучу скобок. Они копают чуть-чуть глубже и однажды выясняют, что есть reader macros, и с помощью них можно воротить что душе заблагорассудится вплоть до полной замены синтаксиса. И ведь воротят же!

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

Начну с того, что сам Пол Грэхэм в описании языка Arc соглашается, что синтаксис Лиспа бывает некомфортен для чтения и записи и высказывает идею, что некоторые скобки можно вывести из отступов и переводов строки. В частности, если строка содержит больше одного S-выражения, следовательно должен существовать префикс в списке для этих S-выражений. Если строки имеют одинаковый отступ, то они должны быть продолжением какого-то S-выражения. Выражения, где разделителями являются переводы строк и отступы получили название I-выражений.
... // префикс должен быть где-то здесь
  (+ 1 2) (- 3 4) // значит здесь должна быть закрывающая скобка
// проверим дискриминант 
(> (/ (+ (- b) (sqrt (- (* b b) (* 4 a c)))) (* 2 a)) 0)
// запись того же в I-выражениях
>
  /
    +
      - b
      sqrt
         -
           * b b
           * 4 a c
    * 2 a
  0


Итак, проект Lispin, то есть LISP INdented. Попытка заменить некоторые уровни вложенности отступами, тем самым уменьшив количество скобок, то есть замены S-выражений на I-выражения. На страничке проекта есть окошко, куда можно чего-нибудь ввести и посмотреть, как расставятся скобки.
defun factorial (n)  if (<= n 1)
    ^ 1
    * n
      factorial (- n 1)

К сожалени есть только для Схемы. создатель Bill Birch хотел сделать для Лиспа, но преждевременно запостил свой результат в comp.lang.lisp (об этом ниже)

Scheme Request for Implementation #49. Всего в 5 экранах вполне законченная референсная имплементация той же идеи замены некоторых S-выражений на I-выражения. Здесь все выражения эквивалентны:

  let
   group
    foo
     + 1 2
    bar
     + 3 4
   + foo bar
  let
   group
    foo (+ 1 2)
    bar (+ 3 4)
   + foo bar
  (let
   (group (foo (+ 1 2)) (bar (+ 3 4)))
   (+ foo bar))
Далее можно упомянуть TwinLisp — это транслятор-фронтэнд из TwinLisp кода в стандартный Common Lisp код. В противоположность Лиспу TwinLisp использует унарные и бинарные операторы типа +, *, блоки в стиле Java и тому подобный сахар. Программы в TwinLisp выглядят примерно так (результат трансляции ниже):
progn {
    a = 0
    progn {
        b = 1
        a = b+2
        a }}

(PROGN (LET (A) (SETF A 0) (PROGN (LET (B) (SETF B 1) (SETF A (_+_ B 2)) A))))

В общем-то попытка так радикально отойти от синтаксиса Лиспа при этом сохранив возможность обращения к символам стандартного Лиспа привела к не всегда хорошо читаемым результатам (символы Лиспа должны предваряться амперсандом плюс ещё много правил всяких):
mac &infinite-loop (**body) {`do () { $@body }}

(DEFMACRO INFINITE-LOOP (&BODY BODY) `(DO NIL (NIL) ,@BODY))


Дальнейшее развитие идеи исправления синтаксиса Лиспа может зайти достаточно далеко, и скорее всего это будет уже самостоятельный язык. Самый известный пример: Dylan (уже даже не напоминает Лисп ):
define macro let-com-interface
  { let-com-interface ?:name :: ?type:expression = ?value:expression;
    ?:body }
  => { let ?name :: ?type = ?value;
       block()         
         ?body
       cleanup
         release(?name)
       end }
end macro let-com-interface;

define function as-com-type(type, interface-ptr)
  let (result, com-interface) = QueryInterface(interface-ptr, 
    dispatch-client-uuid(type));  
  if(SUCCEEDED?(result))
    pointer-cast(type, com-interface);
  else
    #f
  end if;
end function as-com-type;


Есть ряд любительских экспериментальных имплементаций I-выражений, например здесь, здесь, здесь и здесь.

Не могу не упомянуть страницу David A. Wheller'а Readability. На это странице есть ссылки на реализацию предлагаемого им "сахара", кроме того там можно найти ссылку на очень большую и детальную статью "Readable s-expressions and sweet-expressions: Getting the infix fix and fewer parentheses in Lisp-like languages. Если очень коротко, то Дэвид предлагает формировать "sweet-expressions" тремя способами:
1. Отступами (то есть те самые I-выражения).
2. Выносом имён за скобки. То есть термы в форме "NAME(x y ...)" без пробела между "N
AME" и "(" интерпретируются как "(NAME x y ...)". Если содержимое в скобках инфиксное выражение, то оно рассматривается как один параметр.
3. Инфиксная запись. Выражения автоматически интерпретируются как инфиксные выражения, если их второй параметр является инфиксным оператором ("инфиксный оператор" задаётся регэкспом "[+-\*/<>=&\|\p{Sm}]{1-4}|\:"), первый параметр не инфиксный оператор и выражение состоит из как минимум трёх параметров. Иначе выражение интерпретируется как нормальное S-выражение. Инфиксные выражения должны иметь нечётное количество параметров где чётные места занимают бинарные инфиксные операторы. Операторы должны отделяться пробелами. Приоритет поддерживается. Таким образом
2 + y * -(x)   эквивалентно   (+ 2 (* y (- x)))


Многие реализации Схемы (например MzScheme) поддерживают стиль, (либо поддержка содержится в библиотеке), когда можно комбинировать различные скобки, что немного улучшает читаемость кода, но при этом оставляет S-выражения самими собой:
(define preved [lambda (x) {+ x 1}])



Другой путь борьбы со скобками — это сохранить скобки, но не показывать пользователю, дабы не травмировать его чувствительную натуру Наверное самый шикарный результат — это плагин color-box для Emacs:


Ещё интересен подход, когда одновременно с вводом выражений на Лиспе тут же показывается соответсвтвующее дерево. На мой взгляд, превосходный подход для обучения:

Это скриншот японского редактора GTEdit.

Собственно от идеи редактирования S-выражений один шаг до идеи редактирования сразу деревьев, но редактора такого я пока не видел.

Подозреваю, что о Lispworks, Allegro CL и SBCL все знают. Обеспечиваемый сервис настолько высок, что скобы совершенно не напрягают. А вот про CUSP — плагин для Эклипса лично я узнал совсем недавно.



Наконец последний, третий подход в борьбе со скобками — это борьба со стереотипами новичков и профанацией. Характерный пример — A Beginners' Meta FAQ for comp.lang.lisp:

1. Spend some time learning a little Lisp before trying to ask questions on c.l.l....

2. Lisp beginners are often scared by Lisp's novel syntax, and sometimes think to propose a way to "simplify" it by making it look like something they are familiar with (usually their favorite programming language)...
Generally speaking, if you are a Lisp beginner it's best not to try to make suggestions of how Lisp should be fixed until you understand it relatively well...


Несмотря на кажущуюся привлекательность (частичной) замены S-выражений на I-выражения, этот подход встретил почти полное непонимание среди как опытных лисперов, так и не очень опытных. Например, упомянутый выше Lispin вместе с создателем порвали на лоскуты (имеется ввиду что реакция была от скептической до резко негативной):
ANNOUNCE: Lisp Without Parentheses Project (Lispin) Site Open
Lisp and Scheme with fewer parentheses
(поэтому он обиделся и не хочет больше заниматься проектом Lispin).

Также упомянутая выше работа David A. Wheller также стала причиной жарких разборок
sweet-expressions instead of s-expressions?
(Неприятная черта в Лисп-коммьюнити (может только на c.l.l.) — просто зашкаливающая "доброжелательность" к новичкам. Можно конечно понять, но всё равно не очень ...)

Самый оригинальный аргумент, который почти всегда непонятен из-за казалось бы очевидной противоречивости, это "There's no parenthesis in lisp". Тем не менее внятное и подробное объяснение этого тезиса
здесь. Суть сводится к следующему: профессионалы глядя на S-выражение видят сразу дерево, поэтому они не видят скобок!
defun
 |--fact
 |--<parameterList>
 |  `--N
 `--if
    |-->
    |  |--N
    |  `--1    
    |--*
    |  |--N
    |  `--fact
    |     `--+
    |        |---1
    |        `--N    
    `--1


На самом деле слова в защиту скобок можно найти у многих авторов: Пола Грэхема, Питера Норвига, Питера Зибеля и прочих "лисп-хакеров", и все утверждают, что скобки не зло, а большое благо. Можно этим парням верить на слово? Стопудово можно. Но до наступления просветления нужно как то жить, и на мой взгляд лучше выбрать редактор поудобнее.

Ну вот, в принципе всё что я хотел сказать. Спасибо за внимание.

PS: увы, много буков, но надеюсь не смертельно.
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.