Новый булшит ↑

Несколько недель назад я почти потерял всякий интерес к Clojure. И правда, что в ней может быть такого интересного? Она простая, функциональная и в ней есть транзакционная память, персистентные структуры, метаданные и удобные макросы, но я изучал её около года, и в общем, не скажу что её фичи убийственнее фич других языков. В конце концов, я скажу лишь, что это чертовски хороший лисп, на котором приятно писать многопоточные программы: это продукт эволюции, но не революции.

Я пишу эту заметку с небольшим опозданием, всего каких-то пару-тройку недель, да к тому же это еще и злостный самобоян с моей стороны, но да ладно; так вот, недавно в ленте гугл-ридера мне попалась пара постов в ЖЖ — автор показал свою реализацию игры «жизнь» на Clojure — всего 6 строк кода.

(def nbr-deltas [[-1 -1][-1 0][-1 1][0 -1][0 1][1 -1][1 0][1 1]])

(defn nbr-cells [[x y]]
  (map (fn [[a b]] [(+ x a)(+ y b)]) nbr-deltas))

(defn cell-table [cell]
  (apply conj {cell 10} (map #(vec [% 1]) (nbr-cells cell))))

(defn all-table [cells]
  (apply merge-with + (map cell-table cells)))

(defn next-gen [cells] 
  (keys (filter #(#{3 12 13} (second %)) (all-table cells))))

(defn nth-gen [n cells]
  (if (== n 0) cells (recur (- n 1) (next-gen cells))))

Я подумал «почему бы не сравнить „функциональность“ Clojure с „функциональностью“ Python?»

Ну и сравнил: не вдаваясь в подробности и особо не раздумывая на красóтами результата, передрал код один-в-один.

nbr_deltas = (-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)

def nbr_cells(c):
  return ret[(c[0] + d[0], c[1] + d[1]) for d in nbr_deltas]

def cell_table(cell):
  return dict((c, v) for c, v in [(cell, 10)] + [(nbr, 1) for nbr in nbr_cells(cell)])

<<define-function-merge-with>>

def all_table(cells):
  return merge_with(lambda x,y: x+y, [cell_table(cell) for cell in cells])

def next_gen(cells):
  return [cell for cell, val in all_table(cells).iteritems() if val in (3, 12, 13)]

def nth_gen(n, cells):
    gen = cells
    for i in range(0, n):
        gen = next_gen(gen)
    return gen

Фактически, те же самые 6 строк. Единственные отличия — в библиотеке питона нет функции merge-with для слияния словарей, и функция nth-gen реализована императивно из-за отсутствия оптимизации хвостовой рекурсии в питоне.

def merge_with(f, maps):
    result = {}
    for map in maps:
        for key, val in map.iteritems():
            if key in result:
                result[key] = f(result[key], val)
            else:
                result[key] = val
    return result

Автор Clojure-версии протестировал программу на своём ноутбуке:

Ура! Все работает. Расчет занял 12 секунд на МacBook Pro (2.53 GHz Intel Core 2 Duo, 4GB RAM), то есть около 100 поколений в секунду.

> (count (nth-gen 1103 [[0 1][1 1][2 1][1 2][2 0]]))
116

Я протестировал свою версию на своём безымянном нетбуке российской сборки, с процессором Atom N450 1.4 и 2G RAM под Jolicloud 1.0; и у меня расчёт занял те же 12 секунд.

>>> print len(nth_gen(1103, ((0, 1), (1, 1), (2, 1), (1, 2), (2, 0))))
116

P.S. Уже довольно долго у меня зреют сумбурные мысли по поводу использования goto, рекурсии, циклов, и DSL (предметно-ориентированных языков) для решения одних и тех же задач. Буквально месяц назад к этому прибавились еще и размышления о литературном программировании, но сейчас я не смогу связать всё это в что-то осмысленно-структурированное, поэтому предлагаю вам взглянуть на решение одной маленькой задачи, а именно реализации функции nth-gen в четырех парадигмах, и высказать здесь первое, что пришло в голову:

  • Рекурсия, Clojure
    (defn nth-gen [n cells] 
      (if (== n 0) 
          cells
          (recur (- n 1) (next-gen cells))))
    
  • Цикл, Python
    def nth_gen(n, cells):
        gen = cells
        for i in range(0, n):
            gen = next_gen(gen)
        return gen
    
  • DSL, Common Lisp
    (defun nth-gen (n, cells)
      (loop repeat n for gen = cells then (next-gen gen)
            finally return gen))
    
  • Литературное программирование, любой язык
    <<Получить n-ое поколение клеток применив функцию
      'next-generation' к поколению 'cells' n раз>>
    
    <<Получить n-ое поколение клеток применив функцию
      'next-generation' к поколению 'cells' n раз>>=
    Реализация этого псевдокода на чем угодно и как угодно, с
    оптимизацией, без оптимизации, рекурсией, через GOTO, на асме...
    
    Здесь мог бы быть любой из приведенных выше кусков кода.
    @
    

Как-то раз я смотрел лекции университета Беркли по структурам данных (так и не досмотрел со всеми этими блогами), и нашло на меня вдохновение мáлое — подумал «дай думаю напишу крестики-нолики». Лектор как раз про игровые деревья рассказывал, а примером выбрал крестики-нолики, tic-tac-toe по-ихнему — на доске показывал. Еще он пару алгоритмов привел: старый добрый минимакс и α-β-поиск. Но до альфа-бета-поиска у меня руки не дошли, так что я написал минимакс-процедуру, которая говорит кто победит в партии.

Почему я об этом пишу? Оно того стоит, я думаю. Уж больно красивый получился код. С моей колокольни, конечно; но после того как я написал этот код, я для себя отметил на будущее, что в общем, понимаю причину по которой в MIT выбрали питон первым языком — он достаточно хорош для этого. И по-моему, этот код послужит отличным доказательством тому факту, что для многих, очень многих не сильно сложных функциональных программ питон — лучший выбор в плане понятности, нежели Scheme.

def wins(grid):
    rows = [[grid[i+j] for j in 0, 1, 2] for i in 0, 3, 6]
    cols = [[grid[i+j] for j in 0, 3, 6] for i in 0, 1, 2]
    digs = [[grid[i] for i in 0, 4, 8], [grid[i] for i in 2, 4, 6]]
    return any(all(cell is 'x' for cell in row) or
               all(cell is 'o' for cell in row)
               for row in rows + cols + digs)

def full(grid):
    return all(cell is 'x' or cell is 'o' for cell in grid)

def move(grid, cell, symbol):
    moved = list(grid)
    moved[cell] = symbol
    return moved

def moves(grid, symbol):
    return [move(grid, cell, symbol) for cell in 0, 1, 2, 3, 4, 5, 6, 7, 8
            if grid[cell] not in ('x', 'o')]

def minimax(grid, symbol):
    if wins(grid):
        return (1 if symbol is 'o' else -1), None
    elif full(grid):
        return 0, None
    elif symbol is 'x':
        best_score = -2
        best_move = None
        for move in moves(grid, 'x'):
            score, mv = minimax(move, 'o')
            if score >= best_score:
                best_score = score
                best_move = move
        return best_score, best_move
    elif symbol is 'o':
        best_score = 2
        best_move = None
        for move in moves(grid, 'o'):
            score, mv = minimax(move, 'x')
            if score <= best_score:
                best_score = score
                best_move = move
        return best_score, best_move

def tictac(grid, turn):
    score, move = minimax(grid, turn)
    result = ('x win' if score is 1 else
              'o win' if score is -1 else
              'draw')
    print 'On grid %s best move is %s and %s.' % (grid, ''.join(move), result)

Приведенный выше код достаточно прост и не нуждается в комментариях. Но это еще не все. Без примеров использования он будет не полон. Они тоже весьма неплохи, на мой вкус.

tictac('xo-'
       'x-o'
       '-xo', 'x')
# On grid xo-x-o-xo best move is xo-x-oxxo and x win.

tictac('xox'
       'oxo'
       'ox-', 'x')
# On grid xoxoxoox- best move is xoxoxooxx and x win.

tictac('x-o'
       '-ox'
       '-x-', 'o')
# On grid x-o-ox-x- best move is x-o-oxox- and o win.

tictac('x-o'
       '---'
       '---', 'x')
# On grid x-o------ best move is x-o-----x and x win.

tictac('---'
       '--x'
       'oo-', 'x')
# On grid -----xoo- best move is -----xoox and x win.

tictac('ox-'
       '-x-'
       'oox', 'x')
# On grid ox--x-oox best move is ox-xx-oox and draw.

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

Традиция такая. Про переезд на новый хостинг пост. А как иначе? Новоселье!

Вот помню еще той весной задумал завести свой блог, но осенью пишу статью; как раз пол года срок. Ну что ж, начну рассказ с начала.

Все началось с ЖЖ. И все закончилось уже, хватило мне единственного взгляда — ЖЖ ужасен, и сколько бы народу не сидело в нем — порывы рвотные я не могу сдержать при взгляде на его дизайн. Могу лишь навещать, пусть через силу, друзей в сообществах родных по духу. На каждый комментарий что оставил в нем, читая интересный пост, затрачено пустых усилий было столько, что мог бы целый пост я написать, будь волен я не заходить по десять раз на сайт и не елозить мышью по доброй сотне комментариев ветвей.

Потом я Blogger испытал, но не прижился он: был черезчур раздут (или мне показалось). Задумка в целом глупой оказалась, ведь корпорации добра до блогов дела нет.

Ничем кроме проклятья не назову я то, что настигает гугл в его попытках тщетных представить публике почтенной уютное местечко для бесед. Волну никто не понял, а Buzz, в который каждого из нас как будто носом ткнули — вовсе не базар, а так… лоток в безлюдной деревушке, в котором обречен подряд который месяц себя любимого читать, и кросспосты с чтеца, и твиты с твиттера, короче — что угодно. Единственное понял я — оригинального контента не сыскать, там все из третьих мест и не серьезно.

О Posterous товарищ Умпутун в своем подкасте замолвил доброе слово — грех было не попробовать, и честно вам скажу — понравилось. Чего уж там, я всем рекомендую на Posterous взглянуть, запостить что-нибудь, и до тех пор пока ваши посты будут просты вам не в чем будет блог-платформу упрекнуть.

Прошло три месяца, и все бы ничего, но нет, хозяева платформы поспешили презентовать markdown для постов, но допилить забыли возможность обновлять посты потом; чем причинили мне долгие часы в попытках обновить свой пост. Все было тщетно.

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

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

В чем суть? Зачем ради простого блога такие огороды городить?

Шаблоны? HTML? CSS?
Ради индивидуальности. Когда-нибудь настанет время, захочется чтоб просто было все, красиво, чтоб нашу отражало суть, чтоб каждый мог к нам заглянуть и посмотреть не на шаблон, которых 100500 в Сети, а на творение…

Здесь путь один, и раз уж взялся показать себя в Сети — освой HTML и CSS, без них здесь не пройти.

Git
Для техников пишу эту статью. Ужель ты с Википедией знаком? Гит — то же самое, но лучше; хранилище для текста.

Чем отличается программа от статьи? Ничем. Суть текст простой. И нам же лучше, если мы сможем откатить ошибки, поправить сайт не будучи в сети, друзьям послать — пускай помогут нам, и от греха подальше сохранить на множестве машин.

sudo aptitude install git-core
Emacs
При чем здесь Emacs? А при том, что мы работать будем в нем. В Org Mode.

Что лучше может быть, чем пост писать в редакторе достойном? Шаблоны править в нем, по ходу: CSS, HTML, коммитить в нем же в git, и слушать музыку (но я здесь не о том). С тех пор как я намучался с плодами Web 2.0, порою к страшным прибегая извращениям (навроде написания статьи в редакторе GMail'а) подход, где все в одном, все по-старинке — спокойствие вселяет, и заставляет к благому делу большее усердие прилягнуть.

Org Mode
Буквально месяц с тех времен прошел когда впервые я прослышал о сей моде. Немного поискал и примянул использовать разок. Потом другой. И третий. В конце концов оставил не у дел я множество вещей, будь то ToDo-листы, календари и прочий ширпотреб — org-mode с лихом эти вещи заменяет.

Я был шокирован не далее чем пару дней назад, наткнувшись на один из тысяч форков настроек Emacs Starter Kit. От радости я плакал, когда код читал (сам Кнут бы плакал тоже) — настройки те написаны в «литературном стиле». В том самом стиле был написан «TeX». Суть в кратце такова: внутри «org-mode»-файла лежит рассказ о коде, код; рассказ сам — исполняется, двух зайцев убивая в раз — в конце у нас есть просто код и есть рассказ (пригодный к публикации в блоге или в виде PDF-файла сгенерированного LaTeX'ом — безо всякой дополнительной обработки).

Jekyll
Генератор Jekyll написан на Ruby, заточен под блоги. Работа с ним идет таким ходом: пользователь в директории, git-репозитории, создает поддиректории особые, которые наполняет шаблонами, картинками, страницами стилей, скриптами и собственно, постами. Когда очередной работы выполнен кусок — контент коммитится и отправляется на сервера гитхаба, там прогоняется через наш генератор, он из шаблонов и кусков конечный собирает блог.

Без Jekyll'а мы можем обойтись вполне — достаточно нам git'а, но чтоб по десять тысяч раз нам в облако не пушить и не плодить коммитов — поставим; а позже в нем мы сможем запустить и протестировать наш блог.

Вам правду доложу — поставить Jekyll было трудно, я битый час консоль пытал; так было это нудно. Я весь расстроился и был немного зол, что много нехороших слов про руби в тви наплёл. Но превозмог и сделал грязный хак — скачал пакет из Meerkat (вот дурак ;)

sudo aptitude install ruby-full
wget http://mirrors.kernel.org/ubuntu/pool/universe/libg/libgems-ruby/rubygems1.8_1.3.7-2_all.deb
sudo dpkg --install rubygems1.8_1.3.7-2_all.deb
sudo gem install jekyll

Пускай вас эта ложка дегтя не смущает, хотя стабильности она не добавляет, стабильность оставляю не у дел; ведь так всегда — поставил свежий софт и глюки потерпел.

Бинарь ищите в недрах:

/var/lib/gems/1.8/gems/jekyll-0.7.0/bin/jekyll
Ubuntu
Не знаю, как там кто — я пользуюсь Убунтой; тому есть сто причин, но главная из них — я не хочу напрасно тратить время, работаю по принципу «работает — хуй с ним».

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

Мой блог; хоть он не совершенен — своею писаниной дорожу, поэтому я поступаю просто и сурово — я git-репозиторий в svn держу (в Dropbox'е тобиш):

cd ~/Dropbox
git init zahardzhan.github.com
cd ./zahardzhan.github.com

Здесь имя — zahardzhan, пишу своё, я вашего не знаю; читатель, укажи своё, но не забудь .github.com приписать — таков формат.

Залейте бложик на гитхаб.

git remote add origin git@github.com:zahardzhan/zahardzhan.github.com.git
git push origin master

В своем устройстве блог не терпит беспорядка — давно уж прописали: что где лежит и что чему принадлежит.

~
   Dropbox
      zahardzhan.github.com
         atom.xml
         _config.yml
         css
            screen.css
         favicon.ico
         images
            image1.png
            image2.png
         index.html
         _layouts
            default.html
            post.html
         org
            2010-10-09-buddhas-zen.org
            2010-10-10-blog-for-programmer.org
         _posts
            2010-10-09-buddhas-zen.html
            2010-10-10-blog-for-programmer.html
         _site

Вот файлы, по порядку:

atom.xml
Шаблон канала новостей. Уважь читателя — кто будет блог читать без новостей? И пропиши канал в feedburner.com скорей.
_config.yml
Настройки Jekyll в формате YAML. Подробнее в оригинале. Я не большой фанат — держу всё по-дефолту:
css
Здесь я держу страницу стиля.
favicon.ico
Иконка.
images
Картинки.
index.html
Заглавная страница. Три черточки вверху в формате YAML front matter укажут заголовок и шаблон в который будет вставлена страница.
_layouts
Шаблоны сайта; используют язык liquid. Примеры приводить не стану — там много кода для поста, но если любопытно вам, прошу репозиторий блога навестить.
org
Хранилище постов в формате Emacs Org-mode. Отсюда готовые посты будут компилироваться в html-файлы в директорию
_posts
Хранилище для постов в формате html, markdown и textile. Отсюда готовые посты будут компилироваться в html-файлы в директорию
_site
Хранилище скомпилированых постов в формате html. Отсюда они уже никуда не будут компилироваться, поэтому эту директорию нужно проигнорировать в своем .gitignore.

Перед письмом постов настроим Emacs, чтоб правильно он импорт проводил. Нам от готового поста нужно одно лишь тело (контент между тагами <body>).

(setq org-publish-project-alist
      '(("zahardzhan.github.com-org"
         :base-directory "~/Dropbox/Blog/org/"
         :base-extension "org"
         :publishing-directory "~/Dropbox/Blog/_posts/"
         :recursive t
         :publishing-function org-publish-org-to-html
         :headline-levels 4
         :html-extension "html"
         :body-only t)

        ("zahardzhan.github.com-static"
         :base-directory "~/Dropbox/Blog/org/"
         :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|php"
         :publishing-directory "~/Dropbox/Blog/_posts/"
         :recursive t
         :publishing-function org-publish-attachment)

        ("zahardzhan.github.com"
         :components ("zahardzhan.github.com-org"
                      "zahardzhan.github.com-static"))))

Посты мы пишем так:

#+OPTIONS: H:3 num:nil toc:nil \n:nil @:t ::t |:t ^:t -:t f:t *:t TeX:t LaTeX:nil skip:nil d:t 
#+STARTUP: SHOWALL INDENT
#+BEGIN_HTML
---
layout: post
title: Мой первый пост
---

Мой первый пост!

Название .org-файла должно указывать на то, что это пост; он должен быть назван так: 2010-10-10-my-first-post.org, иначе непойдет.

Как будет пост готов — нажмём C-c e X zahardzhan.github.com (с расчетом на свой сайт).

Взглянём что получилось (в браузере по адресу localhost:4000)

~/Dropbox/zahardzhan.github.com # /var/lib/gems/1.8/gems/jekyll-0.7.0/bin/jekyll --server

Magit'ом закоммитим новый _posts/2010-10-10-my-first-post.html.

git push. На этом всё.

Будда сказал:

Я считаю королей и правителей пылинками праха. Я смотрю на сокровища, золото и жемчуг, как на кирпич и гальку. Я смотрю на лучшие шёлковые одежды, как на рваные лохмотья. Я вижу мириады миров Вселенной как маленькие зёрнышки, а самое большое озеро в Индии — как капельку масла на моей ноге.

Я понимаю, что учение о мире — это иллюзии фокусников.

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

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

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

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

Если в школе не было настоящего мотива заморачиваться графическим представлением текстов — всё писалось от руки, написанное не читалось более чем двумя людьми, то сейчас, когда статью в блоге могут прочесть сотни и тысячи человек, мотив определенно есть.

В отличие от языка, с его сотнями сложных правил; правил которые говорят о том, как должен выглядеть красивый печатный текст гораздо меньше, и они значительно проще. Если не вдаваться в подробности профессиональной типографики, то для получения сносного текста достаточно уметь применять всего 6 символов. Здесь я не буду рассказывать о правилах применения — о них хорошо рассказал Лебедев в своем «Ководстве»: § 62. Экранная типографика, § 97. Тире, минус и дефис, § 104. Кавычки, § 158. Короткое тире; я расскажу как прикрутить типографику к Emacs.

Вам понадобится пакет typopunct.el, положите его в вашу директорию с пакетами Emacs.

Поместите нижеследующий код в конфигурационный файл Emacs'а.

  1. Загружаем typopunct.el и выбираем русскую типографику:
    (require 'typopunct)
    (setq-default typopunct-buffer-language 'russian)
    
  2. Функция включает typopunct-mode.
    (defun turn-on-typopunct-mode ()
      (typopunct-mode t))
    
  3. Здесь я включаю типографику в режимах org-mode и markdown-mode. Чтобы включить типографику в других режимах — добавте функцию turn-on-typopunct-mode в соответствующие ловушки или воспользуйтесь командой M-x typopunct-mode.
    (add-hook 'markdown-mode-hook 'turn-on-typopunct-mode)
    (add-hook 'org-mode-hook 'turn-on-typopunct-mode)
    

Запустите Emacs и типографские символы к вашим услугам:

  • Дефис: -
    Кто-либо
  • Короткое тире: --
    20092010
  • Тире: ---
    Кто здесь?
  • Неразрывный пробел и тире: C-x 8 Space ---
    Уссурийск — Владивосток
  • Кавычки-ёлочки: "
    «ёлочки»
  • Кавычки-лапки: '
    лапки
  • Одинарная кавычка: C-q '
    'символ
  • Двойная кавычка: C-q "
    "строка"

В прошлой статье я рассказал об очень позитивных впечатлениях Дональда Кнута от созданной им методологии «литературного программирования», которые не покидают его на протяжении последних двадцати лет. Мои впечатления так же весьма позитивны. Однако, я посетовал, что литературное программирование совсем не используется в сообществе программистов, и в особенности — в мире Open Source. Дабы не быть голословным, представлю вашему вниманию программу Emacs Starter Kit, которую я постарался сделать настолько литературной, насколько это возможно. Но я не супермэн и не могу действительно хорошо объяснить код, который писали несколько сотен человек, к тому же я совсем не силен в Emacs Lisp, поэтому лучшее пояснение я дал тому коду, который написал сам; если вы сильны в программировании Emacs и если у вас есть идеи и предложения по улучшению, дополнению, исправлению и объяснению приведенного здесь когда — милости прошу — форкайте и пульте коммиты; комментируйте здесь и пишите мне на почту.

Итак, это статья является Emacs Lisp-программой, основной частью пакета настроек Emacs Starter Kit, который служит мне и еще как минимум двум тысячам человек в качестве основного конфигурационного файла редактора Emacs. Этот файл хранится в моем git-репозитории на гитхабе. Мой репозиторий является форком репозитория eschulte, литературный конфиг которого я перевел и дополнил. Его репозиторий является в свою очередь форком репозитория technomacy, автора оригинального Kit.

  1. Emacs. Большая и сложная программа. Ему как ничему другому подходит это определение. Эмакс был создан очень давно в лаборатории искуственного интллекта MIT, он несет в себе гены очень своеобразной культуры лисп-хакеров былых времен. С тех пор он не раз переписывался, улучшался, дополнялся и обрастал тысячами расширений в течение десятков лет. Эмакс, если и был когда-то мощным текстовым редактором, сейчас стал некоей универсальной программой, в нём можно делать абсолютно всё что угодно, если это хотя бы немного затрагивает собой задачу отображения или редактирования текста, и даже больше. Людям знáющим универсальность играет на руку, ведь одной программой можно делать тысячу разных дел, при этом не прилагая усилий для переучивания к разным интерфейсам. Людей, впервые запустивших эмакс подобная универсальность и непривычный интерфейс поначалу пугает. Научиться пользоваться эмаксом, понять его идею и проникнуться ею задача непростая, трудоемкая и долгая.

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

  2. Настройка эмакса это своего рода декоративно-прикладное искусство, она — одновременно благословение и проклятье пользователя. В отличие от vi, главная прелесть которого, на мой взгляд, в том, что в нём всё «из коробки» прекрасно настроено — бери да пользуйся, эмакс в своем изкоробочном состоянии далеко не так хорош и удобен, как мог бы быть. С первого же запуска начинается эпопея: эмаксер затачивает редактор под себя, ставит расширения, заводит свои конфигурационные файлы, подсматривает кусочки кода у других, по крупицам собирает свои собственные настройки по всей сети — со временем накапливается приличная база кода. И всё бы ничего, но процесс доводки до более-менее юзабельного состояния очень долог — на это могут уйти целые месяцы и даже годы (на самом деле настройка эмакса не прекращается никогда).

    Всё то время, что я пользовался эмаксом я именно так и поступал: собирал конфиг по кусочкам из тысячи разных мест — форумы, ЛОР, хабр, эмакс-вики, статьи, репозитории с настройками продвинутых пользователей, страница Alex'а Ott'а, и прочее. В итоге конфиг разросся до неприличных размеров и превратился в страшную кашу. В общем, всё было плохо пока я не наткнулся на замечательный скринкаст Meet Emacs. В скринкасте автор не усердствует с базовой настройкой, а сразу после установки эмакса копирует в свою директорию .emacs.d репозиторий emacs-starter-kit. Сперва я скептически отнёсся к такому подходу, но попробовав Starter Kit раз, удалив, и попробовав во второй раз я проникся: его оказалось достаточно чтобы просто пользоваться эмаксом, не задумываясь о его серьезных улучшениях. Всё что мне осталось — установить через пакетный менеджер эмакса и операционной системы необходимые мне расширения, да добавить настройки шрифтов и клавиатуры для нетбука и настольного компьютера.

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

    Оригинальный Emacs Starter Kit, ныне «центральный репозиторий» оного — это личный конфиг Фила Хагельберга, собранный им за долгие годы использования эмакса. Сейчас же Kit — это сотни форков и тысячи следящих за ним на гитхабе.

  4. Установка. Emacs Starter Kit рассчитан на работу с Emacs от 22-ой версии и выше. Чтобы установить Emacs воспользуйтесь пакетным менеджером вашего дистрибутива; пользователи Mac OS X могут получить Emacs с сайта Apple, или собрать напрямую из исходного кода, следуя инструкциям в файле nextstep/INSTALL. Пользователи Windows могут скачать установщик с сайта GNU.

    Также вам понадобится система контроля версий git и система компьютерной верстки TeX (хотя она и необязательна).

  5. Установка Emacs Starter Kit элементарна: клонируйте git-репозиторий с гитхаба в директорию .emacs.d; но перед эти сохраните старые настройки и удалите .emacs:
    git clone http://github.com/zahardzhan/emacs-starter-kit.git ~/.emacs.d
    

    Затем установите git-сабмодули сторонних пакетов

    cd ~/.emacs.d/
    git submodule init
    git submodule update
    

    и соберите последнюю версию Org-mode

    cd ~/.emacs.d/src/org/
    make
    
  6. Запустите Emacs.
  7. Если вы хотите оставить свои старые настройки в ~/.emacs.d на месте и просто попробовать Starter Kit, запустите его следующей командой:
    emacs -q -l ~/emacs-starter-kit/init.el   
    
  8. После того как закончите установку вам, возможно, потребуется перезапустить Emacs несколько раз — во время загрузки пакетов с ELPA происходят ошибки разбора HTML — просто проигнорируйте их.
  9. Если после очередного обновления вы потеряете некоторые автозагрузчики, что даст знать о себе сообщениями об ошибках типа «void function: foobar», попробуйте использовать команду M-x regen-autoloads.
  10. Устройство. Директория .emacs.d Kit'а устроена следующим образом:
    .emacs.d/
      ...
      ... системные
      ...
      elpa/
      elpa-to-submit/
      src/
      init.el
      loaddefs.el
      package.el
      starter-kit.org
      ...
      ... пользовательские
      ...
      username.el
      username.org
      username/
        config1.el
        config2.org
        config3.el
        ...
      system-name.el
      system-name.org
    

    Пакетный менеджер ELPA находится в файле package.el. Он усанавливает пакеты в директорию elpa/.

    Библиотеки, которые ожидают отправки в ELPA передаются вместе со Starter Kit'ом в директории elpa-to-submit/. Эти файлы хранятся там временно до тех пор пока кто-нибудь не удосужится превратить их в нормальные пакеты. Как только они будут отправлены в ELPA, их можно будет удалить. Автозагрузчики для этих библиотек хранятся в файле loaddefs.el. Это позволяет им загружаться по требованию, а не при старте.

    Самый главный файл — это init.el, с него начинается загрузка. Он загружает Org-mode и передает управление этому файлу. Дальнейшая загрузка происходит в порядке выполнения Emacs Lisp-кода в этом файле. В последнюю очередь загружаются пользовательские файлы.

  11. Настройка. Для многих пользователей настройки приведенные здесь станут базой для собственных. Starter Kit предоставляет места для дополнительных настроек, специфичных для пользователей и для машин, на которых будет запущен Emacs. Эти места устроены таким образом, что позволяют легко управлять своими настройками и с легкостью, без конфликтов, получать обновления из основного репозитория.

    Чтобы сделать первый шаг к своим настройкам — создайте ветку репозитория Starter Kit для локальных изменений с помощью команды git branch. Оставьте главную ветку для получения обновлений и храните персональную информацию в своей ветке.

  12. Свои настройки вы можете хранить в файле названым именем вашего пользователя, с расширением .el или .org на конце. Если вы не уверены насчет имени пользователя — выполните в консоли команду
    echo $USER
    
  13. Если ваша конфигурация слишком велика для одного файла — можете разбить её на несколько файлов и сохранить в директории с именем вашего пользователя. Если такая директория существует — она будет добавлена к загрузочным путям и любые Emacs Lisp-файлы и файлы Org-mode с включенными кусками Emacs Lisp-кода будут загружены.
  14. Если вам нужны разные настройки для разных машин — храните их в файлах названых именем хоста с расширением .el или .org.

    Чтобы узнать имя хоста выполните в консоли команду

    hostname
    
  15. Прежде чем браться за создание своей конфигурации я рекомендую вам посмотреть секцию Customization в руководстве по GNU Emacs. Оно доступно непосредственно в самом Emacs по команде M-x info и сочетанию С-h i.

    Прочтите секцию Key Binding Conventions руководства — это поможет вам избежать проблем при определении своих сочетаний клавиш.

    Starter Kit идет с набором цветовых тем. Смотрите инструкции по установке тем в секции Цветовые темы.

  16. Установка дополнительных библиотек. В Starter Kit включено много полезных Emacs Lisp-библиотек, но, возможно, вам захочется установить еще несколько. Предпочтите установку библиотек из Emacs Lisp Package Archive, ELPA, установке из других мест — это избавит вас от необходимости вручную поддерживать зависимости и обновлять установленные библиотеки при появлении новых версий. В недалеком светлом будущем все пакеты будут устанавливаться через ELPA — он будет включен в 24-ую версию Emacs.

    Для установки пакетов вызовите меню установки и удаления командой M-x package-list-packages. Используйте клавишу i для отметки и x для установки отмеченых пакетов.

  17. Если библиотека не доступна через ELPA вы можете поместить её исходный код в директорию src. Любые находящиеся там пакеты будут автоматически добавлены к загрузочным путям при старте Emacs.
  18. Содействие. Если вы знаете толк в Emacs — попробуйте Starter Kit в качестве замены вашим нынешним настройкам. И если есть нечто без чего вы не можете жить — добавте это в Kit или дайте мне об этом знать, чтобы я это добавил.

    Приветствуется помощь в отправке новых библиотек в ELPA. Есть два способа: взять новые библиотеки, подготовить их к ELPA и забросить в директорию elpa-to-submit; или взять файлы из elpa-to-submit, и убедившись в корректности зависимостей, отправить их мэйнтеинеру ELPA. О том как это осуществить можно узнать на http://tromey.com/elpa/upload.html.

  19. Распространение. Файлы идущие в комплекте Starter Kit распространяются под теми же лицензиями что и Emacs, если не указано противное. Смотрите детали в файле COPYING.
  20. Реалиция Emacs Starter Kit. Ниже следует Emacs Lisp-код, который выполняется при каждом старте Emacs. Мы начинем с определения загрузочных файлов и установки загрузочных путей.
    (setq dotfiles-dir (file-name-directory
                       (or load-file-name (buffer-file-name))))
    
    (add-to-list 'load-path dotfiles-dir)
    (add-to-list 'load-path (concat dotfiles-dir "/elpa-to-submit"))
    (add-to-list 'load-path (concat dotfiles-dir "/elpa-to-submit/jabber"))
    
    (setq autoload-file (concat dotfiles-dir "loaddefs.el"))
    (setq package-user-dir (concat dotfiles-dir "elpa"))
    (setq custom-file (concat dotfiles-dir "custom.el"))
    
  21. Повсеместно используемые пакеты загружаются при старте Emacs, а не по требованию, т.к. они используются практически во всех сессиях.
    (require 'cl)
    (require 'saveplace)
    (require 'ffap)
    (require 'uniquify)
    (require 'ansi-color)
    (require 'recentf)
    
  22. Порт для совместимости с Emacs 22.
    (unless (functionp 'locate-dominating-file)
    (defun locate-dominating-file (file name)
      "Look up the directory hierarchy from FILE for a file named NAME.
       Stop at the first parent directory containing a file NAME,
       and return the directory.  Return nil if not found."
       ;; We used to use the above locate-dominating-files code, but the
       ;; directory-files call is very costly, so we're much better off doing
       ;; multiple calls using the code in here.
       ;;
       ;; Represent /home/luser/foo as ~/foo so that we don't try to look for
       ;; `name' in /home or in /.
     (setq file (abbreviate-file-name file))
     (let ((root nil)
           (prev-file file)
           ;; `user' is not initialized outside the loop because
           ;; `file' may not exist, so we may have to walk up part of the
           ;; hierarchy before we find the "initial UID".
           (user nil)
           try)
       (while (not (or root
                       (null file)
                       ;; FIXME: Disabled this heuristic because it is sometimes
                       ;; inappropriate.
                       ;; As a heuristic, we stop looking up the hierarchy of
                       ;; directories as soon as we find a directory belonging
                       ;; to another user.  This should save us from looking in
                       ;; things like /net and /afs.  This assumes that all the
                       ;; files inside a project belong to the same user.
                       ;; (let ((prev-user user))
                       ;;   (setq user (nth 2 (file-attributes file)))
                       ;;   (and prev-user (not (equal user prev-user))))
                       (string-match locate-dominating-stop-dir-regexp file)))
         (setq try (file-exists-p (expand-file-name name file)))
         (cond (try (setq root file))
               ((equal file (setq prev-file file
                                  file (file-name-directory
                                        (directory-file-name file))))
                (setq file nil))))
       root))
    
     (defvar locate-dominating-stop-dir-regexp
       "\\`\\(?:[\\/][\\/][^\\/]+\\|/\\(?:net\\|afs\\|\\.\\.\\.\\)/\\)\\'"))
    
  23. Функция для загрузки файлов starter-kit-*. Нигде не используется — весь код Kit хранится в этом файле.
    (defun starter-kit-load (file)
      "This function is to be used to load starter-kit-*.org files."
      (org-babel-load-file (expand-file-name file
                                             dotfiles-dir)))
    
  24. Менеджер пакетов ELPA. Загружаем пакетный менеджер.
    (require 'package)
    (package-initialize)
    
  25. Проверка доступа в Сеть. При работе в Windows функция network-interface-list недоступна, поэтому мы предполагаем что доступ в Сеть таки есть.
    (defun starter-kit-is-online? ()
      (if (and (functionp 'network-interface-list)
               (network-interface-list))
          (some (lambda (iface) 
                  (unless (equal "lo" (car iface))
                    (member 'up (first (last (network-interface-info (car iface)))))))
                (network-interface-list))
          t))
    
  26. Устанавливает из ELPA пакеты по списку. Это потребует сетевого подключения. Во время выполнения этого кода вам, возможно, придется несколько раз перезапустить Emacs из-за ошибок при получении пакетов.
    (defun starter-kit-install-packages-from-elpa (list-of-packages)
      (when (starter-kit-is-online?)
        (unless package-archive-contents 
          (package-refresh-contents))
        (dolist (package list-of-packages)
          (unless (or (member package package-activated-list)
                      (functionp package))
            (message "Installing %s" (symbol-name package))
            (package-install package)))))
    
  27. Перечисленные ниже пакеты будут автоматически получены и установлены из ELPA при первом запуске Emacs. Можете использовать этот код в своем конфигурационном файле для установки нужных вам пакетов.
    (starter-kit-install-packages-from-elpa '(idle-highlight
                                              ruby-mode
                                              inf-ruby
                                              js2-mode
                                              css-mode
                                              gist
                                              paredit
                                              yaml-mode
                                              find-file-in-project
                                              magit))
    
  28. Обход трудновоспроизводимого бага ELPA.
    (autoload 'paredit-mode "paredit" "" t)
    (autoload 'yaml-mode "yaml-mode" "" t)
    
  29. Установка загрузочных путей и файлов. Обход бага Mac OS X в котором имя системы является полным именем домена.
    (when (eq system-type 'darwin)
      (setq system-name (car (split-string system-name "\\."))))
    
  30. Определение файлов настроек, специфичных для пользователя и машины. Вы можете держать соответствующие настройки в простых emacs-lisp файлах и в файлах org-mode, таких как этот.
    (setq system-specific-config (concat dotfiles-dir system-name ".el")
          system-specific-literate-config (concat dotfiles-dir system-name ".org")
          user-specific-config (concat dotfiles-dir user-login-name ".el")
          user-specific-literate-config (concat dotfiles-dir user-login-name ".org")
          user-specific-dir (concat dotfiles-dir user-login-name))
    (add-to-list 'load-path user-specific-dir)
    
  31. Пакеты emacs-lisp, загруженные из директории src замещают те, что установленны через ELPA. Это полезно если вы используете самые свежие версии пакетов или если их нет в ELPA.
    (setq elisp-source-dir (concat dotfiles-dir "src"))
    (add-to-list 'load-path elisp-source-dir)
    
  32. Определения функций. Далее следуют определения часто используемых в Starter Kit функций.
    (require 'thingatpt)
    (require 'imenu)
    
  33. Указываем URL и открываем новый буфер с содержанием оного.
    (defun view-url ()
      "Open a new buffer containing the contents of URL."
      (interactive)
      (let* ((default (thing-at-point-url-at-point))
             (url (read-from-minibuffer "URL: " default)))
        (switch-to-buffer (url-retrieve-synchronously url))
        (rename-buffer url t)
        (cond ((search-forward "<?xml" nil t) (xml-mode))
              ((search-forward "<html" nil t) (html-mode)))))
    
  34. Обновляет индекс imenu и затем использует ido для выбора и перехода к символу. Символы которые совпадают с текстом под курсором появляются в первых позициях в списке дополнения.
    (defun ido-imenu ()
      "Update the imenu index and then use ido to select a symbol to navigate to.
       Symbols matching the text at point are put first in the completion list."
      (interactive)
      (imenu--make-index-alist)
      (let ((name-and-pos '())
            (symbol-names '()))
        (flet ((addsymbols (symbol-list)
                           (when (listp symbol-list)
                             (dolist (symbol symbol-list)
                               (let ((name nil) (position nil))
                                 (cond
                                  ((and (listp symbol) (imenu--subalist-p symbol))
                                   (addsymbols symbol))
    
                                  ((listp symbol)
                                   (setq name (car symbol))
                                   (setq position (cdr symbol)))
    
                                  ((stringp symbol)
                                   (setq name symbol)
                                   (setq position (get-text-property 1 'org-imenu-marker symbol))))
    
                                 (unless (or (null position) (null name))
                                   (add-to-list 'symbol-names name)
                                   (add-to-list 'name-and-pos (cons name position))))))))
          (addsymbols imenu--index-alist))
        ;; If there are matching symbols at point, put them at the beginning of `symbol-names'.
        (let ((symbol-at-point (thing-at-point 'symbol)))
          (when symbol-at-point
            (let* ((regexp (concat (regexp-quote symbol-at-point) "$"))
                   (matching-symbols (delq nil (mapcar (lambda (symbol)
                                                         (if (string-match regexp symbol) symbol))
                                                       symbol-names))))
              (when matching-symbols
                (sort matching-symbols (lambda (a b) (> (length a) (length b))))
                (mapc (lambda (symbol) (setq symbol-names (cons symbol (delete symbol symbol-names))))
                      matching-symbols)))))
        (let* ((selected-symbol (ido-completing-read "Symbol? " symbol-names))
               (position (cdr (assoc selected-symbol name-and-pos))))
          (goto-char position))))
    
  35. Есть несколько функций для включения разнообразных режимов при открытии буферов с исходным кодом. Здесь мы определяем эти функции и последовательно добавляем их в ловушку coding-hook; λ-функции не используются — у нас нет гарантии того что они уже не добавлены в ловушку.
    (defvar coding-hook nil
      "Hook that gets run on activation of any programming mode.")
    
    (defun local-column-number-mode ()
      (make-local-variable 'column-number-mode)
      (column-number-mode t))
    
    (defun local-comment-auto-fill ()
      (set (make-local-variable 'comment-auto-fill-only-comments) t)
      (auto-fill-mode t))
    
    (defun turn-on-hl-line-mode ()
      (if window-system (hl-line-mode t)))
    
    (defun turn-on-save-place-mode ()
      (setq save-place t))
    
    (defun turn-on-whitespace ()
      (whitespace-mode t))
    
    (defun turn-off-tool-bar ()
      (tool-bar-mode -1))
    
    (defun add-watchwords ()
      (font-lock-add-keywords
       nil '(("\\<\\(FIX\\|TODO\\|FIXME\\|HACK\\|REFACTOR\\):"
              1 font-lock-warning-face t))))
    
    (add-hook 'coding-hook 'local-column-number-mode)
    (add-hook 'coding-hook 'local-comment-auto-fill)
    (add-hook 'coding-hook 'turn-on-hl-line-mode)
    (add-hook 'coding-hook 'turn-on-save-place-mode)
    (add-hook 'coding-hook 'pretty-lambdas)
    (add-hook 'coding-hook 'add-watchwords)
    (add-hook 'coding-hook 'idle-highlight)
    
  36. Запуск ловушки coding-hook включает в буфере соответствующие режимы для удобной работы с исходным кодом.
    (defun run-coding-hook ()
      "Enable things that are convenient across all coding buffers."
      (run-hooks 'coding-hook))
    
  37. Заменяет отступы табами на отступы пробелами во всем буфере.
    (defun untabify-buffer ()
      (interactive)
      (untabify (point-min) (point-max)))
    
  38. Автоматически расставляет отступы во всем буфере.
    (defun indent-buffer ()
      (interactive)
      (indent-region (point-min) (point-max)))
    
  39. Приводит отступы во всем буфере в порядок.
    (defun cleanup-buffer ()
      "Perform a bunch of operations on the whitespace content of a buffer."
      (interactive)
      (indent-buffer)
      (untabify-buffer)
      (delete-trailing-whitespace))
    
  40. Находит файлы которые редактировали в прошлый раз с помощью ido.
    (defun recentf-ido-find-file ()
      "Find a recent file using ido."
      (interactive)
      (let ((file (ido-completing-read "Choose recent file: " recentf-list nil t)))
        (when file
          (find-file file))))
    
  41. Заменяет lambda на λ.
    (defun pretty-lambdas ()
      (font-lock-add-keywords
       nil `(("(?\\(lambda\\>\\)"
              (0 (progn (compose-region (match-beginning 1) (match-end 1)
                                        ,(make-char 'greek-iso8859-7 107))
                        nil))))))
    
  42. Заменяет предыдущее символьное выражение лиспа на результат его вычисления.
    (defun eval-and-replace ()
      "Replace the preceding sexp with its value."
      (interactive)
      (backward-kill-sexp)
      (condition-case nil
          (prin1 (eval (read (current-kill 0)))
                 (current-buffer))
        (error (message "Invalid expression")
               (insert (current-kill 0)))))
    
  43. Перекомпилирует файлы инициализации.
    (defun recompile-init ()
      "Byte-compile all your dotfiles again."
      (interactive)
      (byte-recompile-directory dotfiles-dir 0)
      ;; TODO: remove elpa-to-submit once everything's submitted.
      (byte-recompile-directory (concat dotfiles-dir "elpa-to-submit/" 0)))
    
  44. Регенерирует и загружает файл автозагрузки.
    (defun regen-autoloads (&optional force-regen)
      "Regenerate the autoload definitions file if necessary and load it."
      (interactive "P")
      (let ((autoload-dir (concat dotfiles-dir "/elpa-to-submit"))
            (generated-autoload-file autoload-file))
        (when (or force-regen
                  (not (file-exists-p autoload-file))
                  (some (lambda (f) (file-newer-than-file-p f autoload-file))
                        (directory-files autoload-dir t "\\.el$")))
          (message "Updating autoloads...")
          (let (emacs-lisp-mode-hook)
            (update-directory-autoloads autoload-dir))))
      (load autoload-file))
    
  45. Чрезвычайно полезная функция — используйте её если вам нужно отредактировать системные файлы от имени суперпользователя.
    (defun sudo-edit (&optional arg)
      (interactive "p")
      (if arg
          (find-file (concat "/sudo:root@localhost:" (ido-read-file-name "File: ")))
        (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))
    
  46. Вставляет lorem ipsum.
    (defun lorem ()
      "Insert a lorem ipsum."
      (interactive)
      (insert "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do "
              "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim"
              "ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut "
              "aliquip ex ea commodo consequat. Duis aute irure dolor in "
              "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla "
              "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in "
              "culpa qui officia deserunt mollit anim id est laborum."))
    
  47. Забуривает буфер, если если это текущий буфер, в противном случае вызывает функцию.
    (defun switch-or-start (function buffer)
      "If the buffer is current, bury it, otherwise invoke the function."
      (if (equal (buffer-name (current-buffer)) buffer)
          (bury-buffer)
        (if (get-buffer buffer)
            (switch-to-buffer buffer)
          (funcall function))))
    
  48. Вставляет текущую дату.
    (defun insert-date ()
      "Insert a time-stamp according to locale's date and time format."
      (interactive)
      (insert (format-time-string "%c" (current-time))))
    
  49. Шутка. Бот для эмуляции парного программирования.
    (defun pairing-bot ()
      "If you can't pair program with a human, use this instead."
      (interactive)
      (message (if (y-or-n-p "Do you have a test for that? ") "Good." "Bad!")))
    
  50. Патч для игнорирования пробелов аннотацией.
    (defun vc-git-annotate-command (file buf &optional rev)
      (let ((name (file-relative-name file)))
        (vc-git-command buf 0 name "blame" "-w" rev)))
    
  51. Включает режим paredit для не-лиспов.
    (defun esk-paredit-nonlisp ()
      "Turn on paredit mode for non-lisps."
      (set (make-local-variable 'paredit-space-delimiter-chars)
           (list ?\"))
      (paredit-mode 1))
    
  52. Показывает «текущую точку в буфере» в минибуфере.
    (defun message-point ()
      (interactive)
      (message "%s" (point)))
    
  53. Распахивает окно Emacs на весь экран.
    (defun toggle-fullscreen ()
      (interactive)
      ;; TODO: this only works for X. patches welcome for other OSes.
      (x-send-client-message nil 0 nil "_NET_WM_STATE" 32
                             '(2 "_NET_WM_STATE_MAXIMIZED_VERT" 0))
      (x-send-client-message nil 0 nil "_NET_WM_STATE" 32
                             '(2 "_NET_WM_STATE_MAXIMIZED_HORZ" 0)))
    
  54. Сочетания клавиш. Многие оригинальные сочетания клавиш в Emacs не отличаются особой эргономичностью и функциональностью. Эта секция имеет своей целью исправление подобных недостатков.

    Следует вспомнить, что Emacs — древнейшая ныне здравствующая и широко используемая программа, и оригинальные сочетания клавиш рассчитаны вовсе не на современные клавиатуры, а на клавиатуры почивших 20 лет назад лисп-машин (например, в мануале по Zmacs для Ti Explorer 1985 года можно найти те же самые комбинации, что используются сейчас). Следует вспомнить и принять меры, иначе незадачливый эмаксер рискует стать жертвой «синдрома эмаксового мизинца» — из-за активного использования клавиши Control, которую на современных клавиатурах жуть как неудобно нажимать несколько сотен раз в час. Есть несколько способов сохранить здоровье своих рук:

    • Самый простой: поменять Caps Lock и Control. Если вы не обладатель Happy Hacking Keyboard — меняйте, даже не думайте. Я пользуюсь GNOME — в нем поменять не проблема — ищите опцию в параметрах клавиатуры; в случае простого оконного менеджера настройте клавиатуру в файле /etc/X11/xorg.conf.
    • Купить нормальную эргономичную клавиатуру; такие выпускает Kinesis. Есть парочка хороших клавиатур у Microsoft.
  55. C-w практически во всех эмуляторах терминалов удаляет слово слева от курсора, в эмаксе же она не делает ничего хорошего. Здесь она удаляет предыдущее слово или вырезает регион, если он выделен — это очень удобно, Backspace становится практически не нужен. Эта комбинация хорошо дополняет оригинальную M-w которая копирует выделенный регион.
    (defun backward-kill-word-or-kill-region (arg)
      (interactive "p")
      (if (region-active-p)
          (kill-region (region-beginning) 
                       (region-end))
        (backward-kill-word arg)))
    
    (global-set-key (kbd "C-w") 'backward-kill-word-or-kill-region)
    
    (define-key minibuffer-local-map (kbd "C-w") 'backward-kill-word-or-kill-region)
    
    (add-hook 'ido-setup-hook 
              (lambda ()
                (define-key ido-completion-map (kbd "C-w") 'ido-delete-backward-word-updir)))
    
  56. C-q имеет смысл сделать клавишей отмены; таким образом ряд стандартных сочетаний, который в других системах расположен внизу — C-z, C-x, C-c переезжает наверх — C-q, C-w, M-w. К тому же отмена используется гораздо чаще чем quoted-insert, который назначается на C-z.

    TODO: Если кто подскажет как совместить эту клавишу с C-g я буду очень благодарен.

    (global-set-key (kbd "C-q") 'undo)
    (global-set-key (kbd "C-z") 'quoted-insert)
    
  57. C-x C-m и C-c C-m заменяют M-x:
    (global-set-key (kbd "C-x C-m") 'execute-extended-command)
    (global-set-key (kbd "C-с C-m") 'execute-extended-command)
    
  58. C-x C-k убивает буфер. Гораздо легче это делать не отпуская клавишу Control — так можно в разы быстрее убить сразу несколько буферов.
    (defun kill-current-buffer ()
      (interactive)
      (kill-buffer (current-buffer)))
    
    (global-set-key (kbd "C-x C-k") 'kill-current-buffer)
    
  59. C-s и C-r привязаны к поиску по регэкспу вперёд и назад. C-M-s и C-M-r ищут просто текст, без регекспов. Эти клавиши используются не только для поиска в буфере, но и для перехода к следующему или предыдущему элементу в минибуфере; а в режиме ido — для переключения между буферами, для поиска и открытия файла. И еще в режиме выделения региона.
    (global-set-key (kbd "C-s") 'isearch-forward-regexp)
    (global-set-key (kbd "\C-r") 'isearch-backward-regexp)
    (global-set-key (kbd "C-M-s") 'isearch-forward)
    (global-set-key (kbd "C-M-r") 'isearch-backward)
    
  60. M-Space я рекомендую использовать для переключения между языками, но это может вызвать конфликт с сочетанием «показать меню окна» в среде GNOME.
  61. S-Space и M-/ используются для умного автодополнения. Иногда достаточно просто несколько раз нажать эту комбинацию и желаемый текст чудесным образом напишется сам.
    (global-set-key (kbd "M-/") 'hippie-expand)
    (global-set-key (kbd "S-SPC") 'dabbrev-expand)
    
  62. F3, F4, F4 — начало записи макроса, конец записи макроса, вызов макроса.
  63. С-x \ выравнивает код с помощью регулярных выражений.
    (global-set-key (kbd "C-x \\") 'align-regexp)
    
  64. C-c n очищает буфер.
    (global-set-key (kbd "C-c n") 'cleanup-buffer)
    
  65. F1 включает и отключает меню. Полезно для исследования новых режимов Emacs.
    (global-set-key [f1] 'menu-bar-mode)
    
  66. C–, C-+ и C-= уменьшают и увеличивают размер шрифта в буфере.
    (define-key global-map (kbd "C-+") 'text-scale-increase)
    (define-key global-map (kbd "C-=") 'text-scale-increase)
    (define-key global-map (kbd "C--") 'text-scale-decrease)
    
  67. C-x C-i и C-x Tab позволяют перейти к определению символа в буфере.
    (global-set-key (kbd "C-x C-i") 'ido-imenu)
    
  68. Сочетания для поиска файлов.
    (global-set-key (kbd "C-x M-f") 'ido-find-file-other-window)
    (global-set-key (kbd "C-x C-M-f") 'find-file-in-project)
    (global-set-key (kbd "C-x f") 'recentf-ido-find-file)
    (global-set-key (kbd "C-x C-p") 'find-file-at-point)
    (global-set-key (kbd "C-c y") 'bury-buffer)
    (global-set-key (kbd "C-c r") 'revert-buffer)
    (global-set-key (kbd "M-`") 'file-cache-minibuffer-complete)
    (global-set-key (kbd "C-x C-b") 'ibuffer)
    
  69. Shift со стрелками используется для перехода между окнами.
    (windmove-default-keybindings)
    
  70. C-x O и C-x C-o — переход к предыдущему и к слудующему окну.
    (global-set-key (kbd "C-x O") (lambda () (interactive) (other-window -1)))
    (global-set-key (kbd "C-x C-o") (lambda () (interactive) (other-window 1)))
    
  71. C-x ^ соединяет текущую строку с предыдущей.
    (global-set-key (kbd "C-x ^") 'join-line)
    
  72. C-x m запускает eshell или переключается в уже активный.
    (global-set-key (kbd "C-x m") 'eshell)
    
  73. C-x M запускает новый eshell.
    (global-set-key (kbd "C-x M") (lambda () (interactive) (eshell t)))
    
  74. C-x M-m запускает системный шелл.
    (global-set-key (kbd "C-x M-m") 'shell)
    
  75. C-x h — указать URL и просмотреть его содержимое в новом буфере, см. view-url.
    (global-set-key (kbd "C-x h") 'view-url)
    
  76. C-h a вызывает apropos — глобальный поиск по файлам помощи.
    (global-set-key (kbd "C-h a") 'apropos)
    
  77. C-c e вычисляет выражение и заменяет его результатами вычисления.
    (global-set-key (kbd "C-c e") 'eval-and-replace)
    
  78. Управление Jabber'ом.
    (global-set-key (kbd "C-c j") (lambda () 
                                    (interactive)
                                    (switch-or-start 'jabber-connect "*-jabber-*")))
    (global-set-key (kbd "C-c J") 'jabber-send-presence)
    (global-set-key (kbd "C-c M-j") 'jabber-disconnect)
    
  79. Запуск IRC.
    (global-set-key (kbd "C-c i") (lambda () 
                                    (interactive) 
                                    (switch-or-start (lambda () (rcirc-connect "irc.freenode.net"))
                                                     "*irc.freenode.net*")))
    
  80. C-c g запускает gnus.
    (global-set-key (kbd "C-c g") (lambda () (interactive) (switch-or-start 'gnus "*Group*")))
    
  81. C-x g запускает magit.
    (global-set-key (kbd "C-x g") 'magit-status)
    
  82. Небольшой хак для git add internally в VC.
    (eval-after-load 'vc
      (define-key vc-prefix-map "i" '(lambda () (interactive)
                                       (if (not (eq 'Git (vc-backend buffer-file-name)))
                                           (vc-register)
                                         (shell-command (format "git add %s" buffer-file-name))
                                         (message "Staged changes.")))))
    
  83. C-o активирует occur во время поиска.
    (define-key isearch-mode-map (kbd "C-o")
      (lambda () (interactive)
        (let ((case-fold-search isearch-case-fold-search))
          (occur (if isearch-regexp isearch-string (regexp-quote isearch-string))))))
    
  84. C-c a запускает Org-mode agenda.
    (define-key global-map "\C-ca" 'org-agenda)
    
  85. C-c l сохраняет ссылки для Org-mode, на будущее. Смотрите секцию Handling-links в мануале Org-mode.
    (define-key global-map "\C-cl" 'org-store-link)
    
  86. C-x C-r запускает Rgrep, который необычайно полезен в многофайловых проектах. См. <elisp:(describe-function 'rgrep)>.
    (define-key global-map "\C-x\C-r" 'rgrep)
    
  87. Цветовые темы. Пакет Цветовых тем дает возможность изменять, сохранять и обмениваться цветовыми темами Emacs (color themes). Чтобы посмотреть на доступные темы и применить понравившуюся используйте команду M-x color-theme-select. Дополнительную информацию ищите на страницах Emacs Wiki.
  88. Этот код загружает цветовые темы, тем самым делая их доступными по-умолчанию.
    (add-to-list 'load-path
                 (expand-file-name "color-theme"
                                   (expand-file-name "src" dotfiles-dir)))
    (require 'color-theme)
    (eval-after-load "color-theme"
      '(progn (color-theme-initialize)))
    
  89. Когда вы выберете полюбившуюся вам тему, добавте в файл со своими настройками строку с именем вашей темы, например следующая строка
    (color-theme-charcoal-black)
    

    включит в эмаксе тему Charcoal Black при старте.

  90. Графический интерфейс. Нет скроллбара, нет тулбара, нет меню, нет диалоговых окон. Всего этого нет, ибо принесено в жертву экономии движения. Графические элементы управления требуют мышь, а чтобы дотянуться до мыши нужно оторвать руку от клавиатуры. В случае меню еще потратить уйму времени на поиск нужного пункта. От того, что эти элементы управления отключены — от пользователя не убудет — функционал отключеных элементов продублирован в интерфейсе. Во время редактирования если и используются меню, то это меню текущих режимов, а они доступны в полоске modeline. Скроллбар прекрасно заменяется стандартными клавишами для перемещения по буферу и колёсиком мыши. Тулбар же просто не нужен — выполнить любое действие проще через кейбиндинг.

    Следующий код устанавливает заголовок фрейма и отключает элементы графического интерфейса, если оный присутствует.

    (when window-system
      (setq frame-title-format '(buffer-file-name "%f" ("%b")))
      (when (fboundp 'scroll-bar-mode)
        (scroll-bar-mode nil)
        (setq default-vertical-scroll-bar nil))
      (when (fboundp 'tool-bar-mode)
        (tool-bar-mode nil))
      (tooltip-mode nil)
      (blink-cursor-mode nil))
    
  91. Установка заголовка фрейма.
    (setq frame-title-format '(buffer-file-name "%f" ("%b")))
    
  92. Отключенение элементов графического интерфейса: полосы прокрутки, панели инструментов, графических подсказок и мерцания курсора.
    (when (fboundp 'scroll-bar-mode)
      (scroll-bar-mode nil)
      (setq default-vertical-scroll-bar nil))
    (when (fboundp 'tool-bar-mode)
      (tool-bar-mode nil))
    (tooltip-mode nil)
    (blink-cursor-mode nil)
    
  93. Отключение панели инструментов в новых фреймах.
    (add-hook 'before-make-frame-hook 'turn-off-tool-bar)
    
  94. Отключение меню.
    (when (fboundp 'menu-bar-mode)
      (menu-bar-mode nil))
    
  95. Мерцание по краям буфера при выполнении неправильной команды.
    (setq visible-bell t)
    
  96. Установка разного рода дополнительных настроек оконной системы и буфера.
    (setq echo-keystrokes 0.1
          font-lock-maximum-decoration t
          inhibit-startup-message t
          transient-mark-mode t
          color-theme-is-global t
          delete-by-moving-to-trash t
          shift-select-mode nil
          mouse-yank-at-point t
          require-final-newline t
          truncate-partial-width-windows nil
          uniquify-buffer-name-style 'forward
          whitespace-style '(trailing lines space-before-tab
                                      indentation space-after-tab)
          whitespace-line-column 80
          ediff-window-setup-function 'ediff-setup-windows-plain
          oddmuse-directory (concat dotfiles-dir "oddmuse")
          xterm-mouse-mode t
          save-place-file (concat dotfiles-dir "places"))
    
    (mouse-wheel-mode t)
    
    (add-to-list 'safe-local-variable-values '(lexical-binding . t))
    (add-to-list 'safe-local-variable-values '(whitespace-line-column . 80))
    
    (set-default 'indent-tabs-mode nil)
    (set-default 'indicate-empty-lines t)
    (set-default 'imenu-auto-rescan t)
    
    (add-hook 'text-mode-hook 'turn-on-auto-fill)
    (add-hook 'text-mode-hook 'turn-on-flyspell)
    
    (defalias 'yes-or-no-p 'y-or-n-p)
    (random t) ;; Seed the random-number generator
    
  97. Работаем с системным буфером обмена в Emacs.
    (setq x-select-enable-clipboard t)
    
  98. UTF-8 используется повсеместно.
    (set-terminal-coding-system 'utf-8)
    (set-keyboard-coding-system 'utf-8)
    (prefer-coding-system 'utf-8)
    (ansi-color-for-comint-mode-on)
    
  99. Хиппи-дополнение порою черезчур хиппи.
    (delete 'try-expand-line hippie-expand-try-functions-list)
    (delete 'try-expand-list hippie-expand-try-functions-list)
    
  100. Браузер в котором открываются ссылки. Используйте в своих настройках одну из следующих строчек кода.
    (setq browse-url-browser-function 'browse-url-firefox)
    (setq browse-url-browser-function 'browse-default-macosx-browser)
    (setq browse-url-browser-function 'browse-default-windows-browser)
    (setq browse-url-browser-function 'browse-default-kde)
    (setq browse-url-browser-function 'browse-default-epiphany)
    (setq browse-url-browser-function 'browse-default-w3m)
    (setq browse-url-browser-function 'browse-url-generic
          browse-url-generic-program "~/src/conkeror/conkeror")
    
  101. Компресированные файлы просто открываются.
    (auto-compression-mode t)
    
  102. Включить подсветку синтаксиса для старых эмаксов.
    (global-font-lock-mode t)
    
  103. Хранить список ранее посещенных файлов.
    (recentf-mode 1)
    
  104. Подсвечивать совпадающие скобочки.
    (show-paren-mode 1)
    
  105. Не мешать директории с файлами.
    (setq backup-directory-alist `(("." . ,(expand-file-name
                                            (concat dotfiles-dir "backups")))))
    
  106. Ассоциировать режимы с расширениями файлов.
    (add-to-list 'auto-mode-alist '("COMMIT_EDITMSG$" . diff-mode))
    (add-to-list 'auto-mode-alist '("\\.css$" . css-mode))
    (require 'yaml-mode)
    (add-to-list 'auto-mode-alist '("\\.ya?ml$" . yaml-mode))
    (add-to-list 'auto-mode-alist '("\\.rb$" . ruby-mode))
    (add-to-list 'auto-mode-alist '("Rakefile$" . ruby-mode))
    (add-to-list 'auto-mode-alist '("\\.js\\(on\\)?$" . js2-mode))
    (add-to-list 'auto-mode-alist '("\\.xml$" . nxml-mode))
    
  107. Grep игнорирует файлы при поиске.
    (eval-after-load 'grep
      '(when (boundp 'grep-find-ignored-files)
        (add-to-list 'grep-find-ignored-files "target")
        (add-to-list 'grep-find-ignored-files "*.class")))
    
  108. Обобщенные диффы (unified diffs) по-умолчанию.
    (setq diff-switches "-u")
    
  109. Немного косметики.
    (set-face-background 'vertical-border "white")
    (set-face-foreground 'vertical-border "white")
    
    (eval-after-load 'diff-mode
      '(progn
         (set-face-foreground 'diff-added "green4")
         (set-face-foreground 'diff-removed "red3")))
    
    (eval-after-load 'magit
      '(progn
         (set-face-foreground 'magit-diff-add "green3")
         (set-face-foreground 'magit-diff-del "red3")))
    
    (eval-after-load 'mumamo
      '(eval-after-load 'zenburn
         '(ignore-errors (set-face-background
                          'mumamo-background-chunk-submode "gray22"))))
    
  110. Обходим защиту от спама в Emacs Wiki.
    (add-hook 'oddmuse-mode-hook
             (lambda ()
               (unless (string-match "question" oddmuse-post)
                 (setq oddmuse-post (concat "uihnscuskc=1;" oddmuse-post)))))
    
  111. Ido. Интеллектуальное дополнение.
    (when (> emacs-major-version 21)
      (ido-mode t)
      (setq ido-enable-prefix nil
            ido-enable-flex-matching t
            ido-create-new-buffer 'always
            ido-use-filename-at-point t
            ido-max-prospects 10))
    
  112. Flyspell. Большая часть кода перекочевала сюда из Emacs Wiki. Этот код не включается в конечный файл.

    Устанавливаем путь к aspell, возможно, его нет в $PATH.

    (setq exec-path (append exec-path '("/opt/local/bin")))
    

    Выбираем программу для проверки орфографии.

    (setq ispell-program-name "aspell"
          ispell-dictionary "english"
          ispell-dictionary-alist
          (let ((default '("[A-Za-z]" "[^A-Za-z]" "[']" nil
                           ("-B" "-d" "english" "--dict-dir"
                            "/Library/Application Support/cocoAspell/aspell6-en-6.0-0")
                           nil iso-8859-1)))
            `((nil ,@default)
              ("english" ,@default))))
    
  113. Nxhtml. Nxhtml это большой пакет утилит для веб-разработки и для интеграции нескольких главных режимов Emacs в одном буфере.

    В этой версии Starter Kit Nxhtml не установлен, информацию по установке ищите на EmacsWiki-Nxhtml.

    (setq mumamo-chunk-coloring 'submode-colored
          nxhtml-skip-welcome t
          indent-region-mode t
          rng-nxml-auto-validate-flag nil)
    
  114. Регистры дают вам возможность быстро прыгнуть к файлу или иной локации. Используйте C-x r j с последующей буквой регистра (i для файла init.el, s для этого файла) чтобы прыгнуть к нему.

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

    (dolist (r `((?i (file . ,(concat dotfiles-dir "init.el")))
                 (?s (file . ,(concat dotfiles-dir "starter-kit.org")))))
      (set-register (car r) (cadr r)))
    
  115. Org-mode. Org-Mode используется для хранения заметок, ведения списков дел, планирования проектов, публикации в блог и вообще для быстрой и удобной работы с чистым текстом. Org-mode можно использовать для работы в качестве системы GTD или средства для литературного программирования.

    Чтобы узнать больше об Org-mode загляните на worg, большую вики по Org-mode сделаную с помощью самого Org-mode и git.

  116. Загружаем библиотеку Babel; она содержит много полезных функций которые могут быть использованы в блоках кода в любом файле. Информацию о функциях вы найдете в самом файле библиотеки library-of-babel.org, сведения по использованию ищите на worg:library-of-babel.
    (org-babel-lob-ingest
     (expand-file-name
      "library-of-babel.org"
      (expand-file-name
       "babel"
       (expand-file-name
        "contrib"
        (expand-file-name
         "org"
         (expand-file-name "src" dotfiles-dir))))))
    
  117. Убедимся, что последняя версия мануала Org-mode доступна по команде info (она привязана к сочетанию C-h i). Для этого сделаем директорию doc/, которая находится в пакете Org-mode, первым элементом списка Info-directory-list.
    (unless (boundp 'Info-directory-list)
      (setq Info-directory-list Info-default-directory-list))
    (setq Info-directory-list
          (cons (expand-file-name
                 "doc"
                 (expand-file-name
                  "org"
                  (expand-file-name "src" dotfiles-dir)))
                Info-directory-list))
    
  118. Документация по Starter Kit. Этот код определяет проект starter-kit-project, он используется для публикации html-документации по Starter Kit.
    (unless (boundp 'org-publish-project-alist)
      (setq org-publish-project-alist nil))
    (let ((this-dir (file-name-directory (or load-file-name buffer-file-name))))
      (add-to-list 'org-publish-project-alist
                   `("starter-kit-documentation"
                     :base-directory ,this-dir
                     :base-extension "org"
                     :style "<link rel=\"stylesheet\" href=\"emacs.css\" type=\"text/css\"/>"
                     :publishing-directory ,this-dir
                     :index-filename "starter-kit.org"
                     :auto-postamble nil)))
    
  119. Eshell это хорошая командная оболочка. Дополнительную информацию ищите в вики.
    (setq eshell-cmpl-cycle-completions nil
          eshell-save-history-on-exit t
          eshell-cmpl-dir-ignore "\\`\\(\\.\\.?\\|CVS\\|\\.svn\\|\\.git\\)/\\'")
    
    (eval-after-load 'esh-opt
      '(progn
         (require 'em-prompt)
         (require 'em-term)
         (require 'em-cmpl)
         (setenv "PAGER" "cat")
         (set-face-attribute 'eshell-prompt nil :foreground "turquoise1")
         (when (< emacs-major-version 23)
           (add-hook 'eshell-mode-hook ;; for some reason this needs to be a hook
                     '(lambda () (define-key eshell-mode-map "\C-a" 'eshell-bol)))
           (add-to-list 'eshell-output-filter-functions 'eshell-handle-ansi-color))
    
         ;; TODO: submit these via M-x report-emacs-bug
         (add-to-list 'eshell-visual-commands "ssh")
         (add-to-list 'eshell-visual-commands "tail")
         (add-to-list 'eshell-command-completions-alist
                      '("gunzip" "gz\\'"))
         (add-to-list 'eshell-command-completions-alist
                      '("tar" "\\(\\.tar|\\.tgz\\|\\.tar\\.gz\\)\\'"))))
    
    (defun eshell/cds ()
      "Change directory to the project's root."
      (eshell/cd (locate-dominating-file default-directory "src")))
    
    (defun eshell/find (dir &rest opts)
      (find-dired dir (mapconcat 'identity opts " ")))
    
  120. В директории eshell хранятся определения alias и история. Она служит для тех же целей, что и файл .bashrc (если вы знакомы с bash). Ниже устанавливаем значение переменной eshell-directory-name так что она указывает на директорию ~/.emacs.d/eshell, в которой уже есть файл alias с парочкой полезных алиасов.
    (setq eshell-directory-name (expand-file-name "./" (expand-file-name "eshell" dotfiles-dir)))
    
  121. Lisp. Поддержим диалекты Emacs Lisp, Scheme, Common Lisp и Clojure хорошими настройками. Для начала несколько комбинаций клавиш для всех диалектов.

    Tab и C-\ автодополняют символы в лисп-программе.

    (define-key read-expression-map (kbd "TAB") 'lisp-complete-symbol)
    (define-key lisp-mode-shared-map (kbd "C-\\") 'lisp-complete-symbol)
    

    Enter работает как раньше и дополнительно автоматически расставляет отступы.

    (define-key lisp-mode-shared-map (kbd "RET") 'reindent-then-newline-and-indent)
    

    C-c v вычисляет весь буфер.

    (define-key lisp-mode-shared-map (kbd "C-c v") 'eval-buffer)
    

    C-c l вставляет слово lambda.

    (define-key lisp-mode-shared-map (kbd "C-c l") "lambda")
    
  122. Тусклые скобочки.
    (defface esk-paren-face
       '((((class color) (background dark))
          (:foreground "grey50"))
         (((class color) (background light))
          (:foreground "grey55")))
       "Face used to dim parentheses."
       :group 'starter-kit-faces)
    
  123. Paredit это режим структурного редактирования лиспокода. Проще говоря, он расставляет, переставляет и удаляет скобочки с учётом семантики кода. Возможно, сразу его освоить не получиться, потому как этот режим выполнен в лучших традициях эмакса с добрым десятком зубодробительных комбинаций, но после длительного использования и привыкания без него будет уже непросто.

    Рекомендую освоить базовые комбинации клавиш — они доступны в справке, дополнительно смотрите в вики.

    (defun turn-on-paredit ()
      (paredit-mode +1))
    
    (eval-after-load 'paredit
      ;; need a binding that works in the terminal
      '(define-key paredit-mode-map (kbd "M-)") 'paredit-forward-slurp-sexp))
    
  124. Emacs Lisp. Включаем режим показа документации elisp-функций в минибуфере, запускаем ловушку coding-hook для включения удобcтв при кодировании, включаем режим paredit.
    (add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
    (add-hook 'emacs-lisp-mode-hook 'esk-remove-elc-on-save)
    
  125. Удаляет откомпилированный .elc-файл при сохранении оригинального .el-файла.
    (defun esk-remove-elc-on-save ()
      "If you're saving an elisp file, likely the .elc is no longer valid."
      (make-local-variable 'after-save-hook)
      (add-hook 'after-save-hook
                (lambda ()
                  (if (file-exists-p (concat buffer-file-name "c"))
                      (delete-file (concat buffer-file-name "c"))))))
    
  126. M-. находит определение elisp-функции.
    (define-key emacs-lisp-mode-map (kbd "M-.") 'find-function-at-point)
    
  127. Clojure.
    (eval-after-load 'find-file-in-project
      '(add-to-list 'ffip-patterns "*.clj"))
    
  128. Команда clojure-project больше не используется.
    (defun clojure-project (path)
      (interactive)
      (message "Deprecated in favour of M-x swank-clojure-project. Install swank-clojure from ELPA."))
    
  129. В исходниках Clojure fn заменяется на ƒ для красоты и экономии места.
    (eval-after-load 'clojure-mode
      '(font-lock-add-keywords
        'clojure-mode `(("(\\(fn\\>\\)"
                         (0 (progn (compose-region (match-beginning 1)
                                                   (match-end 1) "ƒ")
                                   nil))))))
    
  130. Во всех режимах лиспа включаются тусклые скобочки, режим paredit и общие улучшения для кодирования.
    (dolist (x '(scheme emacs-lisp lisp clojure))
      (when window-system
        (font-lock-add-keywords
         (intern (concat (symbol-name x) "-mode"))
         '(("(\\|)" . 'esk-paren-face))))
      (add-hook
       (intern (concat (symbol-name x) "-mode-hook")) 'turn-on-paredit)
      (add-hook
       (intern (concat (symbol-name x) "-mode-hook")) 'run-coding-hook))
    
  131. Haskell. Красивые λ в Haskell-коде.
    (defun pretty-lambdas-haskell ()
      (font-lock-add-keywords
       nil `((,(concat "(?\\(" (regexp-quote "\\") "\\)")
              (0 (progn (compose-region (match-beginning 1) (match-end 1)
                                        ,(make-char 'greek-iso8859-7 107))
                        nil))))))
    
  132. Все эти прелести включаются при включении режима Haskell с помощью ловушки haskell-mode-hook.
    (add-hook 'haskell-mode-hook 'run-coding-hook)
    (add-hook 'haskell-mode-hook 'pretty-lambdas-haskell)
    
  133. Ruby. Ниже идет код в поддержку Ruby — динамического языка с открытым исходным кодом.
    (eval-after-load 'ruby-mode
      '(progn
         ;; work around possible elpa bug
         (ignore-errors (require 'ruby-compilation))
         (setq ruby-use-encoding-map nil)
         (add-hook 'ruby-mode-hook 'inf-ruby-keys)
         (define-key ruby-mode-map (kbd "RET") 'reindent-then-newline-and-indent)
         (define-key ruby-mode-map (kbd "C-c l") "lambda")))
    
    (global-set-key (kbd "C-h r") 'ri)
    
  134. И gamespec и rake-файлы — это всё Ruby, включаем для них соответствующий режим.
    (add-to-list 'auto-mode-alist '("\\.rake$" . ruby-mode))
    (add-to-list 'auto-mode-alist '("\\.gemspec$" . ruby-mode))
    (add-to-list 'auto-mode-alist '("\\.ru$" . ruby-mode))
    (add-to-list 'auto-mode-alist '("Rakefile$" . ruby-mode))
    (add-to-list 'auto-mode-alist '("Gemfile$" . ruby-mode))
    (add-to-list 'auto-mode-alist '("Capfile$" . ruby-mode))
    (add-to-list 'auto-mode-alist '("Vagrantfile$" . ruby-mode))
    
  135. Мы не хотим редактировать рубиновый байткод.
    (add-to-list 'completion-ignored-extensions ".rbc")
    
  136. Rake.
    (defun pcomplete/rake ()
      "Completion rules for the `ssh' command."
      (pcomplete-here (pcmpl-rake-tasks)))
    
    (defun pcmpl-rake-tasks ()
       "Return a list of all the rake tasks defined in the current
        projects.  I know this is a hack to put all the logic in the
        exec-to-string command, but it works and seems fast"
       (delq nil (mapcar '(lambda(line)
                       (if (string-match "rake \\([^ ]+\\)" line) (match-string 1 line)))
                    (split-string (shell-command-to-string "rake -T") "[\n]"))))
    
    (defun rake (task)
      (interactive (list (completing-read "Rake (default: default): "
                                          (pcmpl-rake-tasks))))
      (shell-command-to-string (concat "rake " (if (= 0 (length task)) "default" task))))
    
  137. Очищаем буфер с результатами компиляции после каждого тестового запуска.
    (eval-after-load 'ruby-compilation
      '(progn
         (defadvice ruby-do-run-w/compilation (before kill-buffer (name cmdlist))
           (let ((comp-buffer-name (format "*%s*" name)))
             (when (get-buffer comp-buffer-name)
               (with-current-buffer comp-buffer-name
                 (delete-region (point-min) (point-max))))))
         (ad-activate 'ruby-do-run-w/compilation)))
    
  138. Ловушки.
    (add-hook 'ruby-mode-hook 'run-coding-hook)
    
  139. Flymake. Проверка синтаксиса в режиме Ruby.
    (defun flymake-ruby-init ()
      (let* ((temp-file (flymake-init-create-temp-buffer-copy
                         'flymake-create-temp-inplace))
             (local-file (file-relative-name
                          temp-file
             (file-name-directory buffer-file-name))))
        ;; Invoke ruby with '-c' to get syntax checking
        (list "ruby" (list "-c" local-file))))
    
    (defun flymake-ruby-enable ()
      (when (and buffer-file-name
                 (file-writable-p
                  (file-name-directory buffer-file-name))
                 (file-writable-p buffer-file-name)
                 (if (fboundp 'tramp-list-remote-buffers)
                     (not (subsetp
                           (list (current-buffer))
                           (tramp-list-remote-buffers)))
                   t))
        (local-set-key (kbd "C-c d")
                       'flymake-display-err-menu-for-current-line)
        (flymake-mode t)))
    
    (eval-after-load 'ruby-mode
      '(progn
         (require 'flymake)
         (push '(".+\\.rb$" flymake-ruby-init) flymake-allowed-file-name-masks)
         (push '("Rakefile$" flymake-ruby-init) flymake-allowed-file-name-masks)
         (push '("^\\(.*\\):\\([0-9]+\\): \\(.*\\)$" 1 2 nil 3)
               flymake-err-line-patterns)
         (add-hook 'ruby-mode-hook 'flymake-ruby-enable)))
    
  140. Rinari — минорный режим для Ruby On Rails. Ищите на rinari.rubyforge дополнительную информацию о rinari.
    (setq rinari-major-modes
          (list 'mumamo-after-change-major-mode-hook 'dired-mode-hook 'ruby-mode-hook
                'css-mode-hook 'yaml-mode-hook 'javascript-mode-hook))
    
  141. JavaScript.
    (autoload 'espresso-mode "espresso" "Start espresso-mode" t)
    (add-to-list 'auto-mode-alist '("\\.js$" . espresso-mode))
    (add-to-list 'auto-mode-alist '("\\.json$" . espresso-mode))
    (add-hook 'espresso-mode-hook 'moz-minor-mode)
    (add-hook 'espresso-mode-hook 'esk-paredit-nonlisp)
    (add-hook 'espresso-mode-hook 'run-coding-hook)
    (setq espresso-indent-level 2)
    
    ;; If you prefer js2-mode, use this instead:
    ;; (add-to-list 'auto-mode-alist '("\\.js$" . espresso-mode))
    
    (eval-after-load 'espresso
      '(progn (define-key espresso-mode-map "{" 'paredit-open-curly)
              (define-key espresso-mode-map "}" 'paredit-close-curly-and-newline)
              ;; fixes problem with pretty function font-lock
              (define-key espresso-mode-map (kbd ",") 'self-insert-command)
              (font-lock-add-keywords
               'espresso-mode `(("\\(function *\\)("
                                 (0 (progn (compose-region (match-beginning 1)
                                                           (match-end 1) "ƒ")
                                           nil)))))))
    
  142. Perl.
    (eval-after-load 'cperl-mode
      '(progn
         (define-key cperl-mode-map (kbd "RET") 'reindent-then-newline-and-indent)))
    
    (global-set-key (kbd "C-h P") 'perldoc)
    
    (add-to-list 'auto-mode-alist '("\\.p[lm]$" . cperl-mode))
    (add-to-list 'auto-mode-alist '("\\.pod$" . pod-mode))
    (add-to-list 'auto-mode-alist '("\\.tt$" . tt-mode))
    
  143. Регенерация автозагрузочных файлов.
    (regen-autoloads)
    
  144. По-умолчанию загружается пользовательский файл custom.el, его нет в комплекте Kit'a.
    (load custom-file 'noerror)
    
  145. Загрузка настроек конкретного пользователя и машины. После того как мы загрузили все настройки Starter Kit, мы можем загрузить настройки конкретного пользователя и конкретной машины.
    (if (file-exists-p elisp-source-dir)
      (let ((default-directory elisp-source-dir))
        (normal-top-level-add-subdirs-to-load-path)))
    (if (file-exists-p system-specific-config) (load system-specific-config))
    (if (file-exists-p system-specific-literate-config)
        (org-babel-load-file system-specific-literate-config))
    (if (file-exists-p user-specific-config) (load user-specific-config))
    (if (file-exists-p user-specific-literate-config)
        (org-babel-load-file user-specific-literate-config))
    (when (file-exists-p user-specific-dir)
      (let ((default-directory user-specific-dir))
        (mapc #'load (directory-files user-specific-dir nil ".*el$"))
        (mapc #'org-babel-load-file (directory-files user-specific-dir nil ".*org$"))))
    

Не прошло и месяца как я узнал о литературном программировании, но сама концепция произвела на меня неизгладимое впечатление, заставила переосмыслить некоторые вещи и поубавить пыла на пару с максимализмом в некоторых суждениях. О технической стороне литературного программирования (я называю его литературным, а не грамотным; мне кажется это ближе по смыслу оригинальному названию) я видел пару замечательных статей в рунете, но я хочу рассказать о эмоциональной его стороне, надеюсь это придаст вам мотивации копнуть глубже. Эмоциональные слова прозвучали бы из моих уст несколько… по-фанбойски, так что оставлю их на потом, а сейчас предложу вам прочесть несколько цитат создателя методологии, Дональда Кнута.

Перевод я делал с посильной подмогой Google Translate, и это мой первый опубликованый перевод — попрошу пинать только по делу.

Фрагменты из статьи «Literate Programming», 1983 г:

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

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

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

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

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

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

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

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

Это к слову о том почему многие из инструментов для ЛП имеют в своем названии слово web («сеть»).

Смею предположить, что такие достижения в документации возможны из-за опыта, который я получил в течение последних нескольких лет во время интенсивной разработки программного обеспечения (здесь Кнут говорит о разработке TeX и METAFONT). Воспользовавшись несколькими идеями, которые существуют уже длительное время, и систематически примененив их слегка по-новому, я наткнулся на метод создания программ, который чрезвычайно меня взволновал.

В самом деле, мой энтузиазм настолько велик, что я должен предупредить читателя воспринять многое из того, что я скажу, как бред фанатика, который думает, что он только что обрел просветление. Программирование — это очень личная деятельность, поэтому я не могу быть уверен, что то, что работало со мной, будет работать со всеми. Однако этот новый подход основательно повлиял на мой собственный стиль, и мое волнение не утихает и поныне — уже более двух лет. Мне так нравится эта новая методология, что трудно удержаться от того, чтобы не вернуться к каждой когда-либо написаной мной программе и не переделать её в «литературную».

Я не в силах противостоять желанию поработать над задачами по программированию, которые я обычно поручаю помощниками студенческих исследований; и почему? Потому что мне кажется, что наконец я могу писать программы так, как они и должны быть написаны. Мои программы не только объяснены лучше чем когда-либо прежде; они лучше как программы, потому что новая методология заставляет меня делать свою работу лучше.

Небольшой фрагмент из интервью с Дональдом Кнутом, 20 лет спустя:

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

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

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

Вероятно, Джон Бентли расставил точки над i когда его спросили «почему литературное программирование не покорило мир?» Он сказал, что только небольшой процент из всех людей хорош в программировании, и небольшой процент хорош в писательстве; и вы спрашиваете меня чтобы все люди были сразу в двух этих категориях.

Что до меня, литературное программирование — это самая важная вещь, которая вышла из проекта TeX. Оно не только позволило мне быстрее и надежнее, чем когда-либо, писать и поддерживать программы, оно было одним из самых больших источников радости с 1980-ых, и временами без него невозможно было обойтись. Некоторые из моих главных программ, таких как мета-симулятор MMIX, не могли бы быть написаны с помощью любой другой методологии о которой я когда-либо слышал. Они были просто черезчур сложны для моего ограниченного мозга; без литературного программирования подобное предприятие просто провалилось бы.

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

Из позитивного: я был приятно удивлен открыв, что правила CWEB (системы литературного программирования для C) уже являются стандартом в предустановленных программах, таких как Makefile, в современном Linux.

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

Последний год я очень пристально следил за жизнью коммунити лисперов-кложуристов и поглощал всю доступную в сети информацию по этой теме: планету блогов, твиттер, 3 вышедшие на сегодня книги. После достаточно плотного «погружения» в эту тему у меня появилось очень смутное подозрение насчет подхода к изучению языка Clojure и сопутствующей ему инфраструктуры. Потом я узнал о ЛП и смутное подозрение прояснилось. Судите сами. Сейчас в мире есть три книги о Clojure и их содержание очень сильно пересекается между собой, примерно его можно описать как 100500 слабо связаных друг с другом рецептов по размещению граблей предоставляемых языком данной конкретной версии. Прочтение всех трех книг не сделает из вас профессионала. Вы можете прочитать и тысячу подобных книг — мастером вам не стать. Все 3 книги упираются в непробиваемую стену; вы знаете что за стеной лежит сокровище, простое и конкретное; но вам никак не добраться до него через тернии разрозненных и неясных очертаний. Подобная техническая литература — это эссенция клипового мышления: как будто мне скармливают крепко завареную кашу из питательных и чертовски полезных кусочков, но я никогда не смогу ею насытиться, потому что меня кормят вторичностью, оставляя суть недоступной.

Clojure, как и все лиспы по своей сути являются чрезвычайно простыми конструкциями. Кто-то сказал «исходник — лучшая документация»; для семейства этих языков это чистая правда. Фактически та часть языка, которая непосредственно используется пользователем, описана на самой Clojure в одном файле core.clj размером около 5 тысяч строк. Это прозрачный как слеза младенца код с комментариями из которых генерируется довольно простая, но качественная документация. Прочтение этого файла за чашкой чая будет началом того самого путешествия за стену клиповости к самой сути используемой технологии. Но дальнейшее продвижение оказывается куда более сложным — следующим на пути будут Java-исходники, в которых описан компилятор, транзакционная память, ссылки, агенты и персистентные структуры данных. И ни одного комментария — там сам черт ногу сломит. И ведь вещь-то совсем не сложная, но тех кто действительно досконально или хотя бы примерно знает устройство всей этой технологии до самой её основы — на порядки меньше тех кто изучил её до этой труднопроходимой стены.

Другое дело TeX — славное творение великого мастера. Если хочешь стать настоящим техником — читаешь от корки до корки стандарт-руководство описаное в «TeXbook». Решил стать прожженым спецом — опять же изучаешь от корки до корки книгу «TeX The Program», в которой все 20000 строк программы TeX (TeX написан на Паскале, черт меня дери!) описаны простым понятным человеческим языком — это литературное произведение, подробное описание программы с высоты птичего полета вплоть до мельчайших деталей. Всё. Две книги. Альфа и Омега. Технология описана снизу до верху и со всех сторон — никто не скажет больше. Чтобы в полной мере прочувствовать методологию, я очень рекомендую обратить внимание на три большие литературные программы Кнута: «TeX The Program», «METAFONT The Program» и «MMIXware».

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

В процессе написания на Clojure своего маленького, но полезного софта, а также благодаря титаническим усилиям авторов журнала «Практика функционального программирования» (особенно, 3-го выпуска) на поприще лечения функционально-шоковой терапией (шоковой по причине хаскеля, вестимо) оопнуто-императивного мозга ко мне пришло понимание особой полезности и удобства применения функций-комбинаторов других функций.

Пускай это звучит заумно, но объяснение на пальцах полностью раскрывает суть этой простой вещи. Представим, есть у нас две функции-предиката, которые возвращают только истину или ложь — предикаты «жив?», alive? и «мёртв?», dead?. Нам нужно из этих предикатов составить третий предикат, проверяющий сложное условие, например — «не-жив-не-мёртв?», nor-dead-nor-alive? (не факт, что этот предикат всегда будет возвращать false, учитывая динамичность Clojure и особенности применения, но тем не менее). Берем эти предикаты и конструируем эту функцию так, как мы обычно это делаем

(defn nor-dead-nor-alive? [x] 
  (and (not dead? x) (not alive? x)))

Взор немного смущает частое употребление икса, и это при том, что это единственный аргумент для всех функций. Для избавления от этой напасти воспользуемся комбинаторами функций. Комбинаторы по сути своей — это функции высшего порядка, они получают функций, делают с ними нечто и возвращают функцию. Они позволят определить функцию «не-жив-не-мёртв?» не как функцию от x с явными вызовами функций «жив?» и «мёртв?» с аргументом x, а как обычную переменную, значение которой — функция, в которую комбинаторы скомбинировали функции «жив?» и «мёртв?»

(def nor-dead-nor-alive? (fn/and (fn/not dead?) (fn/not alive?)))

Может это и не лучший пример для комбинаторов, в плане небольшого шума, который добавляют префиксы fn/, да и проблем с документацией добавит, но он достаточно прост, чтобы показать саму концепцию. В большинстве случаев использование комбинаторов позволит избавиться от приличного объема копипасты, что приведет к более понятному коду с ясно выраженной сутью вычисления.

Практичный пример.

Прошлый пример с функциями «жив?» и «мёртв?» был довольно игрушечным, на этот раз приведу пример из маленькой полезной программы, которую я написал для себя. Это качалка с местного файлообменника, внутри которой пачка асинхронных агентов делает своё нелёгкое сетевое дело. Агенты организованы в окружение, они могут быть живыми — предикат alive?, иметь некий таг — ацессор tag, и принадлежать к некоторому типу агентов — ацессор type. Мне нужна была функция для выбора «следующего живого агента того же типа, но без тага». Вот реализация с комбинатором:

(defn next-alive-untagged-after [ag]
  (next-after-when (fn/and alive? 
                           (fn/not tag)
                           (partial same type ag))
                   (self ag) (env ag)))

и без комбинатора

(defn next-alive-untagged-after [ag]
  (next-after-when (fn [x] (and (alive? x) 
                                (not (tag x)) 
                                (same type ag x)))
                   (self ag) (env ag)))

Очевидно, в версии без комбинаторов мусора больше.

Реализация комбинаторов.

Комбинаторов много хороших и разных, но в статье и большей части своего кода я использовал только три наиболее полезных «логических» комбинатора — fn/and, fn/or и fn/not. Оригинальный код этих комбинаторов я вычитал в замечательной книге Пола Грэма «On Lisp» (там еще много подобных плюшек). Реализация их на Clojure чуть более функциональна и проще для понимания благодаря деструктуризации аргументов функций и тому, что это Lisp-1 (не нужен funcall).

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

[fn "1.0.0"]

Определение пространства имен заслуживает особых комментариев.

(ns fn
  (:refer-clojure :exclude [not and or]))

Этот код определяет пространство имён fn. В Clojure это имя зарезервировано под фундаментальный макрос, конструктор функций, но это не создаст проблем при использовании. Другое дело, что без префикса fn/ комбинаторами невозможно будет воспользоваться в обычном коде, однако, пораскинув мыслью, всё становится на свои места — хорошие имена разлетаются как горячие пирожки, на всех не напасёшься, префикс же подчёркивает функциональный аспект при употреблении этих конструкций. :refer-clojure :exclude исключает импорт имён and, or и not из пространства имён ядра языка clojure.core. Задействовать эту маленькую библиотеку в своей программе можно лишь с помощью require; use работать не будет из-за перекрытия имен.

Заранее определим имена функций в нашем модуле

(declare not and or)

Устройство этих функций весьма хитрó — за простой идеей и тремя строками кода скрывается целый клубок накрепко связаных друг с другом рекурсий. Подобные вещи не очень хорошо могут сказываться на производительности, поэтому имеет смысл реализовать эти комбинаторы как макросы, чего я делать не буду :)

Комбинатор fn/not это старый добрый complement, для удобстава возвращающий «пересечение» комплементарных функций, если его вызвать с несколькими аргументами

(fn/not f) => (complement f)
(fn/not f g h ...) => (and (fn/not f) (fn/not g) (fn/not h) ...)
(defn not
  ([f] (complement f))
  ([f & fs] (and (not f)
                 (apply and (map not fs)))))

Комбинатор fn/and возвращает «пересечение» функций — функцию, которая возвращает что-либо (non-nil), только если все пересекаемые функции возвращают что-либо:

(and f g h ...) => (fn [xs] (and (f xs) (g xs) (h xs) ...))
(defn and
  ([f] f)
  ([f & fs] (let [chain (apply and fs)]
              (fn [& xs] (clojure.core/and (apply f xs)
                                           (apply chain xs))))))

Комбинатор fn/or возвращает «объединение» функций — функцию, которая возвращает что-либо, когда хотя бы одна функция из объединяемых возвращает что-либо:

(or f g h ...) => (fn [xs] (or (f xs) (g xs) (h xs) ...))
(defn or
  ([f] f)
  ([f & fs] (let [chain (apply or fs)]
               (fn [& xs] (clojure.core/or (apply f xs)
                                           (apply chain xs))))))

Нестабильность современного десктопного линукса повергает меня в пучины отчаяния. Это я о Ubuntu и её производных. Конечно, глупо винить в этом разработчиков, при том что я сам не приложил усилий к улучшению ситуации. В некотором роде сегоднешняя нестабильность — плата за удобство для конечно пользователя. Ubuntu настолько проста и удобна, что я уже давно использую её как основную систему на нетбуке и на его стационарном собрате (ASRock ION по сути тот же нетбук, только в 2 раза больше и мощнее).

В моей Ubuntu 10.04 LTS на ION за несколько месяцев активного использования поотваливались почти все апплеты с панели GNOME, а у Jolicloud Linux (фактически, Ubuntu 9.04) спустя месяц использования отвалился заглавный экран, он же Google Chrome в режиме «окна-рабочего стола». Но это меркнет на фоне того, что у нового нетбука после совсем «мягкого» удара полетел жесткий диск — всего через 3 месяца после покупки.

Черт возьми, современное железо и домашний софт хрупче стекла: разваливаются на глазах от дуновения ветерка. Не то что старое доброе железо 2000-ых и стабильный софт из дебиана. На нижней полке шкафа, в черном чемоданчике, лежит жесткий диск Seagate Medalist на 3 гигабайта, и последний раз когда я его включал (а надо сказать, я пользовался им очень долго) он работал идеально. В том же чемоданчике лежат еще 4 диска, более современные, но не выдержавшие испытание временем.

В моих руках нынешнее железо не выживает долее полугода, и я принимаю это как факт. В былые времена добро, хранимое на жестких дисках было «священной коровой». Многогигабайтная коллекция музыки, фильмов, и анимэ тщательно собиралась, выменивалась и именовалась, а потеря её была настоящей трагедией. Сейчас же мой ежемесячный трафик в среднем — около 50 гигабайт (и это на 250 килобитах, да :). Объем диска ноутбука — 250 гигабайт. Если я буду сохранять весь скаченый из Сети мусор, то место кончится за пол года. Как раз к очередной запланированой смерти жесткого диска или выходу новой версии Ubuntu и разрушению старой.

Пришла облачная эпоха и все вернулось на круги своя — персональный компьютер снова превратился в терминал-приставку, а «мейнфрейм» стал всей Сетью. Правда «кэш-памяти» (HDD) пока довольно много, но в моих тараканях это оправдано малым каналом.

По-настоящему ценной информации на моем компьютере нет. Есть немного такой, потерять которую было бы неприятно — её совсем немного, буквально с десяток мегабайт исходников и этот блог. Но и потерять её не так то просто. Она продублирована на моих компьютерах и на двух репозиториях в Сети: Github и Dropbox (да, я храню git-репозитории в svn-репозитории :). Мусор и прочие медиа размазаны по торрентам на тысячах компьютеров — здесь не о чем волноваться.

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

Для быстрого восстановления я использую Dropbox и маленький скрипт — он позволяет привести компьютер в порядок буквально в «один клик». Для этого достаточно старого доброго Makefile, код которого приведен ниже.

Репозиторий Dropbox находится в директории ~/Dropbox. В ~/Dropbox/Dotfiles хранятся все основные настройки моих компьютеров.

HOME=$(shell echo $$HOME)
DOTFILES=$(HOME)/Dropbox/Dotfiles

На самом верхнем уровне директории именованы по машинам к которым относятся настройки.

UNIVERSAL=universal
NODE=$(shell uname --nodename)

Работа скрипта заключается в замене известных домашних поддиректорий и файлов настроек на ссылки на соответствующие файлы в ~/Dropbox/Dotfiles

~                      ~ на netbook
  Dropbox
    Dotfiles
      universal
        gitconfig      ← .gitconfig
        ssh            ← .ssh
          ...
        zshrc          ← .zshrc
        ...            ← ...
      netbook
        ...            ← ...
        mplayer        ← .mplayer
          config
          subfont.ttf
          ...
      desktop
        ...
        mplayer
          config
          subfont.ttf
          ...

Удаляем текущие файлы и создаем ссылки в репозиторий.

all:
        rm -f   $(HOME)/.config/user-dirs.dirs
        rm -f   $(HOME)/.face
        rm -f   $(HOME)/.gitconfig
        rm -fRv $(HOME)/.fonts
        rm -fRv $(HOME)/.ssh
        rm -fRv $(HOME)/.mplayer
        rm -fRv $(HOME)/.m2
        rm -f   $(HOME)/.Xdefaults
        rm -f   $(HOME)/.zshrc
        ln -fs $(DOTFILES)/$(UNIVERSAL)/config/user-dirs.dirs $(HOME)/.config/user-dirs.dirs
        ln -fs $(DOTFILES)/$(UNIVERSAL)/face       $(HOME)/.face
        ln -fs $(DOTFILES)/$(UNIVERSAL)/gitconfig  $(HOME)/.gitconfig
        ln -fs $(DOTFILES)/$(UNIVERSAL)/fonts      $(HOME)/.fonts
        ln -fs $(DOTFILES)/$(UNIVERSAL)/ssh        $(HOME)/.ssh
        ln -fs $(DOTFILES)/$(UNIVERSAL)/m2         $(HOME)/.m2
        ln -fs $(DOTFILES)/$(NODE)/mplayer         $(HOME)/.mplayer
        ln -fs $(DOTFILES)/$(UNIVERSAL)/Xdefaults  $(HOME)/.Xdefaults
        ln -fs $(DOTFILES)/$(UNIVERSAL)/zshrc      $(HOME)/.zshrc

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

Программная транзакционная память в Clojure предоставляет программисту простые, мощные и безопасные инструменты для многопоточного программирования. Эти инструменты сконструированы с учетом типичных ошибок и проблем в многопоточных программах. Например, Clojure STM гарантирует что взаимные блокировки никогда не возникнут в процессе её работы.

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

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

Например, для агента

(def a (agent nil))

попытка организовать взаимную блокировку в лоб обречена на провал — STM нам этого не позволит — агент не может ожидать окончания выполнения действия в самом действии:

(send-off a #(do (await a) %))

> (agent-error a)
#<Exception java.lang.Exception: Can't await in agent action>

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

(send-off a #(do (deref (future (await a))) %))

Вуаля — наш агент впал в кому, и больше на сообщения не реагирует:

(send-off a (constantly true))

> a
#<Agent@asdf: nil>
↓ Старый булшит