Category: it

glider

Давайте познакомимся

Всем привет! Меня зовут Женя или Ксено. Здесь я буду постить свои новые идеи, рассуждения о программировании, события и факты, преломленные через призму моего восприятия. Не знаю даже, чего больше хочу - поделиться своими идеями во славу Мирового Разума, или заодно еще и получить фидбэк, но по ходу дела разберемся.

Коротко о главном. Очень люблю творчество (в моем случае оно находит выражение в программировании). Ищу общения с людьми, которым интересно жить. Хорошо, если вам будет тут интересно, очень хорошо, если что-нибудь пригодится, совсем замечательно, если мы подискассим посты в каментах. Меня можно и нужно называть на "ты". Кроме каментов связаться со мной можно по мылу/гтолку: xeno.by@gmail.com или по скайпу xeno.by. Также, у меня есть профайлы в плюсе, в твиттере и на фейсбуке.

Из интересного:
  1) Мои опен-сорс проекты проиндексированы вот тут: http://projects.xeno.by (некоторые из них описаны в этом ЖЖ: пост про метапрограммирование в C#, пост про Конфлакс),
  2) В аспирантуре EPFL работаю над макросами для Скалы,
  3) В аспирантуре ОИПИ НАНБ работал над Конфлаксом, системой для гетерогенных параллельных вычислений,
  4) Люблю анализировать и оптимизировать wetware - ментальный фреймворк организации сознательной деятельности,
  5) Проверяю на практике идею расширения сознания посредством смены инструментов работы: языка программирования, программного окружения, операционной системы. На этом пути меня порадовал Линукс, я был наповал сражен Емаксом, и смог заменить тотал коммандер на гораздо более эффективный файловый менеджер. Впрочем, через несколько месяцев линукса я вернулся на винду с багажом новых впечатлений и скиллов. А закончилось все тем, что я теперь работаю в макоси,
  6) Расшарил и проиндексировал подборку статей и книжек по программированию (там есть по разным аспектам функционального программирования, метапрограммирования, теории типов и еще много всякого разного, например, набор статеек и слайдов для подготовки к собеседованию по алгоритмам).
  7) Собрал заметки по переезду в Лозанну: как привезти баблос, как сделать мобильный интернет, как снять жилье, о резиденс пермите и всякое разное остальное.

Через меня можно задать вопросы команде разработчиков Scala. Сразу отмечу, что непонятки по синтаксису и функциональности лучше отправлять на stackoverflow или в почтовую рассылку - там на них весьма быстро ответят люди более опытные, чем я. С другой стороны, открытые вопросы и предложения (вроде, например, вот такого или вот такого) можно запостить и сюда. Так как я нахожусь географически недалеко от Мартина и других участников Scala Team, то у меня есть дополнительная возможность обсуждать с ними сабжевые вещи. Не стоит ожидать чудес, но обещаю делать все, что смогу.

Collapse )
glider

Макросы vs шаблоны

Продолжая дискуссию про метапрограммирование в D: http://thedeemon.livejournal.com/68456.html.

С одной стороны, дишный стиль МП кажется очень адхочным, но, с другой стороны, поверхностное знакомство и отзывы автора журнала оставляют впечатление чего-то крайне легкого в использовании. Цитируя уважаемого thedeemon:
Я видел много разных попыток сделать удобные лисп-стайл макросы (camlp4, nemerle, haxe, какие-то кусочки скалы...), и везде это пляски с бубном, часто отдельные фазы компиляции, отдельный синтаксис и длинное страшное слово "метапрограммирование". В D же просто берешь и пишешь generic код, даже и мысль о таком длинном слове не приходит.
Вот, например, надо сгенерировать класс, который в зависимости от типа, передаваемого в генератор, будет иметь или не иметь какие-то мемберы [1].

Макросы

В скале мы пишем макрос, который заворачивает шаблон класса в квазицитату. В эту квазицитату мы сплайсим условно генерируемые кусочки. Например, вот так:
val length = if (R.method("hasLength").exists) q"..." else EmptyTree
val empty = if (isInfinite(R)) q"..." else EmptyTree
q"""
  class MyRange {
    val innerRange: R = ...
    ... // code that exists in all instantiations of MyRange
    $length
    $empty
  }
"""
Да, классно, что есть полноценный рефлекшен API вместо пачки захардкодженных в язык __traits и основанных на них самописных темплейтах, которые, например, делают is(typeof(...)). Да, классно, что сниппеты кода являются первоклассными сущностями и что, соответственно, нет проблем их модуляризировать как угодно. Да, классно, что МП реализуется только макросами, а не кучей языковых фич вроде alias, enum, template, и так далее [2, 3].

