stdray: (Default)
stdray ([personal profile] stdray) wrote2012-05-11 07:40 am

NEMERLE и поток сознания

Сначала я хотел просто написать, что майский конкурс по ФП закончился, и желающие могут посмотреть, что мы там устроили. А если что-то из увиденного понравится, то и проголосовать.

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


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

Спасибо мне! Сыграл в КО! Однако с языками программирования часто не прокатывает. Осилил %langname% и все, хватит, отвалите. Мне труп страуса нашептал, что у меня в руках ИНСТРУМЕНТ ВЫСОКОГО УРОВНЯ, а потом, прищурившись, добавил: "Полный по Тьюрингу, даааа". Ну теперь вообще ахуеть. Я даже слушать вас не хочу: жаба тормозит, сишка хуже ассемблера, додиез не знаю, зачем придумали, когда у них жаба есть, objc - это просто яблочная илитка, на лиспе искуственный интелект так и не зделали, лямбда-исчисление? исчисление что? Шли бы вы отсюда со своими историями. А потом человеку изменяет сознание и он начинает находить свои любимые костыли даже там, где их никогда и не было. Паттерн ПРИСПОСОБЛЕНЕЦ во все поля.

Я вспоминаю, как начав программировать на C#, испытывал физическую необходимость ОСВОБОДИТЬ ПАМЯТЬ и руками устроить связанный список. Потом-то оно уже проще идет. Знакомые вещи определяются сразу, а новые расширяют кругозор. Интересно узнавать новый язык, задаваясь вопросом: "А как я вообще раньше без этого жил?" Как будто собирается некий пазл, образуя целостную картину, а не "Вот тебе походный ножик. Теперь иди - строй дом".

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

Nemerle - ЯП общего назначения. Я не знаю, что такое общее назначение. Я так мало знаю о программировании микроконтроллеров и о программировании для суперкомьютеров и еще куче областей , что у меня с парнями, работающими в тех сферах, разная специальность. Я понимаю, что хороший программист должен найти себя в любой области, но у нас _настолько_ разные приоритеты, что придется сильно ломать себя. Я пишу средней толстоты эторпрайз (базворды прилагаются), потому для меня важны:
- Надежность. Ведь ничего падать не должно, а если упадет - перезапуститься. Конечно же, все логировать-логировать и алертить. И желательно все это задублировать.
- Прозрачность. Ведь вон та функция "ОТПРАВИТЬ ДЕНЬГИ НА ПЕЧАТЬ", должна отправлять деньги на печать всегда, а не только в состоянии суперпозиции ста тысяч "Если".
- Читаемость. Ну мы же все знаем, что самодокументируемый код - это миф, не так ли? Правда, мы так же знаем, что документирование всего и вся с последующей актуализацией - тот еще кошмар. Потому лучше писать кратко и информативно.
- Тестируемость. Необходимо проверить исправность каждой детальки еще до момента, когда конструктор будет собран. Мы же не хотим ОТПРАВИТЬ ЯЧЕЙКУ НА БОЕВОЕ ДЕЖУРСТВО, ЧТОБЫ ОНА ТАМ ПРОСТО СИГНАЛ РИСОВАЛА ТАБУРЕТОЧКОЙ, так?
- Совместимость. Хаскель исключительно хорош для новых проектов? А если не новых? Вместо библиотек даете возможность их написать? ВЕРНИТЕ НА МЕСТО МОИ ЛЮБИМЫЕ ВЕЛИКИ! БЫЫЫСТРААА!

Nemerle - статически типизуеммый язык программирования под .NET (есть реализация для Mono, но она вроде сломана) со всеми вытекающими. Язык поддерживает ООП в самом распространенном варианте ее реализации с одиночным наследованием, знакомый по Java и C#. Есть:
- Шаблоны (generics)
- Интерфесы
- Методы расширения
- Частичные (partial) классы
- Модификаторы доступа (internal, protected и тд)
- Весь набор, привычный C# программисту. )

