LINUX.ORG.RU

[ЖЖ] *морфизм, Haskell & Lisp

 


3

1

Вот все праздники разбирался с Template Haskell, квазицитированием, SYB и ghc7, не забывая такие важные вещи как распитие спиртных напитков и игру в FreeCiv :)

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

Ведь дело упирается не столько в техническую сторону, сколько в то как компиляторы относятся (или не относятся) к своим собственным структурам и позволяют или нет вмешиваться в них на этапах компиляции.

Единственное, «но» в подходе лисп-систем к компиляции, там компилятор «внутри», а не «с боку» как в более традиционных подходах. А так, работы ведутся, та же Java, та же Scala позволяет вмешиваться в работу компилятора. А в GHC есть Template Haskell, который идеологически близок к лисповским макросам, с той только разницей, что они стоят по разные стороны относительно катаморфизма: лисп как списки, хаскель как алгебраические типы с соответствующей типизацией.

В ООП языках все еще интереснее, там для реализации лисповского подхода нужно две вещи: а) классы должны быть объектами первого класса; б) должен быть способ узнавать конкретный тип объекта в рантайме. В Яве есть и первое (на худой конец в рамках JVM), и второе. В С++ есть RTTI, а вот с первым дела обстоят вроде бы не очень, хотя Александреску в своей книге показывал, вроде бы, как можно генерить иерархию классов с помощью шаблонов. Про Scala, вообще молчу, там алгебраические типы «из коробки» имеются.

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

★★★★★

>Вот и получается что одни дрочат на списки, другие на алгебраические типы, а третьи на субтиповый полиморфизм

А нормальные люди просто работают.

keeHa7er
()

> А реальность, она намного многограннее и интереснее

Угу, в реальности программы пишут, а не на катаморфизмы дрочат.

archimag ★★★
()

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

да, пора прекращать др@чить на что попало, надо др@чить тематически и вприсядку :)

товарищ, надо доводить мысль до конца

shty ★★★★★
()

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

АТД - статические абстракции

Ну и, лисповые макросы они не только для преобразований AST, они вообще суть произвольные вычисления при компиляции

anonymous
()
Ответ на: комментарий от Macil

Хаскеледрочеров тоже знаем. В репозоториях Arch два пакета, зависящих от gch, не считая тех, что предназначены для самого хаскеля. А так мало пакетов потому, что кто-то дрочит на морфизмы и монады вместо того, чтобы писать программы.

keeHa7er
()
Ответ на: комментарий от keeHa7er

>В репозоториях Arch два пакета

Арчеводам привет! А вообще, глянь на hackage.haskell.org... Для хаскеля есть своя система управления сборкой. Cabal называется. Причем она интегрирована с GHC'шной системой пакетов, в которой сделано много чего для избежания module hell'а... А любой дистрибутивный пакет все-равно буедт обязан регистрировать библиотеку в GHC, иначе она работать не будет...

Macil ★★★★★
() автор топика
Ответ на: комментарий от anonymous

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

Ооо! Объекты! Произвольной природы. Афигеть! Любой объект по-определению конкретен, потому что а) является носителем состояния б) предметная область по-определению конкретна, в) у любого объекта есть структура.

они вообще суть произвольные вычисления при компиляции


А какой смысл в этих «произвольных вычислениях», если они не приводят к преобразованию AST? Ради побочных эффектов? Крайне сомнительно.

Macil ★★★★★
() автор топика
Ответ на: комментарий от keeHa7er

>А так мало пакетов потому

Потому что хаскель не нужен.

geekless ★★
()
Ответ на: комментарий от Macil

>Ооо! Объекты! Произвольной природы. Афигеть! Любой объект по-определению конкретен, потому что а) является носителем состояния б) предметная область по-определению конкретна, в) у любого объекта есть структура.

Ты не понял. S-выражения из любых вообще объектов состоят, а АТД - только из тех, которые указаны(причем это может нихуево так ограничивать).

А какой смысл в этих «произвольных вычислениях», если они не приводят к преобразованию AST? Ради побочных эффектов? Крайне сомнительно.


Ну да, ради побочных эффектов. А что сомнительного?
Все define-чтототам макросы фактически побочными эффектами и занимаются(ну, обычно раскрываются в eval-when(:compile-toplevel ...), но суть та же).

anonymous
()

>Ты не понял. S-выражения из любых вообще объектов состоят, а АТД - только из тех, которые указаны