Шаблоны

Но все же, посмотрите, как задорно выглядит дишный вариант! (Ниже приведена моя личная экстраполяция примера из книжки, может содержать ошибки). Заметьте отсутствие ада метауровней вида q"бла" бла бла q"бла бла" бла q"бла" (что-то похожее мы обсуждали на scala-internals в контексте тайп макросов [4]). Понятное дело, метауровни никуда не делись - они просто перемешались с генерируемым кодом, но насколько стало удобнее читать:
struct MyRange(R) {
  R innerRange;
  ... // code that exists in all instantiations of MyRange
  static if (hasLength!R)
    auto length() { return innerRange.length; }
  static if (isInfinite!R)
    enum bool empty = false;
}

Вопрос

А теперь вопрос. Какой стиль метапрограммирования кажется вам предпочтительным для решения повседневных задач: макросы или темплейты? Если ответ неоднозначный, то в каких случаях вы бы выбрали макросы, а в каких - темплейты?

Предлагаю оставить в стороне проблемы имплементации того или иного стиля (падения компилятора на сложных темплейтах, сырость первой (экспериментальной!) версии макросов в Скале 2.10, и так далее). Предположим, что подлежащие реализации работают идеально без глюков.

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

Ссылки

[1] https://github.com/PhilippeSigaud/D-templates-tutorial/raw/master/D-templates-tutorial.pdf, страница 21, раздел "Optional code"
[2] http://dicebot.blogspot.ch/2013/04/7-trivial-tips-for-compile-time-meta.html, часть 1
[3] http://xeno-by.livejournal.com/86259.html
[4] https://groups.google.com/d/msg/scala-internals/91W0-PxMQ9Q/hq5n47RxfN8J
glider

Насчет сложности Скалы и причем здесь макросы

Наряду с ожидаемым эффектом мой вчерашний пост вызвал дополнительную реакцию по поводу сложности Скалы. Уважаемые френды отпостились у себя в бложеках: раз, два, может, скоро будет и три.

Я не буду здесь подробно останавливаться на SIP-18 (Скала 2.10), который прячет за прагмами некоторые из фич Скалы 2.9, включая имплисит конверсии. Также только мельком упомяну про DOT, который заменяет экзистенциальные типы и типы высших порядков на симпатичную и гораздо более простую концепцию. Ну и раз шел разговор о xml-литералах, надо сказать, что многие у нас в команде уже настроены их выкинуть. Как можно видеть, уже прямо сейчас идет неиллюзорная работа по упрощению языка.

***

Но это ладно. Что меня действительно удивляет это отношение к макросам как к игрушке для психов. Мне прямо стыдно это упоминать в надцатый раз, но макросы в Скале и макросы в плюсах это совершенно разные вещи. На эту тему предлагаю пролистать наш туториал: http://scalamacros.org/documentation/gettingstarted.html.

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

Что я имею ввиду. Берется ядро Скалы ака DOT (вот ссылка сейчас будет реально для психов: https://github.com/namin/dot). В ядре находятся только трейты, абстрактные типы и path-dependent типы - по сути, все то, что нужно для кейк-паттерна. Остальная пачка фич реализуется на макросах при помощи дешугаринга в DOT (само собой, это идиллия, реальность будет сложнее, но здесь мы просто обсуждаем идею).

В итоге при желании можно писать на минималистичном языке, который сохраняет сущность Скалы (а сущность эта заключается в скрещивании ФП с ООП). При сильном желании можно написать что-то вроде import language.lazyVals или import language.caseClasses и радоваться жизни.
glider

про ООП - 2

Я вот сейчас с ребе sorhed обсуждал и выяснил, что мне больше всего нравится в Скале. Ее система модулей основанная на объектах и миксинах. Объектно-ориентированная система.

Читал на днях недавнюю паперу First-Class Modules for Haskell, где рассказывается про то, как SPJ с товарищем добавляют Scala-like (ну, или ML-like, как кому приятнее) систему модулей в Хаскелл. Вслед за авторами сравнивал этот подход с Nested Types - основополагающей паперой Одерского.

Как я понял, все решают одну и ту же проблему. Как сделать так, чтобы: 1) был модуль Set с функциями empty, add и asList, 2) внутреннее представление множества (структуры данных, возвращаемой empty, и манипулируемой остальными функциями) было скрыто, 3) можно было писать s.add 1 s.empty и все тайпчекалось.