Я не вижу смысла, останавливаться на ООП, потому как все достаточно стандартно. Идем далее. Nemerle реализует концепции функционального программирования:
- Нормальные кортежи
- Алгебраические типы данных
- Иммутабельность из коробки
- Паттерн матчинг
- Функции высшего порядка
- Частичное применение
- Вывод типов
- Замыкания
- Перегрузка и определение операторов с возможностью задания приоритета и ассоциативности
- Итераторы
- List comprehensions (не знаю, как переводится)
- Монады (нет, ввод-вывод не в монаде)
- Computation expressions (опять не знаю устоявшегося перевода)
- Опционально двухмерный синтаксис (синтаксис на отступах) через #pragma indent
- Опционально может в ленивость
- Оптимизация хвостовых вызовов
- События и делегаты (я думаю, эти вещи в этом блоке смотрятся лучше)

Вообщем-то весь джентльменский набор, необходимый, чтобы назвать язык функциональным. Стоит отметить, что Nemerle разрешает перегрузку функций (в отличии, например, от F#) и вывод типов спокойно разруливает, какую именно следует вызвать. Мне нравится, но наверное хаскелисты негодуют. Правда, Nemerle требует, чтобы на высшем уровне, то есть в классах и модулях (модуль является обычными статическим классом, для методов которого по умолчанию выставлен модификатор public) необходимо явно аннотировать типы. Я не думаю, что кому-то станет плохо от подобного требования, поскольку даже в Haskell, где система типов способна сама все выводить, аннотации типов все равно приводятся, поскольку по типам можно судить о природе процесса, выполняемого функцией. Еще одной особенностью является возможность "открыть" статических класс, импортировав его содержимое в текущее пространство имен. Может мелочь, но меня всегда раздражали все эти Console.WriteLine.

То есть, на первый взгляд, Nemele ничего особенно не представляет. К тому же разработчики старались не сломать мозг своей потенциальной аудитории, потому сделали синтаксис языка по возможности идентичным C#. Разработчики Scala, как я понимаю, пошли тем же путем. А вот ML-подобный синтаксис с непривычки может очень сильно напугать. Проверено. Язык задумывался, как замена C#, то есть каким C# должен был бы быть. И вы знаете, я согласен. Этих возможностей вполне хватает, чтобы комфортно программировать. F# как раз на этой позиции и находится, то есть со вкусом и без изъебств.

Теперь переходим к Nemerle непосредственно, к тому, что делает язык в большой степени уникальным, - макросам. У меня с макросами всегда были проблемы, сначало звучало страшно, потом было непонятно, где и как и применять. Похоже, это не только моя проблема. Иногда, я думаю, что после изобретения LISP'а, значительная часть развития языков программирования является размышлением на тему "Нужны ли макросы? Какими они должны быть тогда?" LISP'еры хоть и не изобрели искустевеный интелект, но очень многое дали миру: gc, графические интерфейсы и конечно макросы. Мне кажется, Ритчи с Томпсоном догадывались, что всего не предусмотришь, потому запили макры. Сишка используется до сих пор, многие даже испытывают к ней теплые чувства. А более другие люди решили, что макры не нужны, а теперь гордятся тьюринг-полным ТЕМПЛЕЙТО-ДОБРОМ. В Скалочку вот добавляют макросы, а я не видел, чтобы кто-то писал, мол Мартин Одерский не особо рубит в языках программирования.

Так с макросами какие проблемы? Во-первых, сразу начинается нытье, мол развалите весь синтаксис со всей семантикой, превратив язык в непонятную ёбу. Я так же ныл (может еще заною, кто его знает). Но это возврат к разговору о возможности прострелить себе ногу. Меня подзаебали эти разговоры, я решил для себя, что прострелить ногу можно везде и всегда. Мутабельность - прострелил. Энергичность - прострелил. Ленивость - иди чини ноги. Иммутабельность - не прочитал книгу по иммутабельным структурам данных? пиздуй в больницу чинить ноги. И так всегда, не бывает серебрянной пули, как не бывает языков общего назначения, как нет искуственого интеллекта. Именно поэтому, программисты существуют, существуют как профессия, как специалисты, а не просто прослойка между креслом и монитором. Хотя надо отдавать себе отчет в собственных возможностях. Это вам не придти и перевести все критические бизнес-процессы на Хаскель из-за того, что кто-то научился Фибоначи считать. И я не один такой, создатель Лифта такой же точки зрения придерживается. То есть в любом случае некоторый опыт необходим.

Во-вторых, макросы сложные. В Nemerle не сложные, то есть при написании макросов используется тот же самый Nemerle и его возможности, никакого специального DSL не требуется. Держим в уме знание, что макрос - функция с типом List[PExpr] -> PExpr, выполняемая во время компиляции, и все будет хорошо. Макросы гигиеничны, то есть при совпадении имен переменной из макроса и контекста, в котором макрос разворачивается, имя из макроса будет автоматически переименовано, хотя это поведение можно отключать, тогда значение будет связано с ближайшим именем в контексте. Таким образом, кстати, работает макра regex match, которую я использовал в своем решении.

Макросы бывают разными
- Макросы выражения: можно сделать printf("%d%f", x, y), который будет на этапе компиляции проверять соответствие количества параметров и их типов строке форматирования; многие компоненты языка такие как if else, while, for и другие реализованы через такие макросы
- Макросы-операторы: собственно через них реализует переопределение и создание новых операторов. Можно задавать приоритет и ассоциативность. Например, с их помощь в Nemerle сделаны pipe-операторы |> и <|
- Мета-атрибуты - видимо, так называются из-за сходства в записили с атрибутами C#. Позволяют изменить класс, метод, свойство и так далее. Для меня самый впечатляющим примером использования является макрос PegGrammar, который по заданной PEG грамматике создает класс-парсер ее разбирающий. Мой игрушечный пример можно увидеть тут. Если игрушки вам не по душе, то можно глянуть на серьезное использование для разбора грамматики C#.
- Лексические макросы: к сожалению, я еще не успел с этим разобраться, но похоже, макра конструирования XML, напоминающая синтаксис для работы с XML в Scala, относится именно к этой категории.

Мне очень хочется, рассказать о макросах Nemerle, кратко и содержательно, чтобы вы могли испытать то же ВАУ, которое испытываю я. Но, по всей видимости, пока в моем мозгу все по полочкам не разложится, я не смогу внятно излагать. Главное, что я хочу донести, что макросы в Nemerle - это очень гибкое средство, которое красиво и лаконично ложится на язык. Синтаксис языка во много сделан на макросах, кодогенерация требующая в C# сторонных тулзовин отлично реализуется, многие хорошие фичи, которые приходят в C#, как новые элементы языка, делаются на макросах. Я думаю, так ожидаемые Type Providers из F# 3.0 делаются на макросах на ура. То есть я пока не вижу, где заканчиваются возможности, но четко представляю, как применять эти вещи в работе: будь-то автоматическая имплементация всяких IDataErrorInfo с INotifyChanged или как присахарить и обезопасить работу с каким-нибудь SqlCommand. При этом я без проблем, могу использовать существующий C# код, могу использовать в C# проектах код на Nemerle (пока не тестировал). Я считаю, что Nemerle - более удобный и качественный инструмент для решения мох задач.

Надесь, было хоть чуть-чуть интересно. Но если вы внезапно дочитали до сюда и считаете все вышенаписаное бредом идиота , хотя бы начинайте псить. Мне приятно будет)


