Category: it

Category was added automatically. Read all entries about "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

scala.meta: новая платформа для метапрограммирования Скалы

scala.reflect - наш публичный API, появившийся в Скале версии 2.10 - оказался весьма полезным на практике. Благодаря макросам, основанным на новом API, стало возможным реализовать такие библиотеки как async (DSL для упрощения работы с асинхронностью), pickling (статически генерируемые сериализаторы с отличным перфомансом), scala-blitz (ускорялка стандартных коллекций), а также улучшить ряд уже существующих решений в scalatest, Play!, parboiled и других популярных библиотеках.

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

Основываясь на нашем опыте с scala.reflect и на отзывах пользователей макросов, мы спроектировали scala.meta - новую платформу для метапрограммирования Скалы, главной целью которой является легкость в использовании и портабельность относительно существующих (Scala 2, Intellij) и планирующихся (Scala 3) реализаций языка. Первый technology preview релиз scala.meta намечен на эту осень, а пока что предлагаю посмотреть видео нашей презентации с ScalaDays и обсудить дальнейшие планы в каментах.

Слайды: Easy Metaprogramming For Everyone!
Видео: Запись презентации на parleys.com
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

Эмуляция ключевого слова static из D на макросах

Очень порадовал комментарий некоего схемиста [1] к посту про compile-time function execution в D.

Если вкратце, в D перед объявлением переменной можно написать ключевое слово static (например, static int r1000 = euler1(1000)) и это приведет к тому, что компилятор попытается посчитать правую часть объявления во время компиляции.

Увидев оригинальный пост, я сразу начал думать, как то же самое можно было бы наколбасить на макросах в Скале. В принципе, что-то похожее можно изобразить уже прямо сейчас, объявив euler1 как макрос, который потом раскроется во время компиляции и дальше понятно. Но, конечно, это далеко не так интересно, как static, т.к. таким образом встраиваемые функции: 1) нужно объявлять специальным образом, 2) в них нужно работать с абстрактными синтаксическими деревьями, а не с обычными значениями, 3) как следствие пункта 2, не получится шарить эти функции между компайл-таймом и рантаймом.

Впрочем, есть решение, которое позволяет изобразить в точности ту же самую семантику, которая доступна в D. Для этого нужно написать хитрый макрос, назовем его ct, который будет использоваться вот так: val r1000 = ct(euler1(1000)). При желании, можно завернуть этот макрос в аннотацию, чтобы все выглядело предельно похоже на D: @static val r1000 = euler1(1000).

Что же будет делать этот макрос? На каждый экспаншен он будет генерить по макросу и тут же эти макросы вызывать. Например:
ct(euler1(1000))

↓

{
  macro temp = lift(euler1(1000))
  temp
}
Крутота неописуемая. Жаль только, что в текущей реализации макросистемы в Скале такие фокусы не пройдут из-за требования раздельной компиляции макросов и кода, их использующего.

[1] http://www.reddit.com/r/programming/comments/cb14j/compiletime_function_execution_in_d/c0rcqac
glider

Метапрограммирование в Агде и немного философии

На днях просмотрел паперу Dependent Type Providers [1] и магистерскую диссертацию Reflection in Agda [2], которые используют средства рефлекшена, предоставляемые Агдой, для метапрограммирования.

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

Но самое интересное было в том, что, несмотря на навороченность системы типов, в Агдовском рефлекшене наблюдаются примерно те же самые проблемы, с которыми мы боремся в Скале:
1) Низкоуровневость API синтаксических деревьев
2) Невозможность использовать квазицитирование для нетипизированных фрагментов кода
3) Отсутствие возможности создавать новые конструкторы данных во время компиляции
4) Протекание деталей реализации (например, [3])

На мой взгляд, это очень показательный факт для иллюстрации того, что удобное метапрограммирование не получается автоматически из факта продвинутости языка программирования. Для метапрограммирования необходим серьезный доменно-специфичный фундамент [4].

