26 окт. 2010 г.

в этом посте я напишу, как я приспособил свой Vim для программирования на Clojure.
для этого существует замечательный плагин - VimClojure. он умеет, разумеется, подсвечивать синтаксис и делать правильные отступы, как и любой языковой плагин, но кроме того он обладает такими полезностями, как автодополнение (omni completion), документация по языку и, наконец, самое полезное - запуск REPL’а в буфере Vim’а!!!
к сожалению, последнюю функцию оказалось весьма непросто настроить. остальные работали из коробки.

приведу краткое руководство по включению плагина и настройке REPL’а (по сути дела, частично процитирую с переводом эти инструкции)
  1. скачиваем с сайта vim.org последнюю версию плагина.
    для меня это была версия 2.2.0. вполне возможно, что в следующей версии надо будет делать что-то другое, хотя скорее всего изменения будут в сторону упрощения.
  2. распаковываем архив в ~/.vim (или в ~\vimfiles на винде) так, чтобы соответствующие папки попали в соответствующие, если те уже существуют.
  3. прописываем (странно, если у кого-то это ещё не сделано) в ~/.vimrc (или ~\_vimrc на винде) следующие строчки:
    set nocompatible
    filetype plugin indent on
    syntax on
    желательно в таком порядке и где-нибудь вверху файла.

всё, теперь основные функции плагина будут работать. одна из них кстати - это замечательная подсветка скобочек: они подсвечиваются парами разными цветами радуги! так сразу видно вложенность и читабельность кода намного повышается. для этого надо включить соответствующую опцию:
let g:vimclojure#ParenRainbow = 1

чтобы настроить интерактивную часть плагина, нужно произвести следующие манипуляции:
  1. скачать vimclojure-nailgun-client-2.2.0-SNAPSHOT.zip.
  2. распаковать его куда следует (например туда, где установлен clojure).
  3. если у вас винда - то для вас уже сделан ng.exe, если нет - надо сделать в консоли make и получить исполняемый файл ng.
  4. прописать в .vimrc:
    let vimclojure#WantNailgun = 1
    let vimclojure#NailgunClient = “путь к этому исполняемому файлу/ng”
  5. скачать server-2.2.0-SNAPSHOT.jar.
  6. теперь надо запустить (и это придётся делать каждый раз, когда вы будете начинать работу заново) этот сервер:
    java -cp clojure-1.2.0/clojure.jar:clojure-contrib-1.2.0/:server-2.2.0-SNAPSHOT.jar vimclojure.nailgun.NGServer 127.0.0.1 &
  7. можно наслаждаться всякими крутыми штуками - как просмотр документации по текушему слову под курсором, или вычисление текущей строки/блока/файла, или просто запуск REPL’а в отдельном буфере.

но в 6м шаге кроется главный подвох. у меня с первого раза не получилось запустиь этот сервер. и со второго тоже. и я никак не мог понять, что я делаю не так. смысл в том, что надо просто делать всё аккуратно - надо правильно указать пути после параметра -cp (java classpath):
<путь к скачанному дистрибутиву clojure/clojure.jar>:<путь к библиотекам clojure-contrib>:<путь к серверу, который мы скачали на шаге 5>

надеюсь, эта инструкция кому-нибудь поможет.. в другой раз напишу, как установить режим редактирования с парными скобками paredit, который тоже очень и очень удобен и, пожалуй, даже обязателен при программировании на Lisp/Clojure.

11 окт. 2010 г.

Про Vim и LaTeX
.