Update: Как-то получилось, что обсуждение велось параллельно в двух местах: тут и еще одна ветка обсуждения.

[identity profile] stdray.livejournal.com 2012-05-11 09:58 am (UTC)(link)
Я посмотрю внимательнее. Некоторые даже WCF лицом к этому делу разворачивают (http://msdn.microsoft.com/ru-ru/magazine/dd315413.aspx).

[identity profile] xeno-by.livejournal.com 2012-05-11 09:58 am (UTC)(link)
С случае строковой интерполяции оно, может, и да, а вот если мы обобщаемся до произвольных интерполяций (например, таким образом реализуем квазицитаты), то ситуация начинает выглядеть хуже.

[identity profile] thesz.livejournal.com 2012-05-11 10:03 am (UTC)(link)
Я затрудняюсь навести мосты между "(Template Haskell - сразу фпень)" и приведёнными вами выше доводам.

Учитывая информацию из других постов (про RESTful интерфейс на Clojure) и соотнося её с приведённой выше, я вообще опасаюсь получить разрыв мозга.

[identity profile] metaclass.livejournal.com 2012-05-11 10:13 am (UTC)(link)
Объясняю еще раз: писать эмулятор процессора на хаскеле - крайне ок. Потому что алгоритмы.
Писать на хаскеле бухгалтерию, которой нужно долбится в базу данных, писать в фискальную память, печатать на принтера и отдавать кривоспроектированный xml в налоговую инспекцию - не ок. Потому что основные усилия уйдут на взаимодействие с внешним миром, а самый сложный алгоритм там - это закрытие транзитных проводок или FIFO на складе.

[identity profile] thesz.livejournal.com 2012-05-11 10:21 am (UTC)(link)
>>Нормальный порядок вычислений (normal reduction order) снижает потребность в макросах на порядки
>Как нормальный порядок вычислений снижает потребность в макросах?

Нормально так снижает, на порядки. Даже можно сказать, замечательно снижает, ибо на порядки.

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

Вот аналог if на Хаскеле:
ifThenElse :: Bool -> a -> a -> a
ifThenElse True th el = th
ifThenElse False th el = el

Теперь вместо "if c then aaa x else bbb y" вы можете писать ifThenElse c (aaa x) (bbb y) и никто не отличит вашу программу от использующую встроенные if. Это будет работать даже с вводом-выводом, ибо этот ввод-вывод не будет выполняться до тех пор, пока от него не потребуют результата.

И далее там всякое интересное. Tying the knot, и тп.

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

Насчёт printf. Хаскель не умеет анализировать значения. Но может анализировать типы.

Пусть за меня говорит Олег: http://okmij.org/ftp/typed-formatting/index.html

[identity profile] metaclass.livejournal.com 2012-05-11 10:39 am (UTC)(link)
О, с if then else хороший пример.

[identity profile] stdray.livejournal.com 2012-05-11 10:45 am (UTC)(link)
Я сначала не понял, что под макросами поманиется. Решил, что нормальный порядок вычислений, избавляет от всего перечня типов макр. Так яснее. За ссылку спасибо! Я как рас сейчас с Хаскелем по-немногу разбираюсь.

[identity profile] sassa-nf.livejournal.com 2012-05-11 11:34 am (UTC)(link)
читинг.

зато здесь switch...case pattern matching является специальной формой

[identity profile] sassa-nf.livejournal.com 2012-05-11 11:37 am (UTC)(link)
"Няяяяяша!"

скала есть под CLR тожа.

[identity profile] thesz.livejournal.com 2012-05-11 11:50 am (UTC)(link)
Читайте книги, они рулят.

http://en.wikipedia.org/wiki/Church_encoding

Сравнение с образцом введено потому, что... удобней! Для алгебраического типа данных можно ввести кодирование функциями, которое будет выполнять "сравнение с образцом". В этом случае case превращается в простое применение функций.

type Bool' a = a -> a -> a -- решил дать параметр, поскольку со скрытием возникают проблемы.
true, false :: Bool' a
true th el = th
false th el = el

a &&. b = a b false
a ||. b = a true b

ifThenElse :: Bool' a -> a -> a
ifThenElse c th el = c th el

[identity profile] thesz.livejournal.com 2012-05-11 11:55 am (UTC)(link)
>Потому что основные усилия уйдут на взаимодействие с внешним миром, а самый сложный алгоритм там - это закрытие транзитных проводок или FIFO на складе.

А взаимодействие с внешним миром под определение алгоритма не подпадает?

http://en.wikipedia.org/wiki/Algorithm

"In mathematics and computer science, an algorithm Listeni/ˈælɡərɪðəm/ (originating from al-Khwārizmī, the famous mathematician Muḥammad ibn Mūsā al-Khwārizmī) is a step-by-step procedure for calculations."

Ну, ладно. Решил оставить этот комментарий для будущих читателей. Совсем не потому, что хочу продолжать нервировать (судя по первой строке и "объясняю ещё раз").

В такой постановке есть, над чем поразмыслить.

[identity profile] metaclass.livejournal.com 2012-05-11 12:09 pm (UTC)(link)
Надо поломать голову будет.
Я нутром чую, что есть метод оформить взаимодействие с внешним миром, не упираясь в ручной FFI и 100500 трансформеров завернутых в IO, но морально не готов этим занятся сейчас :)