Это вполне можно сделать без объектов - как показывает практика ML и схема, описанная в папере про Хаскелл. Внутреннее представление скрывается за экзистенциальным типом (абстрактный тип в ML или экзистенциальный тип в расширенном Хаскелле), в язык добавляется конструкция, которая говорит, что у s.add и s.empty совместимые типы (manifest types в ML или open в расширенном Хаскелле) - все, готово, можно юзать.

Здорово то, как это сделано в Скале. Модули (они же objects ака singleton types) + абстрактные типы (они же abstract type members) + self-types или явные type refinements. Под капотом там тот же самый матан с экзистенциальными типами (чтобы убедиться, можно почитать вышеслинкованную паперу Nested Types), но выглядит это все как кейк паттерн на всем привычном.

Из ООП взята инкапсуляция как понимание того, что есть первоклассная сущность - модуль, он же объект - внутри которого находятся штучки (в данном случае, типы и термы). Взято наследование (ака subtyping) в плане того, что для абстрактных типов можно задавать upper bounds и в плане того, что сущности можно смешивать вместе при помощи миксин-композиции. Про полиморфизм можно много чего подогнать. Например то, что неважно, что именно под капотом абстрактного типа - главное, что все, кому надо, умеют с ним работать (через функции модуля или через функции из upper bound), поэтому подкапотное содержимое можно удобно заменять.

В итоге: ФП-шный матан теории типов скрестили с идеями из ООП - получилась Скала. Матан остался только под капотом, а фасад оказался "объектно-ориентированный", в результате чего получилась гибкая (наппример, на кейке без привлечения дополнительных языковых средств можно сделать виртуальные классы), но простая для восприятия система. upd. Хорошая иллюстрация к посту есть вот тут: http://maxim.livejournal.com/393915.html.
glider

Квазицитаты, метауровни и всякая всячина

В последнее время меня увлекли квазицитаты, поэтому я отложил сторонку допиливание макросов и начал разбираться с КЦ (кстати, это меня поражает в нашей лабе, и я, похоже, уже заразился этой привычкой - куча народу просто занимается тем, что в данный момент интересно, без особого плана или принуждения себя делать что-то конкретное). На всякий случай вот недавний коммит: https://github.com/scalamacros/kepler/commit/28be76ac3f2893a12898eabca139e27d2b166a8b, но сейчас я постараюсь рассказать человеческим языком.

С давних времен (2.8+) у нас в компиляторе поддерживается экспериментальный Code.lift, который превращает кодяру внутри единственного параметра в эквивалентный AST, который можно проинспектировать в рантайме (аля Expression Trees в сишарпе). С появлением reflection api, динамической кодогенерации и макросов настало время причесать лифтинг и добавить ему туда одну штучку, которая абсолютно необходима для удобной работы с деревьями. Ага, точно - я имею ввиду сплайсинг. Про него и про интересные эффекты, которые им порождаются, и будет сегодняшний пост.

***

Немного введения. Лично я лучше всего учусь на примерах, поэтому вместо тонны слов про то, что такое квазицитаты и сплайсы, вот небольшой сниппет, который заодно проиллюстрирует нашу реализацию. Итак, в моем бранче scalac можно писать вот так:
def two = lift{2}                                   // Constant(Literal(2))
def four = lift{splice{two} + splice{two}}          // Apply(Select(two, newTermName("$plus")), List(two))

val reporter = new ConsoleReporter(new Settings)    // немного бойлерплейта - мы пока не заморачивались над API
val toolbox = new ToolBox(reporter)                 // само собой, к релизу 2.10 все будет более приятно в использовании
                                                    
val ttree = toolbox.typeCheck(four.tree)            // тайпчекает дерево из four, после этого вызова можно 
                                                    // пробежаться по результату и узнать типы фрагментов
                                                    // а также увидеть, к каким именно внешним символам что прибиндилось
                                                    // к примеру, если бы в лифченном коде мы вызывали println, то
                                                    // sym.fullName для println после тайпчека показал бы scala.Predef.println

println(toolbox.runExpr(ttree))                     // запускаем компилятор, минуя первую фазу, parser,
                                                    // результаты тайпчека будут стерты нафиг, т.е. предыдущая строчка была необязательна
                                                    // на выходе будет сгенерирован in-memory байткод
                                                    // который будет подхвачен и исполнен специальным класслоадером