А вот тут как раз и проявляется отношение компилятора к своим собственным структурам и к вопросу о месте и времени компиляции. Во-первых, ни что не может состоять из произвольного числа объектов разного типа: предметная область конечна. Во-вотрых, нам ничто не мешает сгенерировать преобразование АТД -> АТД'. В-третьих, катаморфизм (соответствие между списками и АТД). В-четвертых, никакой магии не происходит, и лисп система работает в базисе список-атом-кейворд-ссылка (я может забыл чего). Но факт-то остается фактом, пока ты эту ссылку не дереференснешь, тип объекта ты не узнаешь.

Ну да, ради побочных эффектов. А что сомнительного?


Я не очень четко выразился. Да во время исполнения форм происходят побочные эффекты, например defun своим побочным эффектом имеет создание соответствующией функции. Это понятно, и ожидаемо. Я имел ввиду другие побочные эффекты: работа с «миром».

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

Macil ★★★★★
() автор топика

>В ООП языках все еще интереснее

а) классы должны быть объектами первого класса;

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

В Яве есть и первое (на худой конец в рамках JVM), и второе.

А чо сразу Ява? Если там, чтобы работать с типом, как с объектом, надо как в сишарпе пробежаться по всем классам сборки и проверить, а не является ли наш объект экземпляром такого-то типа, то эт всё полное фуфло. Вот в Smalltalk...

yoghurt ★★★★★
()

бери пхп и не выёб****ся

anonymous
()
Ответ на: комментарий от yoghurt

>Вот в Smalltalk

Smalltalk — штука динамическая. С этим там как раз все нормально. И рефлексия там имеется «из коробки». Но, увы, все зохавал C++.

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

Macil ★★★★★
() автор топика
Ответ на: комментарий от Macil

>проверки типов

GOD, NO!!

Типы никто не проверяет (а зачем?). Объекту всегда отправят сообщение. Если он знает, как его обработать - всё ок. Не знает - вызовется заглушка doesNotUnderstand, по умолчанию выкидывающая эксепшн.

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

всегда есть соблазн учинить бестиповые ссылки

Вы так говорите, будто это что-то плохое. Это ж клево! Главное, чтобы объект по ссылке удовлетворял ожидаемому интерфейсу (или, как тут принято, «реализовывал такой-то протокол»).

Давеча как раз в списке рассылки обсуждали конфигурацию Seaside, там в определенном месте надо указывать конкретные классы для конкретных целей. Кому-то эт не понравилось, мол, что такое, класс по имени указывать, буе — так сразу и ответили, «хоть чорта лысого указывай, главное чтобы протокол 'instance creation' реализовывал и на new отвечать умел»

yoghurt ★★★★★
()

Макросы должны работать с АСТ. Если макросистема заставляет работать с алгебраическими типами данных или S-expr'ами, то такую макросистему следует выкинуть нахуй и больше никогда не вспоминать о существовании этого уебища.

anonymous
()
Ответ на: комментарий от Macil

>Я не очень четко выразился. Да во время исполнения форм происходят побочные эффекты, например defun своим побочным эффектом имеет создание соответствующией функции. Это понятно, и ожидаемо. Я имел ввиду другие побочные эффекты: работа с «миром».

Ну, некоторые библиотеки в макросах вполне себе пишут в файлы, что-то там генерируют, и т.п.
Например, cl-unicode, или closure-commons.

anonymous
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от anonymous

Какая разница, где они используются?
Хорошие макросы, как в CL это просто хуки для компилятора.

Остальные виды макросов - просто препроцессоры.

anonymous
()

А у меня вот такая мысль нарисовалась, об эквивалентности вывода типов и мультиметодов + динамической типизации.

Допустим, операция сложения возвращает не только сами значения, а тьюплы (значение,тип_результата). И тогда определяем обобщённый метод + (целое, вещественное) и + (вещественное, целое) возвращает тьюпл с тип_результата — вещественное; + (целое, целое) -> (сумма, тип_целое), + (строка, любой) -> (строка_конкатенация, строка) и т.п.

То есть: вместо вывода типов результатов ДО вычислений, делаем обобщёнными методами диспетчеризанию по типам на такую функцию, которая вычисляет тип результата ПОСЛЕ вычислений. Вычисления могут быть отложенными, фейковыми: то есть, один раз мультидиспетчеризацией вычисляем конкретную реализацию, она вычисляет простой подстановкой нужный тип, и раскрывается в макрос с кодом нужной конкретной, специализированной операции.

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

anonymous
()
Ответ на: комментарий от anonymous

> Макросы должны работать с АСТ.