[1] https://groups.google.com/forum/#!topic/scala-internals/B9g31aAkebE
[2] http://igitur-archive.library.uu.nl/student-theses/2012-1030-200720/UUindex.html
[3] http://code.google.com/p/agda/issues/detail?id=466
[4] http://xeno-by.livejournal.com/85721.html
glider

Зачем компилятор должен поддерживать гигиену?

Затем, чтобы синтаксический сахар в вот таких ситуациях
case class C(x: D)

↓

object C {
  def apply(x: D) = new C(x)
}
Не приводил к казусам вроде:
case class C(x: D)
object C { type D = Double }

↓

object C {
  type D = Double
  def apply(x: D) = new C(x)
}
Как говорится, science. It works. Низкотехнологичные решения в таких ситуациях вынуждены прибегать к очень некрасивым хакам для реализации того, что в гигиеничных системах достигается забесплатно.
glider

Зачем нужны имплисит макросы?

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

Когда макросы только начинались, и у нас еще даже не было разделения на macro defs и macro impls, весьма интересным занятием было помечтать: "а что если сделать макро типы?", "а что если сделать макро пакеты?" и так далее. Поэтому довольно быстро мы сообразили, что нужно будет поэкспериментировать с тайп макросами и макро-аннотациями, а, например, макро пакеты не особо и нужны, т.к. они эмулируются тайп макросами, и так далее.

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

А потом мы поняли, и с этого момента имплисит макросы стали моей любимой фичей Скалы. Итак, имплисит макросы нужны для автоматической генерации инстансов тайп классов. Есть еще несколько прикольных применений, но там именно что прикольно, а материализация тайп классов это фундаментально. Collapse )
glider

макросы для внутреннего использования

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

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

***

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

Генерация реифицированных типов работает следующим образом. Если кому-то нужно для тайп-параметра T заполучить реифицированный тип (т.е. чтобы каждый полиморфный вызов сохранял тайп-аргумент T), то он пишет: def foo[T](параметры блаблабла)(implicit t: TypeTag[T]) = [тело функции бла бла]. У этой записи есть синтаксический сахар, т.е. она обычно очень компактная, но сейчас не об этом. В итоге, если программист вызывает foo[Int](...), то компилятор сгенерирует TypeTag[Int]. Если вызывается foo[List[T] forSome { type T <: C }](...) или еще какая-нибудь жесть, компилятор все равно сгенерирует точное представление тайп-аргумента. Наконец, если функция foo расположена внутри метода bar[T] с тайптегом для T и вызывается как foo[T](...), то тайптег из bar протечет в вызов foo. Могу потом подробнее рассказать.

Раньше в Implicits.scala, слое компилятора, который отвечает за имплисит серч, был хардкод типа "if (tp.typeSymbol == definitions.TypeTagClass) synthesizeTypeTag". А теперь там вообще нифига нет. Просто в стандартном prelude у нас есть макрос "implicit def materializeTypeTag[T]: TypeTag[T]" (на самом деле, немного не так, но это технические детали). В итоге: убрали хардкод из тайпчекера (т.е. упростили и сделали красивее) + мне (не как аппликейшен девелоперу, а как мейнтейнеру компилятора) стало гораздо легче менять реализацию materialize, т.к. она теперь связана с API компилятора через интерфейс macro API, а не в стиле "можем использовать все, до чего импорты дотянутся".

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

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

О. Еще. При помощи макросов можно одновременно и упростить, и обобщить deriving (для тебя это, вероятно, не новость, но для полноты можно упомянуть). Кое-что на эту тему было видно вот тут: http://docs.scala-lang.org/overviews/macros/inference.html, но есть и другой аспект - при помощи макросов можно генерить бойлерплейт-методы в классах. Даже при текущих убогих технологиях народ уже что-то фигачит: https://github.com/dicarlo2/ScalaEquals, а дальше можно будет просто поставить аннотацию на класс, и она сама все сделает.
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.