Как можно видеть, лифт преобразует кодяру в соответствующий AST, который можно проанализировать, тайпчекнуть и даже исполнить в рантайме. Более подробно про скаловский AST можно почитать у Мигеля Гарсиа (http://www.sts.tu-harburg.de/people/mi.garcia/ScalaCompilerCorner/UntanglingScalaASTs1ofN.pdf), а я лишь замечу то, что, в отличие от сишарпа, меня порадовало небольшое количество и гибкость стройматериалов. Например, нет разделения на Call и Invoke - есть Select, который достает мембер по имени (в том числе это работает и для методов), а есть Apply, который применяет функцию к аргументам (неважно, делегат это или метод).

Даже сам по себе лифтинг очень интересен. Его реализация в сишарпе весьма ограничена (лифтятся только лямбды + в процессе лифтинга теряется информация о переменных, захваченных из лексического скоупа), но все равно он стал очень популярен для создания type-safe апи. Однако, поработав с деревьями выражений сишарпа, довольно быстро сталкиваешься с необходимостью собирать деревья из динамических кусочков. К сожалению, лифтинг здесь не помогает (ибо он работает только с тем, что известно во время компиляции) и приходится вручную конструировать деревья.

Проблема композиции деревьев решается при помощи сплайсинга. В примере выше мы берем ранее залифченный код (переменную two) и вставляем его в квазицитату (выражение под вторым лифтом). В результате компилятор создаст ссылку на two, а также сгенерирует дерево, соответствующее шаблону, в который мы вставляем two. На самом деле, компилятор сделает еще больше - так как тип переменной two известен (он выводится как Code[Int]), то мы знаем, что сложение в four будет соответствовать сложению двух интов, т.е. обе квазицитаты в примере будут статически тайпчекнуты!

Получается все очень симпатично и концептуально просто так что я бы на этом и закончил, но самое интересное только начинается!

***

Во-первых, а что, если перед тем, как что-то сплайснуть, мы хотим это что-то обработать напильником? Каноничный пример - printf. Перед вставкой аргумента в принт мы, возможно, захотим его отформатировать, например, обернув в Apply(Select(arg, "format"), List(...)). Пока что ничего страшного, ибо спецификаторы формата задают тип аргумента, поэтому результирующее дерево все еще можно типизировать.

Но что, если напильник деформирует дерево так, что его родная мама не узнает мы не будем знать статический тип результата? Это не то, что бы совсем уж высосанное из пальца желание. Например, в нашем кусочке большого дерева мы можем захотеть сослаться на переменную, которая объявлена в другом кусочке дерева. Причем эта переменная может иметь тип, который генерируется динамически в третьем фрагменте дерева. Да, получается затык - наша строго типизированная идиллия в стиле MetaML рушится и надо что-то с этим делать.

Для спасения ситуации многие системы метапрограммирования вводят понятие нетипизированных квазицитат. К примеру, F#. Или вот Немерле, в котором макросы и квазицитаты по умолчанию нетипизированные (правда, с опциональными аннотациями типов). Более подробно об этом дизайн-решении написано в диссере одного из создателей Немерле: http://nazgul.omega.pl/macros.pdf (см. главу 8.1.1 "Static typing vs run-time").

Ту же самую штуку я добавил в Скалу, но возникло небольшое затруднение. Очень хочется типизировать все по макисмуму (например, если в кц есть типизируемые фрагменты, то есть желание их проверить), но для этого надо вводить в систему типов специальный магический тип (в слинкованном выше патче я так его и назвал - Magic). Этот тип, прикасаясь к чему угодно, преобразует это что угодно в Magic. Логично в принципе - если мы вызываем метод у результата нетипизированного сплайса, то мы не можем типизировать этот вызов и вынуждены отложить решение до рантайма. А пока что надо убедить компилятор отвязаться. Очень интересно, есть ли для этого формализация, а то пока что мой подход уж сильно ад-хок.

***

Во-вторых, в честь того, что мы сделали стейджинг крайне легким (вызов магической функции - что может быть легче) и сняли все тормоза (программист может вызвать лифт/сплайс откуда угодно), перед нами начинает маячить знаменитая темная башня метауровней (вот мой миррор статьи, оригинал вроде бы сдох: http://dl.dropbox.com/u/10497693/Library/Computer%20Science/Metaprogramming/Macros/Quasiquotations/Metalevels/The%20Dark%20Tower%20of%20Meta-levels.html):



Дело в том, что метауровни логически разделены между собой, а мы только что дали программисту элементарную возможность их смешать в кучу при помощи лексических скоупов. Смотрим еще один пример:
def y: Tree = ...
lift {
  y;                 // metalevel 1 refers to metalevel 0 => okay, but need a translation
                     // we will generate something like: Ident(newTermName("y"))

  splice { y };      // metalevel 0 refers to metalevel 0 => okay, no need of a translation
                     // we just insert a reference to y in the tree generated in LiftCode
                     // so that the generated code will look like: y (yep, as simple as that)

  def x: Tree = ...

  x;                 // metalevel 1 refers to metalevel 1 => okay, no need for a translation
                     // just Ident(newTermName("x"))

  splice { x };      // metalevel 0 refers to metalevel 1 => uh-oh, no translation will help us
                     // exact value of x will be known only during the run-time
                     // so we can only perform the splice during the run-time
                     // for the state of the art JVM it's more like science fiction
}
Итак мы только что убедились на практике, что: а) метауровни могут использовать переменные предыдущих метауровней, б) метауровни не могут ссылаться на переменные старших метауровней. Надо как-то закодировать это ограничение в компиляторе, и для этого мы снова воспользуемся типами (по крайней мере, на это настроен Адриаан, мнение Мартина пока что узнать не удалось).

