LINUX.ORG.RU
 

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


0

1

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

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

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

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

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

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

ЗАСТАВЬ КОМПЬЮТЕР ПОЛИВАТЬ ОГОРОД

автоматизация своими руками: электроприборы под контролем компьютера
beware of programmers who carry screwdrivers!
http://www.unicontrollers.com/products/unc01x

[#]  
geekless

В чём вопрос-то?

** ()
[#]  

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

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

()
[#]  

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

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

** ()
[#]  
shty
>>-----Цитата---->>

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

<<-----Цитата----<<

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

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

*** ()
[#] Ответ на: комментарий от keeHa7er 10.01.2011 21:21:05  

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

Знаем мы этих «нормальных» людей ;)

**** ()
[#]  

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

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

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

anonymous ()
[#] Ответ на: комментарий от Macil 10.01.2011 21:44:19  

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

()
[#] Ответ на: комментарий от keeHa7er 10.01.2011 21:49:12  

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

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

**** ()
[#] Ответ на: комментарий от anonymous 10.01.2011 21:46:11  

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

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

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


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

**** ()
[#] Ответ на: комментарий от keeHa7er 10.01.2011 21:49:12  
geekless

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

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

** ()
[#] Ответ на: комментарий от Macil 10.01.2011 22:01:48  

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

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

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


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

anonymous ()
[#]  

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

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

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


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

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

**** ()
[#]  
yoghurt

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

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

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

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

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

***** ()
[#]  

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

anonymous ()
[#] Ответ на: комментарий от yoghurt 11.01.2011 0:27:34  

>Вот в Smalltalk

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

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

**** ()
[#] Ответ на: комментарий от Macil 11.01.2011 0:36:43  

ЗЫ: А вообще есть MetaLua :)

**** ()
[#] Ответ на: комментарий от Macil 11.01.2011 0:36:43  
yoghurt

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

GOD, NO!!

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

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

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

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

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

***** ()
[#] Ответ на: комментарий от Macil 11.01.2011 0:36:43  
yoghurt

Хотя стоп. Что такое бестиповые ссылки?

***** ()
[#]  

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

anonymous ()
[#] Ответ на: комментарий от Macil 10.01.2011 22:48:11  

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 8:39:31  

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 11:51:05  

> что в контексте текущей темы

Кстати, а о чём вообще разговор то?

** ()
[#] Ответ на: комментарий от archimag 11.01.2011 12:14:40  

> Haskell & Lisp Нутыпонелда.

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 11:51:05  

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

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

anonymous ()
[#]  

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

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

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 6:29:09  

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 14:57:19  

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 15:11:21  

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

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

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 14:10:23  

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 14:57:19  

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 15:26:39  

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 16:15:56  

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

anonymous ()
[#] Ответ на: комментарий от yoghurt 11.01.2011 1:11:28  

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

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

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


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

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

**** ()
[#] Ответ на: комментарий от anonymous 11.01.2011 15:20:13  

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 16:54:58  

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

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

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 17:08:15  

Ну да, ну да.

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 17:34:12  

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

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

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

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

anonymous ()
[#] Ответ на: комментарий от anonymous 11.01.2011 17:34:12  

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

anonymous ()
[#] Ответ на: комментарий от Macil 11.01.2011 16:20:33  

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


Common Lisp же

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

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


Бред.

** ()
[#] Ответ на: комментарий от anonymous 11.01.2011 17:43:38  
korvin_

> Вообще, кстати, если дашь линк на спеку 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_ 11.01.2011 20:16:04  

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

anonymous ()
[#] Ответ на: комментарий от yoghurt 11.01.2011 1:11:28  

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

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

* ()
[#] Ответ на: комментарий от one_more_hokum 12.01.2011 9:43:13  

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

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

anonymous ()
[#] Ответ на: комментарий от one_more_hokum 12.01.2011 9:43:13  

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

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

** ()
[#] Ответ на: комментарий от archimag 12.01.2011 12:01:02  

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

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

* ()
[#] Ответ на: комментарий от archimag 12.01.2011 12:01:02  

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

* ()
[#] Ответ на: комментарий от archimag 12.01.2011 12:01:02  

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

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

anonymous ()
[#] Ответ на: комментарий от one_more_hokum 12.01.2011 12:18:40  

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

** ()
[#]  

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

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

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

* ()
[#] Ответ на: комментарий от archimag 12.01.2011 13:02:59  

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

anonymous ()