ну да, ну да. А потом некоторые утверждаютъ, что такая макросистема не поддерживает произвольных вычислений. Что как-то странно, ведь могу же я слепить AST с произвольными вычислениями и вернуть вместо подстановки макроса?

anonymous
()
Ответ на: комментарий от anonymous

ну и естественно, операции замкнуть надо: + (число, число) -> тьюпл_с_типом, + (тьюпл_с_типом,число) = + (тьюпл_с_типом, тьюпл_с_типом) -> тьюпл_с_типом.

anonymous
()
Ответ на: комментарий от anonymous

> ну да, ну да. А потом некоторые утверждаютъ, что такая макросистема не поддерживает произвольных вычислений.

Поддерживает. Не вижу тут никакого противоречия. Почему над АСТ нельзя производить произвольные вычисления?

Что как-то странно, ведь могу же я слепить AST с произвольными вычислениями и вернуть вместо подстановки макроса?

Можешь. Другое дело, что если тебе приходится делать это часто, то макросистема - говно, которое не поддерживает никаких абстракций над представлением синтаксического дерева.

anonymous
()
Ответ на: комментарий от anonymous

> Какая разница, где они используются?

Хорошие макросы, как в CL это просто хуки для компилятора.

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

anonymous
()
Ответ на: комментарий от anonymous

> То есть, конпелятор с динамических типов сгенерирует программу со статическими, которые он вычислит не выведением, а чем-то вроде трассировки (не полной всей программы, а таких кусков с мультиметодами).

Алгоритм вывода типов именно так и работает.

anonymous
()
Ответ на: комментарий от anonymous

насколько я понял, алгоритм вывода типов используя переписывание термов по правилам преобразования типов операций (ещё нужны сигнатуры для функций, если в функции могут передаваться параметры по ссылке и изменять переменные программы) вычисляет код со статическими типами, который должен инлайниться вместо АСТ узлов.
То есть, если не можем вывести тип, то _I_ — затык.
А можно сделать что-то вроде JIT: один раз динамические типы и мультидиспетчерезация, и вычисление статически типизированного кода, второй раз — инлайн макросом вычисленного. То есть: не нужно выводить все типы, можно на третий раз опять свести к динамическим типам и т.п., так, чтобы статически типизированного кода становилось всё больше с каждым разом, а динамически — всё меньше. И программа каждый раз сама себя переписывает (а поинт в том что для компиляции хватит в основном 1-2 раз)

anonymous
()
Ответ на: комментарий от anonymous

И зачем это надо, если программа не будет статически чекаться до запуска?

anonymous
()
Ответ на: комментарий от yoghurt

>Объекту всегда отправят сообщение.

Ну, это и есть проверка типа, на самом деле. Ну назови его контрактом, если легче... В ООП, к сожалению, понятия «тип» «интерфейс» и «поведение» настолько переплетены, что при поминании одного из них, два других обычно подразумевают. Я пока еще не встречал ни одного ООП языка, где бы интерфейс был отделен от типа: всегда супертип, ИЧХ с обязательной практикой стирания до супертипа. Хотя _любая_ система поимеет массу плюшек, если будет работать с объектом конкретного типа, зная что он реализует такой-то интерфейс

Главное, чтобы объект по ссылке удовлетворял ожидаемому интерфейсу


А вот тут как раз нарисовывается проблема: чтобы узнать тип объекта по ссылке, ее нужно разыменовать. В ней же тип не хранится. Или тип объекта просто стерт до Object'а или еще какого-то класса на вершине иерархии. И вот такой, казалось бы простой штукой мы убили напрочь возможность любого статического анализа. И я до сих пор не могу понять зачем! Ради некой гибкости? Не подходит, мы также натолкнемся на несовместимость протоколов, но уже в рантайме. Ради повышения абстракции? Тоже не подходит! Потому что обнаружить несоответствия протоколов мы можем только методом проб и ошибок.

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

Macil ★★★★★
() автор топика
Ответ на: комментарий от anonymous

AST чего?
Если мы пишем eDSL, особенно eDSL типа loop, то «хуки для компилятора» в стиле CL это максимум, что нам может дать макросистема. Потому что AST такого eDSL может сильно не совпадать с AST хост-языка.

anonymous
()
Ответ на: комментарий от anonymous

> Если мы пишем eDSL, особенно eDSL типа loop, то «хуки для компилятора» в стиле CL это максимум

Чушь собачья.

Потому что AST такого eDSL может сильно не совпадать с AST хост-языка.