Основная идея заключается в том, чтобы кодировать метауровень символов (символ = переменная, функция, класс - все, что угодно, что можно объявить) в аннотации к их типам. Т.е. в приведенном выше примере декларация x выглядела бы вот так: "def x: @ml(1) Tree" (само собой, компилятор вполне способен самостоятельно вывести и @ml и 1, поэтому явное указание аннотации здесь только для примера). Тогда функцию Code.splice мы могли бы объявить как "splice[A](tree: @ml(0) Code[A]): @ml(1) A" и все как бы чотко, но снова возникают проблемы.

Во-первых, метауровней не два и не три, а бесконечное число. Лифт, вложенный в лифт, переносит нас на метауровень 2 и так далее, причем вложенные лифты тоже надо тайпчекать, поэтому наша красивая декларация сплайса ломается и превращается в монстрилу вида "splice[A](tree: @ml(N) Code[A]): @ml(M) A where N < M and M = currentml" (синтаксис исключительно гипотетический).

Во-вторых, снова возникает концепция протекания информации о типах типов через всю программу. Скажем, если мы складываем два @ml(0)-числа, то у нас должно получится @ml(0)-число. А вот если хотя бы одно из этих чисел - @ml(1), то тогда получается @ml(1). Опять же вопрос - есть ли для этого формализация? Реквестирую nponeccop.
glider

емакс, часть 4: ретроспектива

емакс, часть 1: первый взгляд
емакс, часть 2: восторг
емакс, часть 3: windows
емакс, часть 4: ретроспектива

Вдохновленный сегодняшним выступлением @alexott на митапе scala.by, я тоже решил поделиться наблюдениями из своего опыта. Экспы у меня не то чтобы много, но постараюсь быть максимально адекватным. Если я чего-то не догоняю, это не со зла - вы меня поправьте, ладно?

1) Емакс действительно крут. Главные его плюсы, на мой взгляд - неинтрузивность, программируемость и естественная интеграция с консолью. Например, недавно я соорудил наколенную билд-систему, с помощью которой застримлайнил свою работу над абсолютно разнородными проектами в универе. Она прекрасно заинтегрировалась в фар, и не менее замечательно - в емакс. Чтобы из емакса запустить какой-нибудь процесс и вбросить аутпут в буфер, нужны буквально пару телодвижений. Повесить на это все хоткеи и сделать гиперлинки на ошибки - еще немного работы. Пару обтачиваний и вуаля: myke-backend.el. Страшно представить, как что-то похожее сделать в Эклипсе.

2) Практически все можно пилить самому. Это прекрасно и очень вдохновляет (например, одним телодвижением можно посмотреть, на какой именно код забинджен тот или иной ключик), но есть и обратная сторона. Практически все придется допиливать самому. Даже банальную ширину таба нужно настраивать секретным образом (см. отдельную настройку для tab-stop-list), что уж говорить про такие вещи как копипасту, анду или прокрутку (кстати, у меня до сих пор через раз работает выделение мышкой). Это не то что бы уж очень плохо, но надо иметь ввиду, что первые пару недель емакс будет отнимать колоссальное количество времени.