[identity profile] stdray.livejournal.com 2012-05-11 12:22 pm (UTC)(link)
Судя по всему, не очень есть.

[identity profile] sassa-nf.livejournal.com 2012-05-11 12:47 pm (UTC)(link)
и что это демонстрирует?

вот покажите реализацию функции, которая, например, вернёт true этого Bool', если x < 5, а в противном случае - false, тогда отстану. А то пока не очевидно, что можно совсем-совсем обойтись.

[identity profile] sassa-nf.livejournal.com 2012-05-11 12:48 pm (UTC)(link)
в смысле?

ну, я не пробовал, но scala.NET гуглится.

[identity profile] stdray.livejournal.com 2012-05-11 01:03 pm (UTC)(link)
http://stackoverflow.com/questions/743172/is-scala-net-production-ready
http://stackoverflow.com/questions/1897815/scala-on-the-clr

Я посмотрел, нашел SVN репозиторий (http://lampsvn.epfl.ch/svn-repos/scala/scala-experimental/trunk/bootstrap/bin/), но сходу сложно судить насколько этим можно пользоваться и какой все это версии. По всей видимости порт на дотнеты - далеко не самое приоритетное направление для разработчиков Scala.

[identity profile] -darkus-.livejournal.com 2012-05-11 01:10 pm (UTC)(link)
Ох ты ж! Какая драма в комментариях!!!

[identity profile] thesz.livejournal.com 2012-05-11 06:28 pm (UTC)(link)
Уверяю вас, можно.

Попробуйте прочитать про кодирование Чёрча и написать сами. Это не сложно.

Числа:
data Nat = Zero | Succ Nat

Предикат:
less :: Nat -> Nat -> Bool
less Zero (Succ n) = True
less Zero Zero = False
less (Succ a) (Succ b) = less a b
less (Succ n) Zero = False

five = Succ (Succ (Succ (Succ (Succ Zero))))

less5 a = less a five

Я, с вашего позволения, займусь практическим применением Хаскеля. Это более полезно "для одного из обоих нас".

[identity profile] thesz.livejournal.com 2012-05-11 06:30 pm (UTC)(link)
Если хотите моё мнение, то надо зависимые типы двигать в стандарты, натурально.

Чтобы кривых XML вообще в природе не было.

[identity profile] sassa-nf.livejournal.com 2012-05-11 06:56 pm (UTC)(link)
дык кто ж спорит, что можно. другое дело, что в езык встроен готовый предикат <, а чо тогда сравнивать с лисповым "встроенным if"

[identity profile] thesz.livejournal.com 2012-05-11 07:40 pm (UTC)(link)
(<) не готовый предикат. Это обычная функция. Вы уж постарайтесь ознакомится с предметной областью-то.

Сравниваю, чтобы показать выразительность ленивых вычислений. Там, где в Хаскеле обходятся одними функциями высших порядков (функция when :: Monad m => Bool -> m a -> m (), bracket :: Monad m => m h -> (h -> m a) -> (h -> m r) -> m r, использовать bracket openFile hClose thingsToWrite), в Лиспе требуются дополнительные выразительные средства.

[identity profile] sassa-nf.livejournal.com 2012-05-11 08:22 pm (UTC)(link)
Не, ну, я ж не настолько плох. Вестимо, что "обычная функция", но неужто и функции над числами реализованы вот так вот, через Succ как вы пишете?

[identity profile] Влад Чистяков (from livejournal.com) 2012-05-11 09:12 pm (UTC)(link)
Какие проблемы:

WriteLine($"\t$(\"Nemerle\") cool!");

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

В N2 все буде еще круче. Каждый макрос (точнее правило грамматики) сможет сам парсить исходник. Никакого парсера не будет. Каждая макра сама предоставляет свой парсер. Соответственно возможности его будут ограничиваться исключительно возможностями фантазии программиста.

[identity profile] xeno-by.livejournal.com 2012-05-11 09:15 pm (UTC)(link)
N2 звучит здорово! Жаль я не попал на эти Application Developer Days. Можешь еще, пожалуйста, прокомментировать на тему тайп провайдеров в треде выше?

[identity profile] sassa-nf.livejournal.com 2012-05-11 09:16 pm (UTC)(link)
Давайте я щас попробую снова в продуктивное русло перевести беседу.

Ваш тезис был, что "if...else" вообще не нужно в Хаскеле, что является примером, почему в Хаскеле не нужны макросы в отличие от Лиспа. Вот я хочу в этом тезисе разобраться.

Так вот, я замечаю, что ваш первый пример использует другую встроенную форму "if..else".

Второй ваш пример вводит функции true и false, которым можно передать два continuation. Но а. почему это не возможно в лиспе (ибо изначально разговор за if...else начался из-за контрипримера макросам) б. пример с возможной реализацией < через Succ понятен, но неужели так и реализовано?

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

Page 2 of 5