Это как? АСТ хост-языка - это именно то, что передается в макрос, так что eDSL ВСЕГДА представляет кусок АСТа языка. Он может, конечно, отличаться от, назовем так, «стандартного синтаксиса» - но при нормальной макросистеме это ничуть не помешает и кодеволкера писать не придется.

anonymous
()
Ответ на: комментарий от anonymous

Ну да, ну да.

Только почему если схемовые макросы так круты, то для написания банального loop требуется выкинуть все syntax-rules и иже с ними и писать «тупо как на CL»?
За примерами - в гугол.

anonymous
()
Ответ на: комментарий от anonymous

> Только почему если схемовые макросы так круты, то для написания банального loop требуется выкинуть все syntax-rules и иже с ними и писать «тупо как на CL»?

Что это за фантазии?

За примерами - в гугол.

И что писать? «loop для scheme»? Ну тогда результаты вполне ожидаемы - это будет поделие какого-нибудь общелиспера, который по-другому не умеет.

anonymous
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от Macil

> Я пока еще не встречал ни одного ООП языка, где бы интерфейс

был отделен от типа


Common Lisp же

таким подходом мы напрочь угробим возможность комбинации,

когда программа «склеивается» из кусков.



Бред.

archimag ★★★
()
Ответ на: комментарий от anonymous

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

http://www.lispworks.com/documentation/HyperSpec/Body/m_loop.htm#loop

пойдет? и там далее по ссылке более подробно:

http://www.lispworks.com/documentation/HyperSpec/Body/06_a.htm

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

Ну концептуально вот так оно примерно выглядит:
http://pastebin.com/X8b3HvRd
В общем, сперва описываем грамматику, потом протаскиваем все нужные формы, начиная с терминалов, до верхнего уровня, и потом кидаем все это в макрос/функцию, которая принимает все данные уже в удобоваримом представлении и выполняет саму кодогенерацию.

anonymous
()
Ответ на: комментарий от yoghurt

> Если же код таки запустили на выполнение, а объект сказал, что он ниалё - вывалимся в дебаггер

В программах уровня HelloWorld, можбыть, оно и почти сразу вывалится, а в более-менее сложном проекте это ваше «ниалё» случиться может уже на готовом продукте у клиента. Что настроения ни ему, ни разработчику, не добавит.

one_more_hokum ★★★
()
Ответ на: комментарий от one_more_hokum

> а в более-менее сложном проекте это ваше «ниалё» случиться может уже на готовом продукте у клиента. Что настроения ни ему, ни разработчику, не добавит.

Но почему-то в реальности это «ниале» редко случается и очень быстро исправляется. И оно ничем не лучше segmentation fault, access violation at address.

anonymous
()
Ответ на: комментарий от one_more_hokum

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

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

archimag ★★★
()
Ответ на: комментарий от archimag

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

Я-ж говорю: если программа сложнее уровня «HelloWorld», то такие случаи могут быть. Особенно на всяких динамических языках.

one_more_hokum ★★★
()
Ответ на: комментарий от archimag

А полное покрытие теми же тестами кода — мало того, что ресурсов и разработчика, и конпелятора подъедает, так ещё и не всегда случается.

one_more_hokum ★★★
()
Ответ на: комментарий от archimag

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

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

anonymous
()
Ответ на: комментарий от one_more_hokum

Тесты здесь не при чём. Ведь что получается, программист написал вызов у объекта метода, которого реально нет. И не смог этого сам обнаружить сразу же после написания кода. Т.е. просто этот код не запускал. А клиент запустил и обнаружил. Т.е. имеет место написание кода, без пробного запуска. Это полный дурдом. Для этого и придумали REPL, что бы постоянно запускать в нём код. В реальной практике разработки на динамических языках таких ситуаций я не встречал.

archimag ★★★
()

У подхода математического есть минусы.
Примеры: Миранда, РЕФАЛ, из более свежих несвежих - Clean...

Даже нет, не так. Достаю книжку «матлогика в программировании» и просто переписываю:
KRC, SASL, Miranda, Рефал, VLISP

Нутыпонел - основной минус подхода математического - ЯП создается математиками для математиков в итоге.

yaws
()
Ответ на: комментарий от archimag

> Для этого и придумали REPL, что бы постоянно запускать в нём код. А толку от постоянного запуска кода? У тебя просто висит несколько условий, которые при тестовом запуске false, в результате чего ветка с битым методом не выполняется. Или наоборот - у тебя определение функции в какой-то ветке, которая обычно выполняется в тестах, а потому все работает, но как только условие не будет выполнено - получим пиздец в рантайме.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.