3) Лисп не античеловечный, но и не простой в освоении. Довольно быстро я научился колбасить говнокод (что, наверняка, уже увидели уважаемые гуру емакса, пробежавшись по моему конфигу), но что-либо сложное я предпочитаю писать на чем-нибудь другом. Например, билд-система, упоминавшаяся выше, естественным образом выросла из ад-хок сниппетов на елиспе, но попытки превратить ее в что-то более-менее стройное успехом не увенчались, поэтому я по-быструхе переписал все с нуля на сишарпе. Наверняка, проблемы с удобством стандартной библиотеки и структур данных - всего лишь следствие моего несистемного подхода к изучению елиспа, но что вижу, то и пою. В любом случае, крайне помог бложек Стива Йегги, например, вот этот пост: Emergency Elisp.

4) Емакс - не панацея, что бы не писали в инете (по крайней мере для меня). Для разработки компилятора Скалы я юзаю Эклипс (контрол + клик на дефинишен и дебаг слишком важны, чтобы от них отказываться), для коммитов и истории я юзаю TortoiseGit (magit работает через раз + для чего-то нетривиального в VCS тупо нужен гуй). Вначале я думал, что это я такой нехардкорный, а потом увидел, что все в команде делают то же самое (только гуй к гиту другой, ибо никто не сидит на венде). Даже Мартин, который юзает емакс уже лет двадцать.

5) И все же я очень доволен емаксом. За денек я интегрировал в него греп по проектам с персональными свистелками, после чего выкинул поиск эклипса. Для сложных сессий репла Скалы я тоже юзаю емакс (идея консоли в буфере просто прелестна!). Да и домашки по алгоритмам в латеке я тоже фигачу в емаксе, ибо там подсветка синтаксиса и вручную прикрученный side-by-side превью.

Вот так и живем. Браузинг кода и дебаг в эклипсе, компиляция в фаре, текстовый поиск и реплы в емаксе. Use the right tools for the right job. Искренне ваш, кэп.
glider

емакс, часть 3: windows

емакс, часть 1: первый взгляд
емакс, часть 2: восторг
емакс, часть 3: windows
емакс, часть 4: ретроспектива

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

Сразу после запуска емакс встретил меня сообщением об ошибке в скриптах инициализации и антиалиаснутым курьером. "Окей, в принципе никто и не предполагал, что сразу все заработает" - подумал я и нажал Alt+F4, чтобы перезапустить емакс в режиме отладки. В ответ последовало: "<M-f4> is undefined", что в переводе на русский обозначает, что Alt+F4 по умолчанию ни на что не забинджен. Да, поездочка будет долгой...

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

upd. Также я столкнулся с проблемой тормозов. Часть из них была вызвана гитом (см. ниже по тексту), часть из них имела другие причины. Хорошая новость в том, что все удалось починить. Детали см. вот тут: http://xeno-by.livejournal.com/57626.html.

***

Первым делом я обдумал вопрос программного окружения. Так как емакс неслабо использует возможности стандартных юниксовых тулов (например, поиск по пачке файлов (читай, проекту) проще всего реализовать через find+grep), нужно было где-то достать эти самые тулы. Можно было просто вытащить греп и файнд из mingw, но отказываться от шелла тоже не хотелось, ибо писать дакт-тейп на елиспе - удовольствие ниже среднего. В итоге, остались два варианта: 1) запускать емакс из-под цигвина или чего-то подобного (к сожалению, я плохо разбираюсь в теме эмуляции юникса под виндой, поэтому не в курсе, как еще можно поступить в рамках этой опции), 2) юзать NT Emacs (т.е. емакс, скомпилированный в виде нативного виндового приложения), но при этом использовать bash и gnu utils от цигвина.

После чтения емакс-вики и статейки Стива Йегги я решил остановиться на второй возможности. Кроме общефилософского мнения о том, что предпочтительнее юзать нативную прилагу, немаловажным был тот фактор, что цигвин я видел первый раз в жизни, поэтому непонятно, какие косяки меня могли ожидать. В любом случае, и баш, и корные юниксовые утилиты доступны в обоих случаях, поэтому, выбрав NT Emacs, концептуально я ничего не терял. В итоге я поставил цигвин, добавил его bin в виндовый PATH, установил переменную окружения SHELL в c:\cygwin\bin\bash.exe (это все, что нужно для того, чтобы емакс проинтегрировался с цигвиновскими coreutils) и смело пошел навстречу граблям.