Весь вечер сегодня и всю ночь разбирался я в LaTeX'е и настраивал под него Vim. Я пользуюсь замечательным плагином Latex-Suite, но в нём столько всего - прям ваще дофига, так что я долго ещё буду в нём копаться..
Мне захотелось написать о паре крутых настроек прямо сейчас (в 5 ночи/утра), потому что потом как известно не найдётся на это времени.
  • Про языковые раскладки и математический режим Теха

    Я пользуюсь внутренним переключением раскладок в Vim'е:
    set keymap=russian-jcuken
    set iminsert=0
    set imdisable

    Поскольку стандартным сочетанием Ctrl-^ пользоваться неудобно, я замапил его на бесполезную кнопку § (на моём маке она рядом с 1, а та, которая там расположена на обычных клавах - с ~ и ` - между Shift и z).
    noremap §
    lnoremap §

    inoremap §


    На этой же кнопке есть не менее бесполезный символ ±, на который я повесил следующее сочетание:
    inoremap ± $
    lnoremap ± $

    и поместил его в ftplugin/tex.vim чтобы оно работало только в теховских файлах. Это очень удобно: пишешь основной текст на русском, нажимаешь ± и при этом в техе символом $ открывается математический режим и переключается раскладка на английскую, вводишь формулы, снова нажимаешь заветную клавишу и вуаля - режим формул закрыт, а раскладка снова русская, можно дальше набирать текст.

  • Про аббревиатуры и макросы

    В плагине Latex-Suite есть множество макросов для быстрого набора различных теховских конструкций. Их можно посмотреть вот тут.
    Мне очень нравятся макросы, начинающиеся на `. Они короткие и понятные. Например, ` + 'латинская буква' заменяется на "соответствующую" греческую: `a -> \aplha, `t -> \theta и т.д. А сочетание `/ - вставляет команду для дроби: \frac{<++>}{<++>}<++> - при этом в ней есть места для подстановки (<++>) при нажатии Ctrl-j курсор перескакивает к следующему такому месту и там сразу можно писать то, что нужно.

    В общем, таких макросов много, они удобны, но разумеется, хочется написать свои, чтобы ускорить набор, например, собственных команд или окружений. поначалу, я пользовался встроенным вимовским iabbrev, но он как-то кривовато работает и довольно неудобен. Так что стоит воспользоваться, опять же, мощью Latex-Suite и в ftplugin/tex.vim прописать конструкцию следующего вида:
    call IMAP('>=', ' \geq ', 'tex')
    call IMAP('<=', ' \leq ', 'tex')
    - в первом аргументе IMAP то, что надо заменять, во втором - на что заменять. Всё очень просто. Так же можно это сделать для окружений. Но для того, чтобы пользоваться спецсимволами типа (перевод строки), нужно их экранировать и брать второй аргумент в двойные кавычки:
    call IMAP('def ', "\\begin{definition}\<++>\\\end{definition}", 'tex')
    call IMAP('опр ', "\\begin{definition}\<++>\\\end{definition}", 'tex')

  • Построчная синхронизация исходника с результатом

    Это, пожалуй, самая крутая функция, которую я сегодня настроил. Для этого я установил pdf-viewer Skim (mac only), и произвёл следующие манипуляции:
    1. в настройках Skim на вкладке Синхро сделал следующие настройки:

    2. а в ftplugin/tex.vim прописал:
    let g:Tex_DefaultTargetFormat='pdf'
    let g:Tex_CompileRule_pdf='/usr/texbin/pdflatex -synctex=1 -file-line-error -interaction=nonstopmode $*'
    let g:Tex_ViewRule_pdf = 'Skim'

    во второй опции главное - это -synctex=1. Об этом я прочитал вот тут, но к сожалению попал я туда не сразу и пришлось долго искать альтернативы типа pdfsync.. А тут перечислены альтернативные вьюеры, если этот почему-то не подходит.

    В результате этих несложных манипуляций получилась следующая картина:

    когда я редактирую теховский исходник в Vim'е, я нажимаю \ll чтобы скомпилировать его и \ls, чтобы перейти к соответствующему месту в pdf с результатом. Наоборот, когда я просматриваю результат и, положим, нахожу ошибку, я нажимаю Cmd-Shift, щёлкаю мышкой по ошибке и попадаю в Vim'е на соответствующую строчку с кодом!

  • Spell-check

    ну и напоследок, я немного разобрался с проверкой орфографии в Vim'е. не могу сказать, чтобы очень хорошо разобрался и поэтому пользоваться пока не очень удобно, но как и каждый раз, удивлён тем, что в Vim'е таки есть эта функция и она вполне себе нормально работает! вот вкратце, как ей пользоваться:
    1. вкл. ':set spell' / выкл. ':set nospell'.
    2. установить язык проверки ':set spelllang=ru_yo' (русский язык с буквой ё).
    3. перейти к следующей/предыдущей ошибке: ']s' и '[s' соответственно.
    4. добавить в словарь: 'zg'.
    5. предложить варианты исправления: 'z='.
P.S.
Хочу отдельно отметить, что когда я набрал ':set spelllang=ru', MacVim сказал мне что для такого языка словаря нет и спросил разрешения скачать. Я был шокирован и разрешил - он просто скачал нужные файлы и всё - в прежние времена он в лучшем случае отправил бы меня самого всё искать и качать, а то бы и просто ругнулся, что не знает такого языка. А теперь-то - всё для людей!

11 апр. 2010 г.

Покупки на Amazon.com

Купил недавно на Amazon.com себе iPod Touch (3G 64Gb). А сейчас заказал ещё один для знакомого.
Значительно сэкономил по деньгам, но главное - приобрёл опыт покупки через интернеты из-за границы.
Вот краткая схема, по которой я действовал:

1. Оформление кредитки. Подходит любая, которой можно платить в интернете. Например Visa e-Card. Виртуальная карта, только для электронных платежей. Я оформил такую в ВТБ24.
2. Регистрация на Paypal. Необязательно. Я регистрировался, потому что думал ещё на e-bay покупать.
3. Регистрация на Shipito.com. Посредник для доставки товаров из Америки в Россию.
4. Регистрация на Amazon.com. Там на месяц дают возможность бесплатно пользоваться двухднейвной доставкой. на деле это занимает 3-4 дня, с обработкой на почтах, но всё же довольно быстро.
5. Покупка. Оплата по кредитке. Адрес оплаты Российский. Адрес доставки американский - полученный в Shipito.
6. Получение посылки из Amazon'а в Shipito. Оплата доставки (через Paypal или просто по кредитке).
7. Ожидание. Я заказал доставку Expess Mail (рекомендую - это не сильно дороже, но довольно быстро), поэтому ждал недолго. 7-8 дней через океан.
8. Обработка в России на таможне, почте и тп. У меня всё прошло очень быстро - всего несколько дней.
9. Получение.

Первые 5 пунктов совершаются в один вечер. через ~3-4 дня посылка оказывается в Shipito. если для отправки денег на счету достаточно, они отправляют в тот же день или на следующий. потом 7-8 дней до России. и так далее..
Итого, я сэкономил ~4000р. (если сравнивать с официальными реселлерами Apple, то вообще 8000р.) и потратил ~17дней со дня заказа, до дня получения.


ссылки на статьи которые меня вдохновили и очень помогли, там все подробности:
o самая главная, которую я прочитал первой, не очень подробная, зато сразу понятна общая схема (много полезного есть в комментариях)
o много информации про кредитки
o так я остлеживал посылку (помимо писем от USPS)
o всё про Shipito
o ещё раз обо всём, но я не читал особенно, нашёл уже после покупки

p.s. забыл сказать маленький нюанс: в Shipito надо заполнить таможенную декларацию. я написал в наименовании "mp3 player" (не надо писать конкретно), и указал стоимость 330$. реальная стоимость была 350, но беспошлинный лимит у нас на таможне 10000р. за его превышение необходимо платить пошлину: (x-10000)*0.3, где x - стоимость товара.

2 янв. 2010 г.

Twitter to vkontakte. Содержательная часть

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

это literate-haskell-код, то есть текст со вставками кода. этот пост можно просто сохранить целиком в файл с расширением .lhs и скормить интерпритатору/компилятору. всё должно нормально работать.

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

пару раз я использовал регулярные выражения:

> import Text.Regex.TDFA ((=~))

один раз разрезал и склеивал список:

> import Data.List (intercalate)

для всех интернет-запросов пользовался библиотекой curl:

> import Network.Curl (curlGetString)
> import Network.Curl.Opts

читал и парсил rss-фиды:

> import Text.Feed.Import (parseFeedString)
> import Text.Feed.Query (getFeedItems, getItemSummary)

и даже разок кодировал строку в юникод:

> import Codec.Binary.UTF8.String (encodeString)

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


twitter через rss

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

> feedUrl = "https://twitter.com/statuses/user_timeline/22251772.rss"

как забирать rss-фиды и парсить, я подсмотрел в статье про rss2lj. но пользоваться этой библиотечкой я не стал. там всё конечно хорошо сделано, но мне нужна одна простая функция, которая будет скачивать rss-фид, брать первый элемент и извлекать его содержание. и вот как я её сделал:

> getTweet :: IO String
> getTweet = do
>     (_,feed) <- curlGetString feedUrl []
>     return $ getMsg $ head $ getItems feed
>     where
>         getItems = maybe (error "rss parsing failed!") getFeedItems . parseFeedString
>         getMsg   = maybe (error "rss-item parsing failed!")  format . getItemSummary
>         format   = unwords . ("twitter:":. tail . words . encodeString

поясню, что в ней происходит. функция curlGetString :: URLString -> [CurlOption] -> IO (CurlCode, String) берёт url-адрес, список опций, и выдаёт код операции (CurlOk, если всё прошло успешно) и ответ сервера. в данном случае, в качестве адреса мы указываем нашу twitter-rss ленту, и не даём никаких опций. на код завершения не обращаем внимания. а вот содержательную часть ответа обзываем feed.
следующую строку надо читать справа налево: извлекаем элементы фида (getItems feed), получаем список, берём из него первый элемент (head), извлекаем из него собственно сообщение (getMsg) и возвращаем на выход.
а теперь поподробнее об этих функциях, в таком же порядке. каждая из них написана в point-free-style, то есть без указания аргумента, просто как композиция (точка .) других функций.
композицию тоже можно читать справа налево, по точкам (: то есть в порядке применения фукнций: в getItems сначала применяется функция parseFeedString (из библиотеки feed), она имеет тип (String -> Maybe Feed), то есть на вход получает строку со всякой кашей из rss-тегов, а выдаёт абстрактный тип фида, с которым уже можно что-то делать. поскольку возвращается значение Maybe Feed ("Может быть фид"), может статься, что парсер подавится, и вернёт Nothing - тогда мы выдаём ошибку с текстом "rss parsing failed!". если же парсинг пройдёт удачно, мы получим значение (Just фид), и тогда применим к нему функцию getFeedItems, которая извлекает из фида элементы в виде списка. это ветвление (Nothing или Just ...) реализуется стандартной функцией maybe.
после работы getItems мы получим список элементов фида: [Item]. нам нужен только первый из них (то есть последний по дате). берём его функцией head. и теперь хотим выковырять из него текст сообщения: getMsg.
эта функция имеет схожую с getItems структуру: сначала применяется getItemSummary, которая возвращяет Maybe String. если извлечь содержание не удалось, выдаём соответствующую ошибку. иначе, форматируем полученное сообщение.
форматирование (format) производится вкратце следующим образом (опять справа налево): кодируем строку в unicode, разбиваем на слова (по пробелам), выбрасываем первое слово, вставляем вместо него "twitter:" (по желанию), склеиваем обратно все слова в одну строку. первое слово в rss-твитах - это всегда ваш ник. поэтому мы его выкидываем.

вот и всё с rss. я, возможно, описал всё излишне подробно, но думаю, для любопытствующих, незнакомых с haskell'ем, это описание было содержательным.


vkontakte api

первым делом заведём несколько констант для работы с вконтактом:

> email = "ваша почта, использованная для регистрации вконтакте"
> uid = "ваш user id вконтакте"
> pass = "ваш пароль от вконтакта"

это данные, соответствующие вашей регистрации во вконтакте.

все операции осуществляются обычными GET запросами на сервер (всё та же функция curlGetString), с соответствующими хитрыми адресами. строятся они следующим образом:
базовый адрес (например http://userapi.com/data?) плюс список параметров в форме ключ=значение, разделённых амперсандами &.
чтобы формировать такие адреса, напишем пару вспомогательных функций:

> param :: (String, String) -> String
> param (key, value) = key ++ "=" ++ value ++ "&"

эта функция просто берёт пару (ключ, значение) и делает из неё строку нужного формата.

> formUrl :: String -> [(String, String)] -> String -> String
> formUrl base opts sid = base ++ ( concatMap param (opts++[("id",uid)]) ) ++ sid

формируем url нужного формата из базового адреса base, списка опций opts (ввиде пар), и идентификатора сессии sid (о нём позже).
содержательная часть находится в скобках: map берёт функцию и список, и применяет функцию к каждому элементу списка. то есть из списка пар (ключ, значение), делает список строк "ключ=значение&". а concat просто склеивает все эти строки в одну (concatMap = concat . map).
для разных задач набор опций отличается, но во всех случаях нужно указывать идентификатор пользователя (uid), поэтому, чтобы не писать эту опцию каждый раз, мы добавляем её в определении этой функции.

чтобы как-то работать с вконтактом, нужно сначала авторизоваться. тогда сервер даст нам печеньки (cookies) и идентификатор сессии (sid = session id). печеньками я пользоваться не стал, а вот sid нужен практически для любой операции с получением/изменением данных пользователя.

> login :: IO String
> login = do
>     (_,headers) <- curlGetString authUrl [CurlHeader True]
>     return ( headers =~ "sid=[a-z0-9]*"  :: String )
>     where
>         authUrl = formUrl "http://login.userapi.com/auth?"
>                           [("site","2"), ("fccode","0"), ("fcsid","0"), ("login","force"),
>                            ("email",email), ("pass",pass)] ""

адрес для аутентификации имеет кучу опций, назначение которых я не понял, но взял из документации и без них ничего не работает. формируем этот адрес спомощью только что написанной функции formUrl, при этом в последние две опции вставляются наш email и пароль. а параметр sid остётся пустым - у нас его пока нет, и собственно ради него мы и написали функцию login.
что в ней происходит: посылается curl-запрос, по адресу authUrl, который возвращает заголовки headers (для этого выставляется опция CurlHeader). в них собственно печеньки, адрес перенаправления и что-то ещё. вот в адресе, куда нас посылает сервер, и спрятано то, что мы ищем. с помощью секретной техники регулярных выражений, из headers выдирается заветный session id, вида "sid=35dfe55b09b599c9fx622fcx8cd83a37"
на регулярных выражениях в haskell'е я останавливаться не буду - это отдельная тема. можно считать, что это просто поиск подстроки нужного вида.

замечательно! sid мы получили, теперь перед нами открыты все возможности api вконтакта. для нашей узкой задачи нужна только одна - изменение статуса.
в принципе любое взаимодествие с вконтактом будет свобиться к следующей команде:

(_,answer) <- curlGetString someUrl []

где someUrl - соотвествующий запрос (смотреть в документации), а answer - ответ сервера. вот как выглядит запрос на изменение статуса:

> setActivityUrl :: String -> String -> String
> setActivityUrl text = formUrl "http://userapi.com/data?" [("act""set_activity"), ("text", text)]

обратите внимание на то, что третий параметр функции formUrl - sid, не указан. это частичное применение - у функции 3 параметра, а мы дали только 2, значит получилась функция от оставшегося одного параметра. то есть setActivityUrl - функция не только от параметра text (собственно новый статус), но и от второго параметра sid, который как бы дописывается справа.

ещё одна мелочь: в тексте твита будут пробелы, а это недопустимо для url-запроса. поэтому мы сделаем простенькую функцию, заменяющую все пробелы, на %20:

> escSpaces = intercalate "%20" . words

она разбивает строку на список слов, вставляет между соседними элементами этого списка строку "%20", а потом склеивает всё снова в одну строку (последние два действия делает функция intercalate).

теперь мы можем собрать из уже обсуждённых частей, функцию изменения статуса:

> setStatus :: String -> String -> IO ()
> setStatus text sid = do
>     (_,answer) <- curlGetString url [] 
>     if answer =~ "\"ok\":1"   :: Bool
>        then putStrLn ("ok! your new status: " ++ text)
>        else error "something is bad with vkontakte-api..."
>     where
>         url = setActivityUrl (escSpaces text) sid

можно было бы написать эту функцию и проще, в одну строку:

setStatus text sid = curlGetString (setActivityUrl (escSpaces text) sid) []

но первый вариант нагляднее, там делается проверка ответа сервера - если ответ содержит "ok":1, то всё хорошо - статус сменился, о чём мы и сообщаем пользователю (себе то есть).
всё! теперь у нас есть все части мозаики и собрать её очень просто.


main

то, ради чего было были написаны все эти функции:

> main = do
>     tweet <- getTweet
>     sid   <- login
>     setStatus tweet sid

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


заключение

не знаю, нужен ли ещё один пост, рассказывающий о том, как установить необходимые библиотеки, как запустить этот код и как автоматизировать этот запуск.
надеюсь, это было кому-нибудь интересно читать. если таковые найдутся, жду Ваши вопросы/предложения/возражения (:

Twitter to vkontakte. Вступление

с месяц назад, на выходных, когда нужно было готовиться к семинару по гомологической алгебре, у меня проснулась жажда творчества. мне надо было штудировать всякие умные книжки, а я не мог себя заставить (особенно ближе к ночи), таки немного попрограммировал.
суть того, чем я занимался отражена в заголовке - я написал программу, которая репостит твиты в статус во вконтакте.
задача довольно простая и совершенно неоригинальная. собственно началось с того, что я прочитал статью на Хабре о том, как это решается на python и аналогичную статью про php. в интернетах вроде бы даже какие-то онлайн сервисы есть специально для этой задачи. но тут весь цимус в том, чтобы решить эту несложную задачу самому, используя свои любимые инструменты. собственно решение на php появилось позже и с такой же целью.

ну и на чём же писал я? ответ очевиден - на haskell'е, natürlich!

в реализации решения мне помогли те две статьи и статья про репостинг из rss в livejournal на хаскелле.
сначала я хотел по-честному сделать работу с твиттером через twitter-api: потыкал соответствующую библиотечку из hackage, но она сходу не заработала и я её оставил - мне хотелось побыстрее получить результат и было лень копаться и разбираться, что не так. а поскольку твиттер транслируется по rss и чтение rss на haskell' е - уже решённая задача, я пошёл этим путём.

это первое отличие от уже имеющихся решений.
мнимый минус такого подхода в том, что это может медленнее работать, так как твиты получаются несколько опосредовано,, но поскольку срочности особой нету и счёт не идёт на секунды, этот минус отменяется.
второй мнимый минус в том, что так не получить доступ к закрытым страницам. если ваши твиты доступны только для ваших фоловеров, то по rss они не транслируются. но значит вам и не надо, чтобы такие твиты были во вконтакте! так что этот минус тоже отменяется.
плюс же состоит в том, что это более универсальное решение. можно транслировать любой rss-канал во вконтакт. для этого надо будет внести небольшие изменения. но важно то, что нынешняя заготовка даёт возможности для улучшений. можно даже сказать, что это не twitter2vkontakte, а rss2vkontakte.

втрое отличие: я пользовался vkontakte-api, а не парсил страницу в поисках статуса, как мои предшественники. и это правильно. это значит входить в дом через парадную дверь, а не через задний двор.

это было очень длинное вступление, которое можно было не читать (:

остальное - это literate haskell. то есть не код с комментариями, а подробные комментарии с кусочками кода, которые являются обычными исходниками на haskell'е.