Самым первым делом я пофиксил шрифты. Если к сглаживанию как таковому я со временем привык, то сглаженный курьер меня продолжает удивлять и по сей день. Впрочем, оказалось, что установкой одной-единственной переменной можно сразу изменить шрифт для всего окна емакса. Для полноты картины отмечу два нюанса: 1) формат значений этой переменной нетривиален, 2) переменная локальна для фрейма, т.е. для новых окон нужно ее устанавливать заново. За деталями смотрим сюда.

Следующей на очереди была унификация формата сохраненной сессии емакса между виндой и линуксом. Винда успешно поддерживает симлинки, но никакими симлинками не превратить путь типа X:\ в путь типа /foo/bar. И это даже несмотря на то, что емакс прекрасно переваривает прямые слеши вместо обратных и наоборот. Проблема решилась при помощи цигвина. В папке c:\cygwin я воспроизвел структуру рабочего окружения линукса и забросил оттуда симлинки на папки с реальным контентом. А дальше всего лишь осталось заюзать библиотеку cygwin-mount, которая умеет маппить виртуальные юниксовые пути на реальные виндовые пути внутри папки цигвина.

Пришлось также неслабо поисследовать вопрос с хоткеями. В линуксе проблема гвоздями прибитых хоткеев отсутствует - и в метасити, и в компизе можно перебиндить что угодно, включая вещи вроде alt+tab. В винде все несколько грустнее. Во-первых, из коробки емакс не обрабатывал кнопки Win, отдавая их системе. Если в убунте я мог изображать любые сочетания клавиш с участием Win, то в винде эти штучки уже не работали. К счастью, нашлось простое решение. Для того, чтобы Win превратить в super (или во любой другой модификатор), нужно всего лишь установить парочку переменных.

Дальше - интереснее. Если проблемы с win+tab и win+цифры я ожидал, то увидеть занятыми ctrl+esc и даже win+= было удивительно. Вначале я пытался разрулить проблему, копаясь в реестре, но это были просто припарки - там заработало, тут отвалилось. Ситуацию переломил бесплатный тул AutoHotkey. С помощью несложного вида скриптов он позволяет перекрыть и обработать большой спектр хоткеев (я подозреваю, что все, что угодно, кроме ctrl+alt+del), а также даже скомпилировать свои скрипты в standalone икзешники. Кроме того, я перемаппил Caps Lock на Ctrl - это делается регедитом, без всяких тулов.

Конечно, мои изыскания не могли пройти мимо проблемы с разделителями строк. Вначале я подумал, что эту проблему удастся избежать, т.к. емакс достаточно смартовый редактор для того, чтобы автоматически понимать, где \n, а где \r\n. Но после того, как скрипт, отредактированный под виндой, отвалился в линуксе по причине лишнего символа \r, мне стало не до шуток. Была введена политика огораживания, по которой все новосозданные файлы, несмотря на текущую операционку, кодируются с разделителями в стиле юникса. Исключение - бат-файлы, которые без \r\n виндовый интерпретатор не съест. Интуитивно чувствую, что это решение слишком жестко, чтобы быть практичным, но будущее покажет.

Кстати, по концовке оказалось, что проблема была не в емаксе. Символы окончания строки испортил кое-кто другой. Некоторые из вас, вероятно, уже догадались. В честь этого все вместе передадим привет гиту и его core.autocrlf. Впрочем, тема гита на винде выходит за пределы и так уже большого поста.

Последняя грабля была связана снова с гитом. В интернетах много кто жалуется на неторопливость его виндового порта. Как я понял, это связано с тем, что гит юзает идиомы линукса, которые невозможно эффективно замаппить на виндовый апи, в честь чего и тормоза. На горьком опыте я выяснил, для дефолтного сетапа msysgit 1.7.2 операции вида git XXX выполняются минимум 0.3 секунды каждая (ssd, core i7, loads of ram). Все бы ничего, но при открытии каждого файла емаксовая инфраструктура сорс-контроля выполняет более 10 таких операций. Путем красноглазия удалось сократить время до 0.1 секунды на операцию, но это все равно баттхёрт! Пришлось отрубать, но, на счастье, получилось чисто - отключился только хук, но не остальной функционал вроде диффов.

***

tl;dr. Поставлено тулов: 1) Cygwin, 2) NT Emacs. Пришлось подпилить: 1) фонты, 2) имена файлов в сессиях, 3) системные хоткеи винды, 4) политику разделителей строк, 5) интеграцию с гитом. В результате под виндой: 1) gnu тулы из емакса работают, 2) шелл-скрипты из емакса (например, grep+find по проекту) работают, 3) сессии емакса, сохраненные в одной операционке, открываются в другой, 4) функциональность и хоткеи емакса в пределах моего воркфлова одинаковы - и в линухе, и в винде.
glider

емакс, часть 1: первый взгляд

емакс, часть 1: первый взгляд
емакс, часть 2: восторг
емакс, часть 3: windows
емакс, часть 4: ретроспектива

Наконец, переборол свой страх перед емаксом и заставил себя слезть с gedit. За сегодня узнал много чего нового - ecb, dired, tabbar, ну и так, по мелочи настроил .emacs.d. Попутно сгенерилась куча вопросов, вот несколько из них.

1. В чем тайный смысл длинных хоткеев для частых действий? Вот, например, C-x C-_ для redo: требует нажатия пяти клавиш для выполнения весьма распространенной процедуры. Это ведь банально неудобно! Наверное, я что-то упускаю, но что? upd. Просто замаппил redo на C-y при помощи UndoTree. Надеюсь, тайный смысл многокнопочных шорткатов пойму позже.

2. С созданием хоткеев я в целом разобрался, но остался нерешенный момент. Как забиндить C-1? Когда я в init.el пишу (global-set-key (kbd "C-1") 'blah-blah), то хоткей тупо не применяется. Если в этой же строчке заменить С-1, например, на C-=, то все ок. upd. Выяснилось, что C-цифры замаплены на некий digit-argument, но я без понятия что это, поэтому просто заюзал другую комбинацию клавиш.

3. Что по концовке будет удобнее: CUA или стандартный подход емакса к копипасту? upd. Врубил CUA, ибо решил вначале добиться минимально необходимого/привычного набора фич, а потом уже играться с емаксом по ходу работы.

4. Как я понимаю, dired - выбор редакции для всех операций, которые касаются нескольких файлов одновременно? Например, поиск некоторого текста во всех файлах папки или замена чего-либо рекурсивно для всех файлов проекта. Или же есть какой-то более удобный тул для этой цели, про который, например, в силу юного возраста еще не написали в бложеках?

5. Как можно удобно сделать инкрементальный поиск по всем файлам проекта? Я нагуглил вариант с запуском диреда с флажком -R и инкрементальным поиском по окошку диреда. Есть ли альтернативные опции с автокомплитом, например, в минибуфере? Как я понял, и ido, и icicles поддерживают только поиск файла в текущей папке. Какие есть еще варианты? (задал вопрос на stackoverflow)

6. Под виндой я привык, что tortoise* регистрирует кастомные оверлеи на иконках файлов, лежащих под сорс-контролем. Файл не менялся после последнего коммита - зелененький, менялся - красненький, ну и в таком духе. Особенная прелесть такого подхода заключается в том, что иконки рекурсивно применяются к папкам, т.о. можно быстро увидеть что закоммитал, а что забыл. Есть ли что-то похожее в емаксе? Я видел, что ECB рисует иконки файлов с учетом их статуса в VCS. Но умеет ли он отображать статус папок? upd. Забил - вообще отключил иконки ECB. Во-первых, они часто глючат, а, во-вторых, магита хватает за глаза - одной кнопкой можно посмотреть статус репозитория, что, в общем-то, и нужно было от иконок.

7. Про какие мастхэв модули я должен знать? Я нашел интересный пост на SO: The single most useful emacs feature - но там больше про мелочи.
glider

Подборка статей и книжек по программированию

На дропбоксе расшарил и проиндексировал библиотечку по программированию (клик). Там есть по разным аспектам функционального программирования, метапрограммирования, теории типов и еще много всякого разного, например, набор статеек и слайдов для подготовки к собеседованию по алгоритмам.

Некоторые креативы в подборке меня впечатлили до состояния просветления по определенным вопросам. Из самых любимых хочу отметить "Оптимизирующие парсер-комбинаторы", "Delimited Continuations" и "Type Classes as Objects and Implicits". Ну и, конечно, как же я мог забыть про "Lightweight Modular Staging: A Pragmatic Approach to Runtime Code Generation and Compiled DSLs"!

Давайте делиться!

upd. К сожалению, анонимусам дропбокс позволяет скачивать только файлы, но не папки целиком. Чтобы обойти проблему, я запаковал снапшоты технических разделов библиотеки и положил их в корень публичной папки Library (см. http://dl.dropbox.com/u/10497693/Library/index.html). Постараюсь регулярно обновлять архивы с учетом новых поступлений.