Не сразу решился написать это сообщение, т.к. подозреваю что часть народа воспримет его как повод для "войн". Но я всё же надеюсь что большая часть читателей (а особенно модераторы ) поймут что это истинные эмоции, а не "провокация войны" какая-то.
Т.к. об этом всё равно спросят, то напиши сразу: мои (и моих команд) основные инструменты в реальной работе это C++ (11-ый) и Python (2-ой). Естественно для всяких специальных целей применяются другие языки (всякие Javascript, Assembler, XSLT, Lua, SQL и т.п.). Кроме этого есть ещё набор языков, которые я (уже только лично) смотрел "для фана" (Lisp, Prolog, Smalltalk, OCaml, Erlang, D) и набор языков, которые когда-то изучал, но потом выкинул на помойку за ненадобностью (Pascal, Java, C#, PHP).
Haskell'я, как видно, тут нет — как-то так сложилось. А при этом я слышал о нём очень много всего от окружающих. Из этих слухов у меня сформировалось следующее впечатление:
— язык мегасложный, только для самых самых, но зато кто освоит, может творить невероятные вещи, недоступные в других языках
— язык находящийся на острие прогресса, так сказать идеальный функциональный язык, впитавшией в себя всё лучшее из всех остальных
— т.к. поддерживается статическая типизация, компиляция в native и были слухи о хорошем быстродействие в некоторых случаях, то я уже даже подумывал что это возможная замена наших реальных инструментов (хотя пока только D рассматривал в этом смысле).
И вот у меня нашлось свободное время, когда я решился взяться за Haskell. Прочитал несколько вводных статей, скачал среду, написал несколько программок. Одну даже с GUI (на базе знакомой C++ библиотечки, к которой у Haskell есть биндинг) ради смеха. И захотел поделиться с вами ощущениями...
Если очень кратко, то "я в шоке".
А теперь подробнее... Для начала скажу что ничего сложного в нём так и не обнаружил — довольно простой и логичный язык. И это будет единственный положительный отзыв...
Ну а далее я просто увидел набор всех тех же самых вещей из своих рабочих языков (даже не из тех модных, которые "для фана"!), только записаных более неудобным (хотя и более кратким) способом. Те же генераторы, условия, шаблоны, только под другими названиями и с синтаксисом значками, а не словами. Разве что прямого аналога сечений и сопоставления с образцом вроде нет. Но сечение — это довольно редкая потребность на практике, которая к тому же вполне реализуема через что-то типа функтора. Ну а сопоставление с образцом оказалось совсем слабым (по сравнению с Прологом) — не намного сильнее какого-нибудь if/else...
И это я ещё не говорил про монады! Тут же наверное все знают что в Haskell по сути два отдельных языка. Он сам и ещё один встроеный, жутко кривой, по сути императивный язык для всего системного. В начале мне подобная идея показалась интересной — отделим весь "сомнительный" код от внутренней логики и т.п... Но когда я посмотрел (в примерах программ с GUI) к чему это приводит на практике... Ну вы наверное сами догадались, да? Вся программа — это одна "мегамонада". А самого "красивого" Хаскеля по сути вообще нет — только кривой код. А нафига тогда это вообще надо? Уж проще на Питоне, где и императивный и функционаьный код записываются в едином удобном стиле. И кстати монады огорчили ещё кое-чем. Зная что Хаскель компилируется в native, я надеялся что возможно будет путь для прямого вызова API OS. И он действительно по сути есть, но опять же только в этой кривой пародии на императивный язык. Конечно я понимаю что по другому и нельзя было (если хотим сохранять "чистоту"), но я надеялся на некое чудо. )))
Есть ещё конечно действительно особая вещь — ленивость. Но сценарии её использования действительно интересны лишь на различных абстракциях (типа бесконечных структур данных), а в остальных случаях неленивые решения ничем не уступают. В общем саму возможность ленивых вычислений на уровне языка безусловно записываем в плюс, но она даже близко не окупает остальные минусы языка.
В общем в итоге всего этого Haskell сполз для меня с мифического пьедестала (да, каюсь, это я сам воздвиг его, но в основном на основе окружающих слухов) супер языка и превратился в неудобную экспериментальную поделку... Теперь даже и не знаю где искать новую "серебряную пулю" для фана. Ну а о замене основных инструментов похоже пока даже и речи нет...
Ну что вам сказать... Вы не правы, потому что судите о Хаскеле исключительно с высоты своего опыта программирования на нем. И ваш опыт чрезмерно мал, чтобы ваша оценка была объективной и слишком эмоциональной. Единственное, в чем вы правы: язык, действительно, не такой сложный, как его малюют. Но на нем, как и на любом другом языке, очень легко написать г... плохой код, особенно если начать с GUI и не осознать, что такое "программирование в чистых функциях исключительно в функциональном стиле". Обычно весь предыдущий императивный опыт перекашивает программиста в сторону императивности. Программист неосознанно ищет в монаде IO то, к чему привык; конечно же, не находит, — и обламывается. И теперь все монады кажутся ему отдельным встроенным языком, да еще и невозможно кривым. А на самом деле более изящной вещи в программировании еще стоит поискать. Я не знаю, кого вы имели в виду под словами:
_>И это я ещё не говорил про монады! Тут же наверное все знают что в Haskell по сути два отдельных языка. _>Ну вы наверное сами догадались, да? Вся программа — это одна "мегамонада".
Ничего подобного. при правильном подходе к проектированию программы, любая монада прекрасно вписывается в остальной код и неотличима от него, потому как там все логично. Даже так напугавшая вас монада IO органично может быть частью чего-то большего. Просто смотрите достойные примеры, а изучение Haskell лучше начинать не с монад, а с принципов чистого функционального программирования. И тогда вы поймете, как разделить код на слои, чтобы не было одной мегамонады.
_>Те же генераторы, условия, шаблоны, только под другими названиями и с синтаксисом значками, а не словами.
О каких таких генераторах, шаблонах и условиях "с синтаксисом значками" идет речь? Вообще, тут в контексте беседы стоит упомянуть APL, и на том холиварную тему закрыть. (Но в скобках замечу, что ничто не мешает в Haskell делать синтаксис как со значками, так и словами. Зависит от предпочтений программиста.)
_>Ну а сопоставление с образцом оказалось совсем слабым (по сравнению с Прологом) — не намного сильнее какого-нибудь if/else..
Это заявление вообще является показателем, что вам не известны все возможности сопоставления с образцом.
Наконец, эта фраза:
_>А самого "красивого" Хаскеля по сути вообще нет — только кривой код.
как и все сообщение, является лишь вашим желанием похоливарить, ибо Haskell тут ни при чем. А при чем — исключительно руки, которые и пишут код.
Здравствуйте, alex_public, Вы писали:
_>- язык мегасложный, только для самых самых, но зато кто освоит, может творить невероятные вещи, недоступные в других языках
Когда я начал читать этот пост, подумал "ну вот, еще один всё понял" и хотел уже написать что-то вроде: "да, на самом деле Хаскель — довольно простой язык, про его сложность пишут в основном тролли с Лоа, и ничего `невероятного' там по сути нет, просто позволяет немного лучше структурировать код." Но когда дочитал до конца, сам начал в себе сомневаться. Похоже Хаскель действительно "affect the way you think about programming", и, похоже, действительно это недоступно ни в одном другом из перечисленных языков. Рекомендую почитать здесь http://www.paulgraham.com/avg.html про "Blub Paradox". Там про Лисп, но суть феномена отражена верно.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Похоже Хаскель действительно "affect the way you think about programming"
У меня так и было. Haskell серьезно изменил мое программистское мышление и повернул его в сторону, о которой я даже не подозревал. И это было приятное чувство, ни с чем не сравнимое.
Здравствуйте, alex_public, Вы писали:
_>А теперь подробнее... Для начала скажу что ничего сложного в нём так и не обнаружил — довольно простой и логичный язык.
Это правда. В то же время — не для всех "логичный" означает "простой", для многих всё наоборот. Отсюда и миф.
_>Те же генераторы, условия, шаблоны,
А нельзя поподробнее? Потому что пока я понимаю так:
1) Шаблоны — если имеются в виду шаблоны C++, то ничего близкого в Хаскеле нет. Шаблоны плюсов — это макросистема, откуда вытекают и их преимущества и их ограничения. Гораздо ближе дженерики из Java или C# — это, скажем так, подмножество хаскельного параметрического полиморфизма.
2) Условия — вообще не понял, о чём речь.
3) Генераторы — ничего похожего, опять-таки, в Хаскеле нет. Если речь идёт о чём-то вроде foldl, то это гораздо более мощная конструкция.
Я вполне допускаю, что понял неправильно, и прошу пояснить.
_>Ну а сопоставление с образцом оказалось совсем слабым (по сравнению с Прологом) — не намного сильнее какого-нибудь if/else...
Это сомнительное утверждение. Единственное преимущество Пролога в этом смысле (ЕМНИМЭ) — это возможность использовать две одинаковых переменных (что-нибудь вроде [X|X|R]). В Хаскеле это эмулируется как (x : x' : rest) | x' == x. Причина этому — то, что в прологе любые термы можно сравнить на равенство; в Хаскеле нельзя. Это, в свою очередь, следствие того, что в Хаскеле терм может оказаться, например, функцией — а функции ну никак не сравниваются. Поэтому Хаскель не предполагает по умолчанию, что к переменным паттерна применимо свойство "такой же, как этот".
_>И это я ещё не говорил про монады! Тут же наверное все знают что в Haskell по сути два отдельных языка.
Это неверно. Монады — всего лишь один из множества полезных классов Хаскеля. Отличаются они только тем, что для них предусмотрен лёгкий синтаксический сахар. Но пользоваться им необязательно.
_>Но когда я посмотрел (в примерах программ с GUI) к чему это приводит на практике...
Ну, если вся программа — только лишь GUI, то удивляться нечему. А в "примерах", скорее всего, так и есть. Это то самое "отделение сомнительной части от внутренней логики", только в учебных примерах по GUI-библиотекам внутренней логики, собственно, нет.
_>я надеялся что возможно будет путь для прямого вызова API OS. И он действительно по сути есть, но опять же только в этой кривой пародии на императивный язык. Конечно я понимаю что по другому и нельзя было (если хотим сохранять "чистоту"), но я надеялся на некое чудо. )))
Чудес не бывает. А хаскельный FFI считается одним из наиболее продуманных и удобных.
_>Но сценарии её использования действительно интересны лишь на различных абстракциях (типа бесконечных структур данных), а в остальных случаях неленивые решения ничем не уступают.
Это тоже не так. На самом деле, практически во всех энергичных (== во всех остальных) языках есть элементы ленивости — потому что без них никак. Как минимум, конструкция if...then...else является ленивой вообще везде. Операторы "и" и "или" практически везде тоже ленивы — не вычисляют второй аргумент, если первого достаточно. А, скажем, нормальный Y-комбинатор вообще в энергичных языках не встречается, да и не может.
Здравствуйте, MigMit, Вы писали:
MM>1) Шаблоны — если имеются в виду шаблоны C++, то ничего близкого в Хаскеле нет. Шаблоны плюсов — это макросистема, откуда вытекают и их преимущества и их ограничения. Гораздо ближе дженерики из Java или C# — это, скажем так, подмножество хаскельного параметрического полиморфизма.
template haskell?
_>>И это я ещё не говорил про монады! Тут же наверное все знают что в Haskell по сути два отдельных языка.
MM>Это неверно. Монады — всего лишь один из множества полезных классов Хаскеля. Отличаются они только тем, что для них предусмотрен лёгкий синтаксический сахар. Но пользоваться им необязательно.
Синтаксически ещё и стрелки подслащены.
Вы совсем не упомянули спарки (Control.Parallel par, pseq). Разве это не круто?
Re[2]: Мифический Haskell
От:
Аноним
Дата:
16.02.12 08:20
Оценка:
Здравствуйте, MigMit, Вы писали:
_>>Ну а сопоставление с образцом оказалось совсем слабым (по сравнению с Прологом) — не намного сильнее какого-нибудь if/else...
MM>Это сомнительное утверждение. Единственное преимущество Пролога в этом смысле (ЕМНИМЭ) — это возможность использовать две одинаковых переменных (что-нибудь вроде [X|X|R]). В Хаскеле это эмулируется как (x : x' : rest) | x' == x.
Все-таки в Прологе полноценная унификация, и она действительно мощнее паттерн-матчинга.
Здравствуйте, alex_public, Вы писали:
_> Но когда я посмотрел (в примерах программ с GUI) к чему это приводит на практике... Ну вы наверное сами догадались, да? Вся программа — это одна "мегамонада".
Спрашивается — а на кой хрен вообще программировать GUI на Хаскелле? Да еще используя биндинги к чужеродным библиотекам, а не родную для Хаскелля идеологию functional reactive programming? Для GUI есть всякие там Tcl/Tk, идеально под эту задачу заточенные.
_> А самого "красивого" Хаскеля по сути вообще нет — только кривой код. А нафига тогда это вообще надо? Уж проще на Питоне, где и императивный и функционаьный код записываются в едином удобном стиле.
Функциональный код на Питоне?!? Не смешите мои панталоны! Какой такой "функциональный" код на языке, где expression и statement это разные вещи?
_>В общем в итоге всего этого Haskell сполз для меня с мифического пьедестала (да, каюсь, это я сам воздвиг его, но в основном на основе окружающих слухов) супер языка и превратился в неудобную экспериментальную поделку... Теперь даже и не знаю где искать новую "серебряную пулю" для фана.
Человеку, программирующему гуйню, ничего сложнее Tcl и не нужно по определению. Меня, честно говоря, сильно удивляет, как это жирный и сложный C++ (он на порядок сложнее того же Хаскелля) оказался в списке ваших инструментов. Не гуйнячий же язык, совсем не гуйнячий.
Здравствуйте, http://palm-mute.livejournal.com/, Вы писали:
HPM>Здравствуйте, MigMit, Вы писали:
HPM>Все-таки в Прологе полноценная унификация, и она действительно мощнее паттерн-матчинга.
А сужение (narrowing) мощнее унификации?
Здравствуйте, alexlz, Вы писали:
A>Здравствуйте, MigMit, Вы писали:
MM>>1) Шаблоны — если имеются в виду шаблоны C++, то ничего близкого в Хаскеле нет. Шаблоны плюсов — это макросистема, откуда вытекают и их преимущества и их ограничения. Гораздо ближе дженерики из Java или C# — это, скажем так, подмножество хаскельного параметрического полиморфизма. A>template haskell?
Поправка принимается. Я о нём забыл (хотя можно, конечно, начать придираться и говорить, что в стандарт оно, дескать, не входит, и т.п... но заниматься иезуитством не будем). Однако, это не то, что я посоветую использовать новичку. Да и профи тоже.
_>>>И это я ещё не говорил про монады! Тут же наверное все знают что в Haskell по сути два отдельных языка.
MM>>Это неверно. Монады — всего лишь один из множества полезных классов Хаскеля. Отличаются они только тем, что для них предусмотрен лёгкий синтаксический сахар. Но пользоваться им необязательно. A>Синтаксически ещё и стрелки подслащены.
Ну, строго говоря, да. Но и только. Функторы не подслащены, аппликативные функторы тоже не подслащены, и т.п.
Здравствуйте, http://palm-mute.livejournal.com/, Вы писали:
HPM>Все-таки в Прологе полноценная унификация, и она действительно мощнее паттерн-матчинга.
Здравствуйте, Аноним, Вы писали:
А> Спрашивается — а на кой хрен вообще программировать GUI на Хаскелле?
А как же вкусные fudgets, formlets, прикручивание представлений с помошью тайпклассов, построение композитных интерфейсов с помошью комбинаторов и прочие ништяки?
А> Да еще используя биндинги к чужеродным библиотекам, а не родную для Хаскелля идеологию functional reactive programming?
Оно не исключает. Всё равно таргет — какая-нибудь нативная либа, сверху может быть прикручен frp.
А> Человеку, программирующему гуйню, ничего сложнее Tcl и не нужно по определению.
Ну нет. Вон даже в C# функциональную гуйню пытаются тянуть: http://linquid.codeplex.com/ Потому что действительно удобно. проблема написания гуйни на Хаскелле не в том, что `нинужно', а в том, что нормальных либ нет и все хаскеллевские GUI-фреймворки протухают еще до того, как их успевают дописать. А так, если бы была либа, которой бы постоянно занимались, было бы очень хорошо, конечно.
Здравствуйте, MigMit, Вы писали:
MM>1) Шаблоны — если имеются в виду шаблоны C++, то ничего близкого в Хаскеле нет.
Не вижу существенной разницы. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.78.2151. ИМХО разница в основном в синтаксисе и в том, что на шаблонах скорее всего не сделать что-нибудь вроде (a ~ b) => ... То как это внутри реализовано — так вообще детали и в хаскеле более менделенная реализация, хотя можно было бы сделать и быструю как в крестах (путём запрета раздельной компиляции и увеличения кол-ва сгенерированного кода).
Я лично просто не могу воспринимать язык с таким синтаксисом. Когда я вижу код на Хаскеле, я понимаю, что я в этом разбираться не хочу и не буду.
__________________________________
Не ври себе.
Re[4]: Мифический Haskell
От:
Аноним
Дата:
16.02.12 09:45
Оценка:
Здравствуйте, MigMit, Вы писали:
MM>Здравствуйте, http://palm-mute.livejournal.com/, Вы писали:
HPM>>Все-таки в Прологе полноценная унификация, и она действительно мощнее паттерн-матчинга.
MM>М-м-м... Care to elaborate?
А чего тут elaborate?
$ swipl
1 ?- foo(X, 2, 3) = foo(1, 2, Y).
X = 1,
Y = 3.
2 ?- X=Y, Y=Z, Z=42.
X = 42,
Y = 42,
Z = 42.
Re[4]: Мифический Haskell
От:
Аноним
Дата:
16.02.12 09:46
Оценка:
Здравствуйте, alexlz, Вы писали:
A>Здравствуйте, http://palm-mute.livejournal.com/, Вы писали:
HPM>>Здравствуйте, MigMit, Вы писали:
HPM>>Все-таки в Прологе полноценная унификация, и она действительно мощнее паттерн-матчинга. A>А сужение (narrowing) мощнее унификации?
Здравствуйте, Artifact, Вы писали:
A>Я лично просто не могу воспринимать язык с таким синтаксисом. Когда я вижу код на Хаскеле, я понимаю, что я в этом разбираться не хочу и не буду.
Старнное заявление в разделе /decl. Вроде у всех более-менее декларативных и функциональны эзыков синтаксис весьма далёк от сишкоподобного. Если, конечно, это не эрзацы вроде Скалы или Менерле.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Здравствуйте, MigMit, Вы писали:
MM>>1) Шаблоны — если имеются в виду шаблоны C++, то ничего близкого в Хаскеле нет.
ПМ>Не вижу существенной разницы. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.78.2151. ИМХО разница в основном в синтаксисе и в том, что на шаблонах скорее всего не сделать что-нибудь вроде (a ~ b) => ... То как это внутри реализовано — так вообще детали и в хаскеле более менделенная реализация, хотя можно было бы сделать и быструю как в крестах (путём запрета раздельной компиляции и увеличения кол-ва сгенерированного кода).
Разница в том, что в Хаскеле — нормальный ПП (с явной передачей словаря), а в плюсах — макросистема.
Здравствуйте, http://palm-mute.livejournal.com/, Вы писали:
HPM>Здравствуйте, MigMit, Вы писали:
MM>>Здравствуйте, http://palm-mute.livejournal.com/, Вы писали:
HPM>>>Все-таки в Прологе полноценная унификация, и она действительно мощнее паттерн-матчинга.
MM>>М-м-м... Care to elaborate?
HPM>А чего тут elaborate?
А, в этом смысле. Понял. Но тут, скорее, играет "двустороннесть" правил пролога, это не относится именно к ПМ.
T>Вы совсем не упомянули спарки (Control.Parallel par, pseq). Разве это не круто?
В теории — круто. На практике — полный отстой. В моей практике эти ваши стратегии и прочие pseq доблестно глючили (скажем, map обратабывал только несколько первых элементов списка, а весь хвост пропадал) и почти всегда были намного медленней чем элементарное линейное выполнение. И это несмотря на то, что вычисления должны были бы параллелиться легко и непринужденно.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Здравствуйте, MigMit, Вы писали:
MM>>Разница в том, что в Хаскеле — нормальный ПП (с явной передачей словаря), а в плюсах — макросистема.
ПМ>Это особенность реализации. Готов поспорить на выпивку, что даже в стандарт не обязывает его реализовывать через передачу словаря (стандарт не читал :)
Здравствуйте, Rtveliashvili Denys, Вы писали:
RD>В моей практике эти ваши стратегии и прочие pseq доблестно глючили (скажем, map обратабывал только несколько первых элементов списка, а весь хвост пропадал)
Здравствуйте, alex_public, Вы писали:
_>идеальный функциональный язык, впитавшией в себя всё лучшее из всех остальных
К сожалению, хаскель не является идеальным ФЯ. Это сейчас единственный декларативный ФЯ, который можно назвать "реальным". Остальные либо мертвы, никогда и не жили, либо являются обычными императивными языками, на которые навешены пара-тройка функциональных фич. Последовательно писать функциональный код на таких языках либо вовсе не получается, либо такой код оказывается неприемлимо тормозным или вовсе не работающим. В такой ситуации у того, кто считает направление интересным просто нет выбора: хаскель как ФЯ безальтернативен, и уж какой есть — такой есть.
Утешает только то, что он сейчас прогрессирует заметными темпами и стал существенно лучше за довольно таки небольшое время.
Но я всерьез опасаюсь, что это не продлиться долго: хаскель становится популярнее, а популярность будет консервировать язык, его развитие замедлится, а потом и вовсе остановится. Скорее всего, задолго до того, как он приблизится к идеалу функционального языка.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Судя по всему, ты написал на хаскеле дубовый императивный GUI-код, совершенно такой же, как на питоне. Вот по-этому разницы особой нет. Попробуй пописать что-нить более существенное, чем дергание сишных библиотечных ф-ий.
_>Т.к. об этом всё равно спросят, то напиши сразу: мои (и моих команд) основные инструменты в реальной работе это C++ (11-ый) и Python (2-ой). Естественно для всяких специальных целей применяются другие языки (всякие Javascript, Assembler, XSLT, Lua, SQL и т.п.). Кроме этого есть ещё набор языков, которые я (уже только лично) смотрел "для фана" (Lisp, Prolog, Smalltalk, OCaml, Erlang, D) и набор языков, которые когда-то изучал, но потом выкинул на помойку за ненадобностью (Pascal, Java, C#, PHP).
После lisp/prolog/ocaml с хаскеллом вообще особых сложностей быть не должно.
_>Haskell'я, как видно, тут нет — как-то так сложилось. А при этом я слышал о нём очень много всего от окружающих. Из этих слухов у меня сформировалось следующее впечатление: _>- язык мегасложный, только для самых самых, но зато кто освоит, может творить невероятные вещи, недоступные в других языках
Миф про мегасложность был распространен пару лет назад несколькими (одним?) крикливыми анонимусами. Про самых-самых и невероятные вещи -- видимо -- последствия этого мифа.
_>- язык находящийся на острие прогресса, так сказать идеальный функциональный язык, впитавшией в себя всё лучшее из всех остальных
На острие какая-нить agda. Haskell-у уже 20 лет, хотя он не потерял гибкость и развивается. Хаскелл -- такой исследовательский проект коммерческого качества.
_>- т.к. поддерживается статическая типизация, компиляция в native и были слухи о хорошем быстродействие в некоторых случаях, то я уже даже подумывал что это возможная замена наших реальных инструментов (хотя пока только D рассматривал в этом смысле).
А что у вас за задачи, что требуется быстродействие и при этом используется питон?
_>И вот у меня нашлось свободное время, когда я решился взяться за Haskell. Прочитал несколько вводных статей, скачал среду, написал несколько программок. Одну даже с GUI (на базе знакомой C++ библиотечки, к которой у Haskell есть биндинг) ради смеха. И захотел поделиться с вами ощущениями...
_>Если очень кратко, то "я в шоке".
Бывает )
_>А теперь подробнее... Для начала скажу что ничего сложного в нём так и не обнаружил — довольно простой и логичный язык. И это будет единственный положительный отзыв...
_>Ну а далее я просто увидел набор всех тех же самых вещей из своих рабочих языков (даже не из тех модных, которые "для фана"!), только записаных более неудобным (хотя и более кратким) способом. Те же генераторы, условия, шаблоны, только под другими названиями и с синтаксисом значками, а не словами. Разве что прямого аналога сечений и сопоставления с образцом вроде нет. Но сечение — это довольно редкая потребность на практике, которая к тому же вполне реализуема через что-то типа функтора. Ну а сопоставление с образцом оказалось совсем слабым (по сравнению с Прологом) — не намного сильнее какого-нибудь if/else...
"генераторы, условия, шаблоны" -- это что?
Сопоставление с образцом конечно слабее унификации (но мощнее серии if/then/else). Только вот в хаскеле можно проверить полноту сопоставления (и вообще правильность типов), а в прологе нет. Также со всякими ViewPatterns/PatternGuards/RecordWildcards сопоставление с образцом уже не кажется таким слабым.
Сечения используются довольно часто. Ты попробуй попиши код с ф-иями высшего порядка и поймешь.
_>И это я ещё не говорил про монады! Тут же наверное все знают что в Haskell по сути два отдельных языка. Он сам и ещё один встроеный, жутко кривой, по сути императивный язык для всего системного. В начале мне подобная идея показалась интересной — отделим весь "сомнительный" код от внутренней логики и т.п... Но когда я посмотрел (в примерах программ с GUI) к чему это приводит на практике... Ну вы наверное сами догадались, да? Вся программа — это одна "мегамонада". А самого "красивого" Хаскеля по сути вообще нет — только кривой код. А нафига тогда это вообще надо? Уж проще на Питоне, где и императивный и функционаьный код записываются в едином удобном стиле. И кстати монады огорчили ещё кое-чем. Зная что Хаскель компилируется в native, я надеялся что возможно будет путь для прямого вызова API OS. И он действительно по сути есть, но опять же только в этой кривой пародии на императивный язык. Конечно я понимаю что по другому и нельзя было (если хотим сохранять "чистоту"), но я надеялся на некое чудо. )))
Монады -- не встроенный язык. Это type class с небольшим сахаром для одной ф-ии. И служат они не только для IO. Хотя в дубовом IO (последовательный вызов библиотечных ф-ий) без использования ФВП и прочих хаскельныйх фишек могут выглядеть даже похуже питона. Только такой код на хаскеле почти не пишется (если только в примерах работы с GUI библиотеками .
foreign вызовы, если они чистые, могут быть и не в монаде. FFI у хаскелла вообще один из лучших, что я видел.
А пример записи императивного и функционального кода в питоне в едином стиле можно? В питоне вообще expression и statement -- разные вещи.
Более того, монада тоже чисто функциональная.
do x <- y; z -- это то же самое что y >>= \ x -> z
_>Есть ещё конечно действительно особая вещь — ленивость. Но сценарии её использования действительно интересны лишь на различных абстракциях (типа бесконечных структур данных), а в остальных случаях неленивые решения ничем не уступают. В общем саму возможность ленивых вычислений на уровне языка безусловно записываем в плюс, но она даже близко не окупает остальные минусы языка.
Ага, а еще ленивые вычисление позволяют тебе писать код в любом порядке, как удобно, а не как будет вычисляться. Т.е. если ты написал x = y where y = x, ты уже используешь ленивые вычисления.
А еще в хаскеле есть type classes/type functions/GADTs и еще куча плюшек, которых почти нигде больше нет.
_>В общем в итоге всего этого Haskell сполз для меня с мифического пьедестала (да, каюсь, это я сам воздвиг его, но в основном на основе окружающих слухов) супер языка и превратился в неудобную экспериментальную поделку... Теперь даже и не знаю где искать новую "серебряную пулю" для фана. Ну а о замене основных инструментов похоже пока даже и речи нет...
Для GUI посмотри на Tcl/Tk. Удобнее вроде пока ничего не придумали (правда выглядит плохо, но все равно стоит ознакомиться). Ну и попробуй напиши какую-нить логику к твоему GUI, может мнение о хаскелле поменяется )
Здравствуйте, MigMit, Вы писали:
MM>Здравствуйте, Паблик Морозов, Вы писали:
ПМ>>Здравствуйте, MigMit, Вы писали:
MM>>>1) Шаблоны — если имеются в виду шаблоны C++, то ничего близкого в Хаскеле нет.
ПМ>>Не вижу существенной разницы. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.78.2151. ИМХО разница в основном в синтаксисе и в том, что на шаблонах скорее всего не сделать что-нибудь вроде (a ~ b) => ... То как это внутри реализовано — так вообще детали и в хаскеле более менделенная реализация, хотя можно было бы сделать и быструю как в крестах (путём запрета раздельной компиляции и увеличения кол-ва сгенерированного кода).
MM>Разница в том, что в Хаскеле — нормальный ПП (с явной передачей словаря), а в плюсах — макросистема.
А что такое ПП? И, чтоб 2 раза не вставать уже — что такое "(a ~ b) => ..."?
Интересно потестить возможности плюсовых шаблонов.
_> Разве что прямого аналога сечений и сопоставления с образцом вроде нет.
про какие сечения идет речь? (АОР?)
вообще единственное что в хаскеле хорошее -- это тайпклассы (ну и может по мелочам паттерн матчинг и может какие расширения, но я их не знаю)
крики про мега-крутой язык, как всегда, сильно преувеличены
полезность ФП проявится в основном при написании thread-safe кусочков кода (и да, там, если припрет, *придется* повторять весь этот хаскельный выпендреж в стиле эмуляции double linked list и прочих извращений) -- а вся остальная программа, как и раньше, будет императивная
короче, знание ФП потребуется почти лишь только тем, кто будет писать потокобезопасные структуры данных, да и то, возможно там будет использоваться вовсе не ФП, а доказательствами того, что императивные по сути действия снаружи выглядят одинаков независимо от того, как спланировались нити и как данные раскидались по кэшам процессоров, памяти и т.д.
Здравствуйте, graninas, Вы писали:
G>Ну что вам сказать... Вы не правы, потому что судите о Хаскеле исключительно с высоты своего опыта программирования на нем. И ваш опыт чрезмерно мал, чтобы ваша оценка была объективной и слишком эмоциональной...
Эх, я же специально привёл список знакомых мне языков, что бы было ясно что я вполне в курсе функционального стиля.
G>Ничего подобного. при правильном подходе к проектированию программы, любая монада прекрасно вписывается в остальной код и неотличима от него, потому как там все логично. Даже так напугавшая вас монада IO органично может быть частью чего-то большего. Просто смотрите достойные примеры, а изучение Haskell лучше начинать не с монад, а с принципов чистого функционального программирования. И тогда вы поймете, как разделить код на слои, чтобы не было одной мегамонады.
Речь не о том. Я имел в виду, что когда я заглянул в реальные примеры кода (не своего), то увидел что там кода "внутри монад" больше по объёму чем вне их. Соответственно возникает вопрос: "а нафига тогда так отделять было?". Ведь цена такого отдельния тоже есть — не слишком удобный синтаксис внутри монад.
G>О каких таких генераторах, шаблонах и условиях "с синтаксисом значками" идет речь? Вообще, тут в контексте беседы стоит упомянуть APL, и на том холиварную тему закрыть. (Но в скобках замечу, что ничто не мешает в Haskell делать синтаксис как со значками, так и словами. Зависит от предпочтений программиста.)
Генераторы это [ f x | x <- xs ]
В Питоне записываем как что-то типа [f(x) for x in xs]
Ну и дальше там есть аналоги буквальные на guards и так далее.
G>Это заявление вообще является показателем, что вам не известны все возможности сопоставления с образцом.
Хыхы, а вам известны все возможности сопоставления с образцом в Прологе? )
G>как и все сообщение, является лишь вашим желанием похоливарить, ибо Haskell тут ни при чем. А при чем — исключительно руки, которые и пишут код.
Ну пока что я разглядывал чужой код на Haskell в основном, а не свой. )))
Здравствуйте, alex_public, Вы писали:
_>Эх, я же специально привёл список знакомых мне языков, что бы было ясно что я вполне в курсе функционального стиля.
Для справки, в вашем списке языков примерно 0 наименований, которые подготовили бы вас к реальному функциональному стилю в котором пишут на хаскеле и примерно 12 наименований, которые бы затруднили понимание и написание кода на хаскеле. Именно это и имеют в виду, когда говорят, что хаскель "сложный": привычные по другим языкам "очевидные" умолчания и практики на пользу не пойдут.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, MigMit, Вы писали:
_>>Те же генераторы, условия, шаблоны,
MM>А нельзя поподробнее? Потому что пока я понимаю так: MM> ... MM>Я вполне допускаю, что понял неправильно, и прошу пояснить.
Я ответил в сообщение выше. Речь была про работу со списками.
MM>Это сомнительное утверждение. Единственное преимущество Пролога в этом смысле (ЕМНИМЭ) — это возможность использовать две одинаковых переменных (что-нибудь вроде [X|X|R]). В Хаскеле это эмулируется как (x : x' : rest) | x' == x. Причина этому — то, что в прологе любые термы можно сравнить на равенство; в Хаскеле нельзя. Это, в свою очередь, следствие того, что в Хаскеле терм может оказаться, например, функцией — а функции ну никак не сравниваются. Поэтому Хаскель не предполагает по умолчанию, что к переменным паттерна применимо свойство "такой же, как этот".
А как на счёт техники отката и отсечения из Пролога в сопоставление с образцом в Haskell?
MM>Это неверно. Монады — всего лишь один из множества полезных классов Хаскеля. Отличаются они только тем, что для них предусмотрен лёгкий синтаксический сахар. Но пользоваться им необязательно.
Эээ, я могу вызывать какие-то системные функции не из монад? Возможно я в чём-то не разобрался конечно...
MM>Ну, если вся программа — только лишь GUI, то удивляться нечему. А в "примерах", скорее всего, так и есть. Это то самое "отделение сомнительной части от внутренней логики", только в учебных примерах по GUI-библиотекам внутренней логики, собственно, нет.
Да, но у нас же не только GUI идёт в монадах, но и любые "внешние операции". Т.е. там в файл записать и по сети передать и так далее. Если мы вычтем и gui и всю эту непосредственную работу, то что остаётся то? Несколько алгоритмических кусков и всё? Я к тому что получается что код внутри монад будет очень сильно преобладать по объёму в любом реальном приложение...
_>>Но сценарии её использования действительно интересны лишь на различных абстракциях (типа бесконечных структур данных), а в остальных случаях неленивые решения ничем не уступают.
MM>Это тоже не так. На самом деле, практически во всех энергичных (== во всех остальных) языках есть элементы ленивости — потому что без них никак. Как минимум, конструкция if...then...else является ленивой вообще везде. Операторы "и" и "или" практически везде тоже ленивы — не вычисляют второй аргумент, если первого достаточно. А, скажем, нормальный Y-комбинатор вообще в энергичных языках не встречается, да и не может.
Само собой что ленивость можно реализовать в любом языке. Тут основное преимущество что она полноценно встроена в синтаксис языка. Но опять же на таком уровне она и нужна не так что бы часто. Т.е. я вспоминая свои проекты не вижу особо где она могла бы добавить реальных преимуществ.
Здравствуйте, Аноним, Вы писали:
А> Спрашивается — а на кой хрен вообще программировать GUI на Хаскелле? Да еще используя биндинги к чужеродным библиотекам, а не родную для Хаскелля идеологию functional reactive programming? Для GUI есть всякие там Tcl/Tk, идеально под эту задачу заточенные.
Я оценивал как язык справляется с этой задачей — он же позиционируется как универсальный... Да, и в данный момент у меня не стоит проблема выбора инструментария. Я просто присматривался не сможет ли вдруг Хаскель оказаться лучше моего текущего выбора. Оказалось что не может.
Да, и кстати дело не в GUI. Как я понимаю вообще вся внешняя работа (включая запись в файл) вынесена в монады. Соответственно инструмент получается неудобный почти для всех практических задач.
А> Функциональный код на Питоне?!? Не смешите мои панталоны! Какой такой "функциональный" код на языке, где expression и statement это разные вещи?
Там естествено нет "доказанной чистоты". ))) Но зато отлично сочетаются чисто функциональные практики с прямыми вызовами API.
А> Человеку, программирующему гуйню, ничего сложнее Tcl и не нужно по определению. Меня, честно говоря, сильно удивляет, как это жирный и сложный C++ (он на порядок сложнее того же Хаскелля) оказался в списке ваших инструментов. Не гуйнячий же язык, совсем не гуйнячий.
А мы как бы и не только GUI создаём — это только небольшая, но необходимая часть любого проекта. )))
Здравствуйте, vshabanov, Вы писали:
V>Судя по всему, ты написал на хаскеле дубовый императивный GUI-код, совершенно такой же, как на питоне. Вот по-этому разницы особой нет. Попробуй пописать что-нить более существенное, чем дергание сишных библиотечных ф-ий.
Нуу GUI в виде "мегамонады" — это я в основном не сам писал, а посмотрел примеры к библиотечке. А сам я писал естественно вообще без этого. Так, всякия математические фокусы попроверял ради интереса. Единственно что действительно "цепляет" — это ленивость встроенная в язык, а не путём "eval". Кажется в Maple такое было ещё помнится... Но неабстрактные практические применения что-то так и не пришли в голову.
V>А что у вас за задачи, что требуется быстродействие и при этом используется питон?
Питон используем во всех внутренних утилитах/процессах и для серверных скриптов на сайтах.
V>"генераторы, условия, шаблоны" -- это что?
Хы, уже 3-ий кто спрашивает. ))) Я там чуть выше ответил кому-то.
V>Сопоставление с образцом конечно слабее унификации (но мощнее серии if/then/else). Только вот в хаскеле можно проверить полноту сопоставления (и вообще правильность типов), а в прологе нет. Также со всякими ViewPatterns/PatternGuards/RecordWildcards сопоставление с образцом уже не кажется таким слабым.
Согласен) Я так и сказал что слабее прологовского и немного сильнее if/else. Просто я как бы ждал от языка слишком многого. Т.е. если сопоставление с образцом, то тогда уж уровня Пролога. Это же "великий хаскель". )))
V>Сечения используются довольно часто. Ты попробуй попиши код с ф-иями высшего порядка и поймешь.
Нуу функторы делают тоже самое в любом языке (где можно оперировать функциями). Тут просто это красивее и естественнее. Но как бы я не особо часто пользуюсь функторами в таком стиле.
V>Монады -- не встроенный язык. Это type class с небольшим сахаром для одной ф-ии. И служат они не только для IO. Хотя в дубовом IO (последовательный вызов библиотечных ф-ий) без использования ФВП и прочих хаскельныйх фишек могут выглядеть даже похуже питона. Только такой код на хаскеле почти не пишется (если только в примерах работы с GUI библиотеками .
Как бы не только GUI же получается но и любые "внешние операции"... Т.е. их получается "слишком много" в реальные приложениях. Я посмотрел там всякие примеры не образцового математического кода, а чего-то реального...
V>А пример записи императивного и функционального кода в питоне в едином стиле можно? В питоне вообще expression и statement -- разные вещи.
Нуу грубо говоря мы можем записать в генератор (или в map) чтение/запись в файл и никаких проблем не будет. Естественно это будет не "чистая" операция. Но мы же стремимся не к идеалу какому-то абстрактному, а к решению реальных задач. А "чистота" нам полезна только для удобства разработки, для уменьшения ошибок.
V>Ага, а еще ленивые вычисление позволяют тебе писать код в любом порядке, как удобно, а не как будет вычисляться. Т.е. если ты написал x = y where y = x, ты уже используешь ленивые вычисления.
Ага, удобно для символической математике. В Maple помнится тоже были всякие удобные вещи из этой области. Но я как бы математическим софтом не занимаюсь, поэтому на практике пока не особо вижу пользу.
V>Для GUI посмотри на Tcl/Tk. Удобнее вроде пока ничего не придумали (правда выглядит плохо, но все равно стоит ознакомиться). Ну и попробуй напиши какую-нить логику к твоему GUI, может мнение о хаскелле поменяется )
Эээ, это тут оффтопик конечно. Но Tcl/Tk — это недоинструмент по сравнению с современными мощными библиотеками с визуальными редакторами, нативными контролами и т.д. Да ну и главное: GUI — это как бы не главная цель, а просто один из обязательных параметров.
Что касается Хаскеля, то как бы я на самом деле не говорю что это плохой язык. Просто не такой хороший, как я о нём когда-то думал. Ну и как бы неполезный на практике в большинстве случаев.
Здравствуйте, m e, Вы писали:
ME>про какие сечения идет речь? (АОР?)
Которое "частичное применение"...
ME>полезность ФП проявится в основном при написании thread-safe кусочков кода (и да, там, если припрет, *придется* повторять весь этот хаскельный выпендреж в стиле эмуляции double linked list и прочих извращений) -- а вся остальная программа, как и раньше, будет императивная
Ага, вроде за счёт этого Эрланг сейчас процветать начинает.
Правда у меня на C++ с многопоточностью ни малейших проблем нет. Кстати, а вот в Питоне проблема с этим вроде имеется, но мы его не используем для таких задач.
Здравствуйте, Klapaucius, Вы писали:
K>Для справки, в вашем списке языков примерно 0 наименований, которые подготовили бы вас к реальному функциональному стилю в котором пишут на хаскеле и примерно 12 наименований, которые бы затруднили понимание и написание кода на хаскеле. Именно это и имеют в виду, когда говорят, что хаскель "сложный": привычные по другим языкам "очевидные" умолчания и практики на пользу не пойдут.
Здравствуйте, alex_public, Вы писали:
_>Речь не о том. Я имел в виду, что когда я заглянул в реальные примеры кода (не своего), то увидел что там кода "внутри монад" больше по объёму чем вне их. Соответственно возникает вопрос: "а нафига тогда так отделять было?". Ведь цена такого отдельния тоже есть — не слишком удобный синтаксис внутри монад.
Не нравится специальный синтаксис монад -- никто не заставляет им пользоваться
Здравствуйте, alex_public, Вы писали:
_>Да, и кстати дело не в GUI. Как я понимаю вообще вся внешняя работа (включая запись в файл) вынесена в монады. Соответственно инструмент получается неудобный почти для всех практических задач.
Вся внешняя работа вынесена в монаду IO ("монада Йо" (c) lurkmore)
Здравствуйте, alex_public, Вы писали:
_>Нуу грубо говоря мы можем записать в генератор (или в map) чтение/запись в файл и никаких проблем не будет. Естественно это будет не "чистая" операция. Но мы же стремимся не к идеалу какому-то абстрактному, а к решению реальных задач. А "чистота" нам полезна только для удобства разработки, для уменьшения ошибок.
А кто мешает так писать на хаскеле? mapM спасёт отца русской демократии.
_>Эээ, это тут оффтопик конечно. Но Tcl/Tk — это недоинструмент по сравнению с современными мощными библиотеками с визуальными редакторами, нативными контролами и т.д. Да ну и главное: GUI — это как бы не главная цель, а просто один из обязательных параметров.
Оно конечно, недоинструмент, но некоторые вообще на delphi'ях/C-builder'ах гуи валяют.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, vshabanov, Вы писали:
V>>Судя по всему, ты написал на хаскеле дубовый императивный GUI-код, совершенно такой же, как на питоне. Вот по-этому разницы особой нет. Попробуй пописать что-нить более существенное, чем дергание сишных библиотечных ф-ий.
_>Нуу GUI в виде "мегамонады" — это я в основном не сам писал, а посмотрел примеры к библиотечке. А сам я писал естественно вообще без этого. Так, всякия математические фокусы попроверял ради интереса. Единственно что действительно "цепляет" — это ленивость встроенная в язык, а не путём "eval". Кажется в Maple такое было ещё помнится... Но неабстрактные практические применения что-то так и не пришли в голову.
Ленивость позволяет меньше думать над кодом. Пишешь как пишешь, в каком хочешь порядке, а вычисляться будет как надо.
Из практики -- позволяет очень много чего обрабатывать как списки, а не в цикле, при этом не выжирая всю память. Какой-нить (putStr . map toUpper =<< getContents) может хоть на гигабайтном вводе работать.
Также можно не задумываясь писать и использовать всякие (forM_ [1..n] ...), никакого списка в памяти не будет (а последние версии ghc вроде вообще такой код могут до обычного цикла соптимизировать).
В общем, у ленивых вычислений выше выразительная сила. И в хаскеле они применяются везде, только это не очень заметно (он же ленивый по-умолчанию)
V>>А что у вас за задачи, что требуется быстродействие и при этом используется питон?
_>Питон используем во всех внутренних утилитах/процессах и для серверных скриптов на сайтах.
Для утилит хаскелл в принципе самое то (если, конечно, недостаточно sed/awk/grep/10 строк perl). Хотя и для остального хорошо подходит, просто с утилитами проще всего попробовать.
V>>"генераторы, условия, шаблоны" -- это что?
_>Хы, уже 3-ий кто спрашивает. ))) Я там чуть выше ответил кому-то.
V>>Сопоставление с образцом конечно слабее унификации (но мощнее серии if/then/else). Только вот в хаскеле можно проверить полноту сопоставления (и вообще правильность типов), а в прологе нет. Также со всякими ViewPatterns/PatternGuards/RecordWildcards сопоставление с образцом уже не кажется таким слабым.
_>Согласен) Я так и сказал что слабее прологовского и немного сильнее if/else. Просто я как бы ждал от языка слишком многого. Т.е. если сопоставление с образцом, то тогда уж уровня Пролога. Это же "великий хаскель". )))
Он не то, чтобы великий. Практически все, что в нем есть, где-то реализовано лучше (ну кроме ленивости/type class-ов), просто в хаскеле это все в одном месте.
V>>Сечения используются довольно часто. Ты попробуй попиши код с ф-иями высшего порядка и поймешь.
_>Нуу функторы делают тоже самое в любом языке (где можно оперировать функциями). Тут просто это красивее и естественнее. Но как бы я не особо часто пользуюсь функторами в таком стиле.
Тут дело в удобстве. Если где-то надо писать отдельный класс с оператором, а где-то просто скобочки с галочками поставить, то там где проще, там и начинаешь пользоваться. В том же С++ не часто делается new, т.к. сразу надо за памятью следить, а в java/c# над этим никто и не задумывается особо.
V>>Монады -- не встроенный язык. Это type class с небольшим сахаром для одной ф-ии. И служат они не только для IO. Хотя в дубовом IO (последовательный вызов библиотечных ф-ий) без использования ФВП и прочих хаскельныйх фишек могут выглядеть даже похуже питона. Только такой код на хаскеле почти не пишется (если только в примерах работы с GUI библиотеками .
_>Как бы не только GUI же получается но и любые "внешние операции"... Т.е. их получается "слишком много" в реальные приложениях. Я посмотрел там всякие примеры не образцового математического кода, а чего-то реального...
Не сказал бы, что монады вообще напрягают и чего-то там слишком много. Кроме ввода/вывода/вызова внешних ф-ий, есть еще и логика, ради которой все эти вызовы делаются. И вот эта логика обычно чистая.
Т.е. код внешне может выглядеть каким-то большим do, но реальной императивности там почти нет.
V>>А пример записи императивного и функционального кода в питоне в едином стиле можно? В питоне вообще expression и statement -- разные вещи.
_>Нуу грубо говоря мы можем записать в генератор (или в map) чтение/запись в файл и никаких проблем не будет. Естественно это будет не "чистая" операция. Но мы же стремимся не к идеалу какому-то абстрактному, а к решению реальных задач. А "чистота" нам полезна только для удобства разработки, для уменьшения ошибок.
В хаскеле тоже можно, только потом придется sequence добавить, чтобы все-таки выполнить список действий:
sequence_ [do print x; print y | x <- [1..5], y <- [1..5]]
V>>Ага, а еще ленивые вычисление позволяют тебе писать код в любом порядке, как удобно, а не как будет вычисляться. Т.е. если ты написал x = y where y = x, ты уже используешь ленивые вычисления.
_>Ага, удобно для символической математике. В Maple помнится тоже были всякие удобные вещи из этой области. Но я как бы математическим софтом не занимаюсь, поэтому на практике пока не особо вижу пользу.
Математическим софтом вообще мало кто занимается. Это удобно и при обычном программировании. По другому начинаешь на код смотреть -- не "так, тут а=foo, теперь b=bar, ага, возвращаем baz a b", а "ага baz a b", а что такое 'a'/'b' можно зачастую и не смотреть, если название более менее осмысленное
V>>Для GUI посмотри на Tcl/Tk. Удобнее вроде пока ничего не придумали (правда выглядит плохо, но все равно стоит ознакомиться). Ну и попробуй напиши какую-нить логику к твоему GUI, может мнение о хаскелле поменяется )
_>Эээ, это тут оффтопик конечно. Но Tcl/Tk — это недоинструмент по сравнению с современными мощными библиотеками с визуальными редакторами, нативными контролами и т.д. Да ну и главное: GUI — это как бы не главная цель, а просто один из обязательных параметров.
_>Что касается Хаскеля, то как бы я на самом деле не говорю что это плохой язык. Просто не такой хороший, как я о нём когда-то думал. Ну и как бы неполезный на практике в большинстве случаев.
Ну неполезность, она от задач зависит. Возможно, если есть куча наработок/нужных библиотек на питоне, от хаскелла особого толка и не будет. Хотя, у кого как http://habrahabr.ru/company/selectel/blog/135858/
Здравствуйте, jazzer, Вы писали:
J>А что такое ПП? И, чтоб 2 раза не вставать уже — что такое "(a ~ b) => ..."?
ПП — Параметрический полиморфизм. Грубо говоря, одна реализация для разных типов данных.
Второе объяснять долго, это фишка в последних версиях GHC, чисто хаскельная, в общем-то.
J>Интересно потестить возможности плюсовых шаблонов.
ПП в плюсах нет.
Есть неполноценная имитация — шаблоны. Когда функция для каждого типа пишется отдельно, но макросистема (т.е., шаблоны) позволяет один раз показать, как оно должно выглядеть, а дальше компилятор пишет их сам.
Re[3]: Мифический Haskell
От:
Аноним
Дата:
16.02.12 17:58
Оценка:
Здравствуйте, alex_public, Вы писали:
А>> Спрашивается — а на кой хрен вообще программировать GUI на Хаскелле? Да еще используя биндинги к чужеродным библиотекам, а не родную для Хаскелля идеологию functional reactive programming? Для GUI есть всякие там Tcl/Tk, идеально под эту задачу заточенные.
_>Я оценивал как язык справляется с этой задачей — он же позиционируется как универсальный...
Вот именно, что универсальный. А задача-то узкоспециализированная.
_>Да, и кстати дело не в GUI. Как я понимаю вообще вся внешняя работа (включая запись в файл) вынесена в монады.
Что значит "вынесена"? Кажется, вы не понимаете, что такое монады.
_> Соответственно инструмент получается неудобный почти для всех практических задач.
Странные у вас практические задачи.
А>> Функциональный код на Питоне?!? Не смешите мои панталоны! Какой такой "функциональный" код на языке, где expression и statement это разные вещи?
_>Там естествено нет "доказанной чистоты". ))) Но зато отлично сочетаются чисто функциональные практики с прямыми вызовами API.
Какие такие "функциональные практики"? Там полноценной лямбды нет. Закопать!
А>> Человеку, программирующему гуйню, ничего сложнее Tcl и не нужно по определению. Меня, честно говоря, сильно удивляет, как это жирный и сложный C++ (он на порядок сложнее того же Хаскелля) оказался в списке ваших инструментов. Не гуйнячий же язык, совсем не гуйнячий.
_>А мы как бы и не только GUI создаём — это только небольшая, но необходимая часть любого проекта. )))
А я вот за всю свою жизнь вообще ни одного гуя не нарисовал. И потребности такой никогда не возникало. Может, не такая уж это и "необходимая" часть любого проекта, а?
Тем более что на кой хрен надо делать гуй на том же языке, что и логику? Все нормальные люди гуй и логику всегда разделяют.
Здравствуйте, m e, Вы писали:
MM>>Я как-то приводил пример: http://migmit.livejournal.com/32688.html
ME>поставь туда null вместо одного из Cons-ов -- и компилятор жизнерадостно сховает неверную программу
Ты убеждаешь меня, что хаскель лучше шарпа и жабы? Спасибо, я знаю.
Здравствуйте, Don Reba, Вы писали:
DR>Здравствуйте, MigMit, Вы писали:
MM>>Как минимум, конструкция if...then...else является ленивой вообще везде.
DR>Не знаю как сейчас с OpenCL, но Brook на картах ATI параллельно вычислял обе ветки.
Здравствуйте, alex_public, Вы писали:
_>Я ответил в сообщение выше. Речь была про работу со списками.
Ясно, спасибо. Да, в питоне компрехеншены спижжены именно из Хаскеля.
_>А как на счёт техники отката и отсечения из Пролога в сопоставление с образцом в Haskell?
Не понял. Как техника отката относится к паттерн-матчингу?
_>Эээ, я могу вызывать какие-то системные функции не из монад? Возможно я в чём-то не разобрался конечно...
Во-первых, не из монад, а из IO. Только. Монад гораздо больше.
Во-вторых, можешь. Хаскельный FFI позволяет объявить внешнюю функцию как чистую. Другой вопрос, что если она таки нечистая, то ты поимеешь проблем (от непредсказуемости порядка вычислений, например).
_>Да, но у нас же не только GUI идёт в монадах, но и любые "внешние операции". Т.е. там в файл записать и по сети передать и так далее. Если мы вычтем и gui и всю эту непосредственную работу, то что остаётся то? Несколько алгоритмических кусков и всё? Я к тому что получается что код внутри монад будет очень сильно преобладать по объёму в любом реальном приложение...
По-моему, ты не писал реальных приложений вообще.
Мне приходилось работать в геймдеве. Сейчас я занимаюсь антивирусами.
И там, и там, объём того, что можно назвать "внешними операциями" безумно мал по сравнению с манипуляциями внутренними структурами данных.
_>Само собой что ленивость можно реализовать в любом языке. Тут основное преимущество что она полноценно встроена в синтаксис языка. Но опять же на таком уровне она и нужна не так что бы часто. Т.е. я вспоминая свои проекты не вижу особо где она могла бы добавить реальных преимуществ.
Естественно. В проекте на C++ ленивость никому нафиг не нужна.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, Klapaucius, Вы писали:
K>>Для справки, в вашем списке языков примерно 0 наименований, которые подготовили бы вас к реальному функциональному стилю в котором пишут на хаскеле и примерно 12 наименований, которые бы затруднили понимание и написание кода на хаскеле. Именно это и имеют в виду, когда говорят, что хаскель "сложный": привычные по другим языкам "очевидные" умолчания и практики на пользу не пойдут.
_>Эээ, Lisp — это тоже анти Хаскель? )))
Лисп — это такой суровый императивный язык с некоторыми функциональными возможностями. Написанию кода в функциональном стиле он совершенно не способствует. Теоретически, это возможно, но вот Женя Кирпичёв, например, даже на Java умудрялся писать в функциональном стиле.
MM>>>Я как-то приводил пример: http://migmit.livejournal.com/32688.html ME>>поставь туда null вместо одного из Cons-ов -- и компилятор жизнерадостно сховает неверную программу MM>Ты убеждаешь меня, что хаскель лучше шарпа и жабы? Спасибо, я знаю.
нет, я убеждаю тебя, что написанный тобой код на жабе и шарпе не работает -- не проверяет статически равную длинну векторов
кстати, его похоже можно обламать и иным способом, кроме null
а еще он может быть дыряв даже на хаскеле с расширениями
Здравствуйте, MigMit, Вы писали:
MM>Здравствуйте, jazzer, Вы писали:
J>>А что такое ПП? И, чтоб 2 раза не вставать уже — что такое "(a ~ b) => ..."?
MM>ПП — Параметрический полиморфизм. Грубо говоря, одна реализация для разных типов данных.
реализация в каком смысле?
Если в текстовом — то это шаблоны.
А если в бинарном — то это type erasure уже (void* в экстремальном случае)...
MM>Второе объяснять долго, это фишка в последних версиях GHC, чисто хаскельная, в общем-то.
J>>Интересно потестить возможности плюсовых шаблонов.
MM>ПП в плюсах нет.
MM>Есть неполноценная имитация — шаблоны. Когда функция для каждого типа пишется отдельно, но макросистема (т.е., шаблоны) позволяет один раз показать, как оно должно выглядеть, а дальше компилятор пишет их сам.
Это да, но он еще может кой-чего на типах посчитать в процессе.
Я посмотрел твой пост про скалярное произведение, мало что понял, если честно — если длина известна во время компиляции, то есть более простые способы ее проверить безо всяких консов...
Есть какая-нть задача, которая решается только ПП и другого более прямого решения на С++ нету?
Здравствуйте, jazzer, Вы писали:
MM>>ПП — Параметрический полиморфизм. Грубо говоря, одна реализация для разных типов данных. J>реализация в каком смысле? J>Если в текстовом — то это шаблоны. J>А если в бинарном — то это type erasure уже (void* в экстремальном случае)...
В бинарном.
Аргумент про type erasure не понятен для языков, компилирующихся в натив. В ассемблерном коде типов всё равно нет.
Вот в Java, где типы сохраняются в байткоде, там да, там type erasure приводит к безумным спецэффектам — скажем, если есть interface I<T>, то нельзя одновременно в одном классе реализовать I<A> и I<B>. В шарпе можно, там не на стирании типов это дело завязано.
J>Я посмотрел твой пост про скалярное произведение, мало что понял, если честно — если длина известна во время компиляции, то есть более простые способы ее проверить безо всяких консов...
А она неизвестна. Известно, что она ОДИНАКОВАЯ.
J>Есть какая-нть задача, которая решается только ПП и другого более прямого решения на С++ нету?
Здравствуйте, vshabanov, Вы писали:
V>Ленивость позволяет меньше думать над кодом. Пишешь как пишешь, в каком хочешь порядке, а вычисляться будет как надо.
Возможно стоит ещё посмотреть это. Есть какие-нибудь интересные примеры "правильного" кода, но при этом не из абстрактной области, а из реальных приложений?
V>Из практики -- позволяет очень много чего обрабатывать как списки, а не в цикле, при этом не выжирая всю память. Какой-нить (putStr . map toUpper =<< getContents) может хоть на гигабайтном вводе работать. V>Также можно не задумываясь писать и использовать всякие (forM_ [1..n] ...), никакого списка в памяти не будет (а последние версии ghc вроде вообще такой код могут до обычного цикла соптимизировать).
Кстати, что касается ленивости в списках, то в том же Питоне есть некая забавная попытка ленивости — две отдельных функции range и xrange.
V>Для утилит хаскелл в принципе самое то (если, конечно, недостаточно sed/awk/grep/10 строк perl). Хотя и для остального хорошо подходит, просто с утилитами проще всего попробовать.
У нас много сложных процессов. Правда у Питона в данном случае ещё преимущество в быстрой правке (не компилируемые программки).
V>Не сказал бы, что монады вообще напрягают и чего-то там слишком много. Кроме ввода/вывода/вызова внешних ф-ий, есть еще и логика, ради которой все эти вызовы делаются. И вот эта логика обычно чистая.
Во многих приложения сама логика часто реализуется в виде набора вызовов системных функций. Т.е. даже дело не в императивности (возможно это и можно было под функциональных стиль подвести), а именно в том что вызовы системы, а это в Хаскеле "не чистое". )))
V>В хаскеле тоже можно, только потом придется sequence добавить, чтобы все-таки выполнить список действий: V>sequence_ [do print x; print y | x <- [1..5], y <- [1..5]]
Воо, это уже интереснее. ) Но в том же Питоне оно у нас без лишних слов работает. Но в любом случае посмотрею на эту sequence_...
V>Ну неполезность, она от задач зависит. Возможно, если есть куча наработок/нужных библиотек на питоне, от хаскелла особого толка и не будет. Хотя, у кого как http://habrahabr.ru/company/selectel/blog/135858/
Интересно. ) Хотя если у них затык в быстродействие был, то я бы на их месте перешёл на C++11... Ещё про OCaml или D можно было бы подумать, но только в рамках "поиска и фана". А вот C++11 (если конечно найти реальных специалистов) дал бы гарантированный результат.
Что касается нас, то кучи своих библиотек на Питоне у нас может и нет. Но допустим для сборки проектов (а они бывают сложные, многоступенчатые, с тестами, сборками хелпов, языков, инсталляторов и т.п.) у нас используется scons — скрипт сборки представляет собой просто нормальную Питон программу, работающую в особом окружение. Потом у Питона есть биндинги ко множеству полезных нам библиотек: криптография, гуи, сеть, базы данных.
О, кстати, интересно... А кто-нибудь использует haskell в качестве серверных скриптов? По идее там многие недостатки будут скрыты за допустим fastcgi интерфейсом или чем-то подобным...
Здравствуйте, Аноним, Вы писали:
_>>Я оценивал как язык справляется с этой задачей — он же позиционируется как универсальный...
А> Вот именно, что универсальный. А задача-то узкоспециализированная.
GUI — это явно не ускоспециализорованная задача. ))) Ну разве что если вы программируете микроконтролёры и т.п... )))
А> Какие такие "функциональные практики"? Там полноценной лямбды нет. Закопать!
Хыхы, а какие тогда на ваш взгляд есть языки с нормальной поддержкой функционального программирования? )
А> А я вот за всю свою жизнь вообще ни одного гуя не нарисовал. И потребности такой никогда не возникало. Может, не такая уж это и "необходимая" часть любого проекта, а?
Для наших проектов это необходимая часть. )
А> Тем более что на кой хрен надо делать гуй на том же языке, что и логику? Все нормальные люди гуй и логику всегда разделяют.
Разделять (логически) — это одно. А использовать для них разные инструменты (и плюс неизбежные переходники делать) — это только от бедности используемых инструментов. К счастью у моих команд в руках инструменты позволяющие и просто создавать быстрый системный код с логикой и быстро создавать красивые интерфейсы.
Здравствуйте, alex_public, Вы писали:
_>О, кстати, интересно... А кто-нибудь использует haskell в качестве серверных скриптов? По идее там многие недостатки будут скрыты за допустим fastcgi интерфейсом или чем-то подобным...
А зачем это надо, если есть тот же warp, например?
(ссылка немного старовата уже правда)
Здравствуйте, MigMit, Вы писали:
MM>Ясно, спасибо. Да, в питоне компрехеншены спижжены именно из Хаскеля.
Хы, а Питон не раньше его появился на свет случаем? )))
Да, кстати, там ещё guard'ы Хаскеля выглядят буквательно так же. Только вместо запятой в Питоне "if".
MM>Не понял. Как техника отката относится к паттерн-матчингу?
Как это какое? Это как раз основное преимущество Пролога в этой области. Без этого сопоставление с образцом является только синтаксическим сахаром к if/else лестнице. А с ним получается мощнейший инструмент. Ну на самом деле это просто у меня были завышенные ожидания к Хаскелю, что типа там собрали всё лучшее и если уж есть сопоставления с образцом, то "максимальное, прологовское". )
MM>Во-вторых, можешь. Хаскельный FFI позволяет объявить внешнюю функцию как чистую. Другой вопрос, что если она таки нечистая, то ты поимеешь проблем (от непредсказуемости порядка вычислений, например).
Хм. Интересно. Я так понимаю что по умолчанию все системные функции объявлены как нечистые и подобные действия — это типа нарушения практик?
Здравствуйте, MigMit, Вы писали:
MM>Лисп — это такой суровый императивный язык с некоторыми функциональными возможностями. Написанию кода в функциональном стиле он совершенно не способствует. Теоретически, это возможно, но вот Женя Кирпичёв, например, даже на Java умудрялся писать в функциональном стиле.
Нууу у нас и STL с частью Boost'а можно обозвоать функциональным стилем. ))) Только от этого C++ не становится функциональным языком. )
Но вот то что Lisp императивный — это довольно неожиданная новость. )))
Здравствуйте, Курилка, Вы писали:
К>Здравствуйте, alex_public, Вы писали:
_>>О, кстати, интересно... А кто-нибудь использует haskell в качестве серверных скриптов? По идее там многие недостатки будут скрыты за допустим fastcgi интерфейсом или чем-то подобным...
К>А зачем это надо, если есть тот же warp, например? К>(ссылка немного старовата уже правда)
Собственно я что-то типа этого и спрашивал. Спасибо. ) А на практике кто-нибудь это использовал? Или может есть известные проекты на этой базе?
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, MigMit, Вы писали:
MM>>Ясно, спасибо. Да, в питоне компрехеншены спижжены именно из Хаскеля.
_>Хы, а Питон не раньше его появился на свет случаем? )))
Компрехеншены появились, если википедия не врёт, в Python 2.4, это 2004 год. Стандарт Хаскеля (уже содержащий компрехеншены) — 1998 год.
MM>>Не понял. Как техника отката относится к паттерн-матчингу?
_>Как это какое? Это как раз основное преимущество Пролога в этой области.
В какой области? Особенно учитывая, что это вообще основа Пролога в целом.
_>Ну на самом деле это просто у меня были завышенные ожидания к Хаскелю, что типа там собрали всё лучшее
Разумеется, нет. Хаскель — это новое изообретение, а не коллекция старых трюков.
MM>>Во-вторых, можешь. Хаскельный FFI позволяет объявить внешнюю функцию как чистую. Другой вопрос, что если она таки нечистая, то ты поимеешь проблем (от непредсказуемости порядка вычислений, например).
_>Хм. Интересно. Я так понимаю что по умолчанию все системные функции объявлены как нечистые и подобные действия — это типа нарушения практик?
По умолчанию системные функции не объявлены. Хаскель — кроссплатформенный инструмент, и на одну конкретную версию системного API не завязан.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, Курилка, Вы писали:
К>>Здравствуйте, alex_public, Вы писали:
_>>>О, кстати, интересно... А кто-нибудь использует haskell в качестве серверных скриптов? По идее там многие недостатки будут скрыты за допустим fastcgi интерфейсом или чем-то подобным...
К>>А зачем это надо, если есть тот же warp, например? К>>(ссылка немного старовата уже правда)
_>Собственно я что-то типа этого и спрашивал. Спасибо. ) А на практике кто-нибудь это использовал? Или может есть известные проекты на этой базе?
Ну вот скоро сдаём проект в том числе и на этом деле.
Из проектов вспоминается с ходу только http://www.haskellers.com/ на йесоде, исходники его (haskellers) были толи у сноймана толи в аккаунте yesodweb на гитхабе.
Re[5]: Мифический Haskell
От:
Аноним
Дата:
16.02.12 19:57
Оценка:
Здравствуйте, alex_public, Вы писали:
А>> Вот именно, что универсальный. А задача-то узкоспециализированная.
_>GUI — это явно не ускоспециализорованная задача. )))
Да куда уж узкоспециализированнее? Там своя, отличная от всего остального семантика предметной области. ГУЙ на XAML или Tcl/Tk делать надо, на специальных DSL-ях, а не на языках общего назначения.
_> Ну разве что если вы программируете микроконтролёры и т.п... )))
Никогда не программировал микроконтроллеры.
А>> Какие такие "функциональные практики"? Там полноценной лямбды нет. Закопать!
_>Хыхы, а какие тогда на ваш взгляд есть языки с нормальной поддержкой функционального программирования? )
Те, где нет разницы между expression и statement (то есть, у каждого выражения есть значение), где есть полноценные лямбды и лексические замыкания. Питон не пройдет.
А>> Тем более что на кой хрен надо делать гуй на том же языке, что и логику? Все нормальные люди гуй и логику всегда разделяют.
_>Разделять (логически) — это одно. А использовать для них разные инструменты (и плюс неизбежные переходники делать) — это только от бедности используемых инструментов.
Причем тут "бедность"? Это единственный вменяемый подход. Логика отдельно, отрываемые разные ГУЙни к ней — отдельно, другим процессом, с текстовым тупым протоколом между гуем и логикой. Unix way, однако.
_> К счастью у моих команд в руках инструменты позволяющие и просто создавать быстрый системный код с логикой и быстро создавать красивые интерфейсы.
Это C++-то? Не поверю. C++ для логики шикарен, да. Но гуй на нем деланный выглядит тошно (имею в виду код). Особенно если это какая-либо пакость вроде Qt.
Здравствуйте, jazzer, Вы писали:
J>А что такое ПП? И, чтоб 2 раза не вставать уже — что такое "(a ~ b) => ..."?
ПП — это пистолет-пулемёт. А `~' — это волночка. Означает, что типы в контексте должы совпадать. Например, если функция имеет сигнатуру (a ~ b) => a -> b -> c, это значит, что она будет работать с любыми a и b, но они должны совпадать. Полезно это, если есть какие-нибудь функции над типами. Тогда, например можно писать (F a ~ b) => .. что означает, что должны совпадать b и результат применения F к а.
Здравствуйте, MigMit, Вы писали:
_>>Хм. Интересно. Я так понимаю что по умолчанию все системные функции объявлены как нечистые и подобные действия — это типа нарушения практик?
MM>По умолчанию системные функции не объявлены. Хаскель — кроссплатформенный инструмент, и на одну конкретную версию системного API не завязан.
Мммм ну их просто вызывают из соответствующих функций runtime library. Как я понимаю все родные функции оборачивающие OS API (типа работы с файлами и т.д.) тоже нечистые, да? И это как бы обосновано?
Здравствуйте, MigMit, Вы писали:
_>>Но вот то что Lisp императивный — это довольно неожиданная новость.
MM>Он не только императивный, он ещё и перехваленный.
А я и не говорю что Лисп очень хорош. Но вот на счёт императивности... Это как-то слишком. )))
Здравствуйте, Аноним, Вы писали:
А> Да куда уж узкоспециализированнее? Там своя, отличная от всего остального семантика предметной области. ГУЙ на XAML или Tcl/Tk делать надо, на специальных DSL-ях, а не на языках общего назначения.
Ну конечно. ))) Может ещё ресурсы (*.rc) от MS были хорошим решением? )))
_>>Хыхы, а какие тогда на ваш взгляд есть языки с нормальной поддержкой функционального программирования? )
А> Те, где нет разницы между expression и statement (то есть, у каждого выражения есть значение), где есть полноценные лямбды и лексические замыкания. Питон не пройдет.
Вообще то это не ответ на мой вопрос — я предложил перечислить подходящие языки. )
А> Причем тут "бедность"? Это единственный вменяемый подход. Логика отдельно, отрываемые разные ГУЙни к ней — отдельно, другим процессом, с текстовым тупым протоколом между гуем и логикой. Unix way, однако.
Да, да, идеология завоевавшая целый 1% рынка — это самый достойный образец для подражания. )))
А> Это C++-то? Не поверю. C++ для логики шикарен, да. Но гуй на нем деланный выглядит тошно (имею в виду код). Особенно если это какая-либо пакость вроде Qt.
Мне как бы пофиг как он там внутри выглядит — его генерит графический редактор, в котором контролы таскают мышкой. )))
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Похоже Хаскель действительно "affect the way you think about programming", и, похоже, действительно это недоступно ни в одном другом из перечисленных языков. Рекомендую почитать здесь http://www.paulgraham.com/avg.html про "Blub Paradox". Там про Лисп, но суть феномена отражена верно.
После изучения лиспа я очень быстро наигрался в конс-пары и виртуальные машины и прекратил это.
Адский ад был — придуманный мной курсовой проект по созданию смолтолка из лиспа поверх dBase III
А вот после изучения хаскелла уже который год практикую list comprehensions всюду где можно (и за это очень люблю питон с его ленивостью (!)).
Здравствуйте, alex_public, Вы писали:
_>Возможно стоит ещё посмотреть это. Есть какие-нибудь интересные примеры "правильного" кода, но при этом не из абстрактной области, а из реальных приложений?
Конечно, есть! Вот пожалуйста: http://habrahabr.ru/blogs/Haskell/129235/ Там и ссылку на код найдете, и всю предысторию. Код работает ежемесячно, никаких нареканий нет, без этой утилиты теперь жизнь не представляем.
_>А я и не говорю что Лисп очень хорош. Но вот на счёт императивности... Это как-то слишком. )))
Ну функциональный стиль больше поощряется в scheme, в лиспе за всю историю старались развивать императивные возможности (за ради практической пользы).
Здравствуйте, MigMit, Вы писали:
MM>Есть неполноценная имитация — шаблоны. Когда функция для каждого типа пишется отдельно, но макросистема (т.е., шаблоны) позволяет один раз показать, как оно должно выглядеть, а дальше компилятор пишет их сам.
И что? Вопросы реализации. Причём позволяющие использовать низкоуровневую оптимизацию, применительно к конкретному фактическому типу. inbox -- не всегда достоинство.
Здравствуйте, MigMit, Вы писали:
J>>Если в текстовом — то это шаблоны. J>>А если в бинарном — то это type erasure уже (void* в экстремальном случае)... MM>В бинарном. MM>Аргумент про type erasure не понятен для языков, компилирующихся в натив. В ассемблерном коде типов всё равно нет.
Ну void* же, не?
J>>Я посмотрел твой пост про скалярное произведение, мало что понял, если честно — если длина известна во время компиляции, то есть более простые способы ее проверить безо всяких консов...
MM>А она неизвестна. Известно, что она ОДИНАКОВАЯ.
Тогда что надо проверять, если уже все известно?
можешь привести пример пользовательского кода, который либо должен компилироваться, либо нет?
типа вот тут мы одинаковые вектора подаем — все компилируется.
А тут — разные, и не компилируется.
J>>Есть какая-нть задача, которая решается только ПП и другого более прямого решения на С++ нету? MM>Оксюморончик получился.
В смысле, что можно пытаться городить арифметику Пеано, а можно просто воспользоваться тем фактом, что в С++ числа — это числа, которые можно умножать/делить/складывать напрямую, и никакие пеаны для этого не нужны. Это я имею в виду под "более прямым".
Здравствуйте, alexlz, Вы писали:
A>Здравствуйте, MigMit, Вы писали:
MM>>Есть неполноценная имитация — шаблоны. Когда функция для каждого типа пишется отдельно, но макросистема (т.е., шаблоны) позволяет один раз показать, как оно должно выглядеть, а дальше компилятор пишет их сам. A>И что? Вопросы реализации. Причём позволяющие использовать низкоуровневую оптимизацию, применительно к конкретному фактическому типу. inbox -- не всегда достоинство.
Нет. Про бесконечное развёртывание шаблонов не забыл?
Здравствуйте, jazzer, Вы писали:
J>можешь привести пример пользовательского кода, который либо должен компилироваться, либо нет? J>типа вот тут мы одинаковые вектора подаем — все компилируется. J>А тут — разные, и не компилируется.
В том ЖЖ-шном посте есть.
J>В смысле, что можно пытаться городить арифметику Пеано, а можно просто воспользоваться тем фактом, что в С++ числа — это числа, которые можно умножать/делить/складывать напрямую, и никакие пеаны для этого не нужны. Это я имею в виду под "более прямым".
А теперь вспоминаем, что эти числа должны быть известны при компиляции. Не тот факт, что они равны, а они сами.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Здравствуйте, jazzer, Вы писали:
J>>А что такое ПП? И, чтоб 2 раза не вставать уже — что такое "(a ~ b) => ..."?
ПМ>ПП — это пистолет-пулемёт. А `~' — это волночка. Означает, что типы в контексте должы совпадать. Например, если функция имеет сигнатуру (a ~ b) => a -> b -> c, это значит, что она будет работать с любыми a и b, но они должны совпадать. Полезно это, если есть какие-нибудь функции над типами. Тогда, например можно писать (F a ~ b) => .. что означает, что должны совпадать b и результат применения F к а.
Можешь дать пример или ссылку на пример?
А то пока что непонятно, почему бы просто не написать a -> а -> c вместо (a ~ b) => a -> b -> c.
Здравствуйте, MigMit, Вы писали:
MM>Здравствуйте, jazzer, Вы писали:
J>>А то пока что непонятно, почему бы просто не написать a -> а -> c вместо (a ~ b) => a -> b -> c.
MM>Опять-таки, если есть функции над типами, то возможно что-нибудь вроде (F a ~ G b) => a -> b -> ...
Т.е. просто проверка совпадения типов?
Типа такого?
template<class a, class b>
enable_if<is_same< F<a>::type, G<b>::type >>
func( a a_, b b_ )
{}
Здравствуйте, alex_public, Вы писали: _>Разделять (логически) — это одно. А использовать для них разные инструменты (и плюс неизбежные переходники делать) — это только от бедности используемых инструментов. К счастью у моих команд в руках инструменты позволяющие и просто создавать быстрый системный код с логикой и быстро создавать красивые интерфейсы.
Нелюбимый Вами tcl/tk изначально был рассчитан на двухязыковую среду: C (или замена) и tcl. Просто в жизни появилось много проектов на одном tcl/tk (или tcl/tk с некоторыми сишными модулями). Но заявленный Остерхутом принцип создания приложений -- два языка.
Здравствуйте, jazzer, Вы писали:
MM>>Опять-таки, если есть функции над типами, то возможно что-нибудь вроде (F a ~ G b) => a -> b -> ...
J>Т.е. просто проверка совпадения типов? J>Типа такого?
Угу, оно самое.
Re[7]: Мифический Haskell
От:
Аноним
Дата:
17.02.12 08:09
Оценка:
Здравствуйте, alex_public, Вы писали: А>> Да куда уж узкоспециализированнее? Там своя, отличная от всего остального семантика предметной области. ГУЙ на XAML или Tcl/Tk делать надо, на специальных DSL-ях, а не на языках общего назначения.
_>Ну конечно. ))) Может ещё ресурсы (*.rc) от MS были хорошим решением? )))
Не надо каку с конфеткой сравнивать. У вас, похоже, весьма слабые представления о том, что такое DSL.
_>>>Хыхы, а какие тогда на ваш взгляд есть языки с нормальной поддержкой функционального программирования? )
А>> Те, где нет разницы между expression и statement (то есть, у каждого выражения есть значение), где есть полноценные лямбды и лексические замыкания. Питон не пройдет.
_>Вообще то это не ответ на мой вопрос — я предложил перечислить подходящие языки. )
Scheme, Ruby, ML, Haskell
А>> Причем тут "бедность"? Это единственный вменяемый подход. Логика отдельно, отрываемые разные ГУЙни к ней — отдельно, другим процессом, с текстовым тупым протоколом между гуем и логикой. Unix way, однако.
_>Да, да, идеология завоевавшая целый 1% рынка — это самый достойный образец для подражания. )))
Идиотские у вас критерии, батенька. Абсолютно все вменяемые приложения под тот же Windows строятся ровно по той же идеологии. Хотя, гуятому программисту этого не объяснить. Для таких, как вы, возможность оторвать безболезненно гуй и заменить его скриптом, или оторвать гуй и заменить его другим гуем (тем же вебом, например) не значит ничего. Потому-то я и не пользуюсь говнопрограммами, которые пишут такие как вы.
А>> Это C++-то? Не поверю. C++ для логики шикарен, да. Но гуй на нем деланный выглядит тошно (имею в виду код). Особенно если это какая-либо пакость вроде Qt.
_>Мне как бы пофиг как он там внутри выглядит — его генерит графический редактор, в котором контролы таскают мышкой. )))
А, мсье мышевоз? Ну что-ж вы тогда в Хаскелль-то полезли, да и вообще в программирование? Ваше дело — дизайн, вот и дизайньте себе.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, alex_public, Вы писали: А>>> Да куда уж узкоспециализированнее? Там своя, отличная от всего остального семантика предметной области. ГУЙ на XAML или Tcl/Tk делать надо, на специальных DSL-ях, а не на языках общего назначения.
_>>Ну конечно. ))) Может ещё ресурсы (*.rc) от MS были хорошим решением? )))
А> Не надо каку с конфеткой сравнивать. У вас, похоже, весьма слабые представления о том, что такое DSL.
А есть какие-то внятные варианты таких DSL, но чтоб не были прибиты к MS (XAML) или Firefox (XUL) ?
Re[9]: Мифический Haskell
От:
Аноним
Дата:
17.02.12 09:04
Оценка:
Здравствуйте, Курилка, Вы писали:
А>> Не надо каку с конфеткой сравнивать. У вас, похоже, весьма слабые представления о том, что такое DSL.
К>А есть какие-то внятные варианты таких DSL, но чтоб не были прибиты к MS (XAML) или Firefox (XUL) ?
Лично меня прибитость гвоздями к MS или Moonlight ни разу не пугает, красота кода важнее. Для кроссплатформности Tcl/Tk есть.
Здравствуйте, Аноним, Вы писали:
А> Функциональный код на Питоне?!? Не смешите мои панталоны! Какой такой "функциональный" код на языке, где expression и statement это разные вещи?
Лучше скажи, какой "функциональный" код на языке, где expression и statement это одна и та же вещь?
Здравствуйте, Аноним, Вы писали:
А> Не надо каку с конфеткой сравнивать. У вас, похоже, весьма слабые представления о том, что такое DSL.
DSL надо использовать, если он в чём-то удобнее основного языка. Что касается GUI, то в современных инструментах он в любом случае генерируется в визуальных редакторах. Соответственно что там генерируется на самом деле не особо и принципиально — всё равно руками это трогать скорее всего никто не будет. А если редактор имеет возможность генерировать напрямую код на языке, используемом для программирования логики, то это получается совсем хорошо, т.к. убирает все лишние сущности при построение проекта.
А> Scheme, Ruby, ML, Haskell
Т.е. Lisp (кажется в этой темке уже несколько человек сказали что Лисп совсем не функциональный), семейство ML (OCaml, Haskell, F#?) и Ruby (это с его то навязанным ООП???) — это функциональные, да? А скажем Scala, Python, Javascript уже нет? )))
А> Идиотские у вас критерии, батенька. Абсолютно все вменяемые приложения под тот же Windows строятся ровно по той же идеологии. Хотя, гуятому программисту этого не объяснить. Для таких, как вы, возможность оторвать безболезненно гуй и заменить его скриптом, или оторвать гуй и заменить его другим гуем (тем же вебом, например) не значит ничего. Потому-то я и не пользуюсь говнопрограммами, которые пишут такие как вы.
Что-то попытался вспомнить хоть одно популярное приложение работающее в два процесса (движок и интерфейс) на винде и так не смог. Случаи интерфейсов к драйверам или сервисам естественно не рассматриваем, т.к. там это вынужденная мера. А вы говорите "абсолютно все". Может и MS Office так работает?
А> А, мсье мышевоз? Ну что-ж вы тогда в Хаскелль-то полезли, да и вообще в программирование? Ваше дело — дизайн, вот и дизайньте себе.
Т.е. у вас проектируют GUI в текстовом редакторе? Соболезную)))
Здравствуйте, alex_public, Вы писали:
_>DSL надо использовать, если он в чём-то удобнее основного языка. Что касается GUI, то в современных инструментах он в любом случае генерируется в визуальных редакторах.
Слишком сильное утверждение
_>Соответственно что там генерируется на самом деле не особо и принципиально — всё равно руками это трогать скорее всего никто не будет.
Как правило так _>А если редактор имеет возможность генерировать напрямую код на языке, используемом для программирования логики, то это получается совсем хорошо, т.к. убирает все лишние сущности при построение проекта.
При таком подходе профи может быстро сделать качественный интерфейс. Но вот почему-то то, что творит большинство гуевых специалистов вызывает не очень положительные эмоции. Поменял размер окна -- и ты в заднице. Или чего-то не видно, или ещё что... А что, ведь когда автор мышом контролы двигал в редакторе, у него всё отлично смотрелось.
_>Т.е. у вас проектируют GUI в текстовом редакторе? Соболезную)))
Кому? Не факт, что это требует больше времени или движений рук
Здравствуйте, alex_public, Вы писали:
А>> Scheme, Ruby, ML, Haskell
_>Т.е. Lisp (кажется в этой темке уже несколько человек сказали что Лисп совсем не функциональный)
Scheme — не лисп. Ну, или, если считать, что Scheme — лисп, то тогда Common Lisp — не лисп. Тут, знаете, как с римскими папами и антипапами ситуация. Они бы и жгли друг друга на кострах с удовольствием, да боятся, что огонь на бороду перекинется.
_>семейство ML (OCaml, Haskell, F#?) и Ruby (это с его то навязанным ООП???) — это функциональные, да? А скажем Scala, Python, Javascript уже нет? )))
Haskell — не ML семейство. Scala в него и то больше смысла записать.
ML семейство и Scheme — это функциональные, императивные языки. Haskell функциональный и декларативный. В принципе, любой язык, в котором функции первоклассны — функциональный и таких ФЯ-инвалидов довольно много.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, vshabanov, Вы писали:
V>>Ленивость позволяет меньше думать над кодом. Пишешь как пишешь, в каком хочешь порядке, а вычисляться будет как надо.
_>Возможно стоит ещё посмотреть это. Есть какие-нибудь интересные примеры "правильного" кода, но при этом не из абстрактной области, а из реальных приложений?
Не могу сказать. Чукча не читатель. В принципе можно на Hackage залезть, посмореть. Там, правда, в основном библиотеки, и многие сделаны достаточно хило, но все-таки.
V>>Из практики -- позволяет очень много чего обрабатывать как списки, а не в цикле, при этом не выжирая всю память. Какой-нить (putStr . map toUpper =<< getContents) может хоть на гигабайтном вводе работать. V>>Также можно не задумываясь писать и использовать всякие (forM_ [1..n] ...), никакого списка в памяти не будет (а последние версии ghc вроде вообще такой код могут до обычного цикла соптимизировать).
_>Кстати, что касается ленивости в списках, то в том же Питоне есть некая забавная попытка ленивости — две отдельных функции range и xrange.
А в хаскелле над этим думать не надо (если только в некоторых местах, где может быть переполнение ленивых thunk-ов, но со временем просто перестаешь писать код, где это возможно)
V>>Для утилит хаскелл в принципе самое то (если, конечно, недостаточно sed/awk/grep/10 строк perl). Хотя и для остального хорошо подходит, просто с утилитами проще всего попробовать.
_>У нас много сложных процессов. Правда у Питона в данном случае ещё преимущество в быстрой правке (не компилируемые программки).
Хаскелл вообще достаточно быстро компилится (к тому-же у него есть очень удобный repl, и большую часть времени вообще ничего компилить не надо). А сложные процессы обычно быстро не правятся, т.к. сложные. Плюс, лучше подождать пару секунд компиляции, чем сразу запустить и через полчаса обнаружить опечатку.
V>>Не сказал бы, что монады вообще напрягают и чего-то там слишком много. Кроме ввода/вывода/вызова внешних ф-ий, есть еще и логика, ради которой все эти вызовы делаются. И вот эта логика обычно чистая.
_>Во многих приложения сама логика часто реализуется в виде набора вызовов системных функций. Т.е. даже дело не в императивности (возможно это и можно было под функциональных стиль подвести), а именно в том что вызовы системы, а это в Хаскеле "не чистое". )))
V>>В хаскеле тоже можно, только потом придется sequence добавить, чтобы все-таки выполнить список действий: V>>sequence_ [do print x; print y | x <- [1..5], y <- [1..5]]
_>Воо, это уже интереснее. ) Но в том же Питоне оно у нас без лишних слов работает. Но в любом случае посмотрею на эту sequence_...
В питоне есть много других лишних слов -- def, class, скобки при вызове ф-ий.
sequence -- просто выполнить список действий:
sequence_ [] = return ()
sequence_ (x:xs) = x >> sequence_ xs
Дело в том, что
IO a = World -> (a, World)
т.е., чистая ф-ия, изменяющая мир (она действительно так реализована)
Соответственно, список из print-ов -- это именно список из print-ов ([IO ()]), а не список, в процессе вычисления которого что-то мимоходом напечаталось. Соответственно, его еще надо как-то запустить (вычислить), самое простое -- sequence_.
Поскольку IO a -- это именно действие (и, между прочим, чистая ф-я), то их можно комбинировать также как и все остальные выражения. Т.е. можно сделать несколько таких спискок-print-ов и сливать их, можно сделать take/drop, да что угодно. Т.е. уровень работы с программой совершенно другой.
Т.е. все эти "нечистые" монадные вычисления, на самом деле чистые, и по сути:
do a <- b; c
более простой способ записать
\ w0 -> let (a, w1) = b w0 in c w1
По-сути, единственное нечистое место -- это ф-я main.
V>>Ну неполезность, она от задач зависит. Возможно, если есть куча наработок/нужных библиотек на питоне, от хаскелла особого толка и не будет. Хотя, у кого как http://habrahabr.ru/company/selectel/blog/135858/
_>Интересно. ) Хотя если у них затык в быстродействие был, то я бы на их месте перешёл на C++11... Ещё про OCaml или D можно было бы подумать, но только в рамках "поиска и фана". А вот C++11 (если конечно найти реальных специалистов) дал бы гарантированный результат.
Про C++ сразу же вспоминается, почему Торвальдс не использовал C++ для Git (где-то был еще более развернутый ответ, чем по сслыке).
Ждать от С++ гарантированный результат по скорости не стоит. С моей точки зрения оптимальным является вариант Haskell-а с Си-шными вставками для самых тупых процессоро-емких частей.
Лет 5 назад еще рулил окемл, но хаскелл значительно подтянулся по скорости, плюс умеет использовать все ядра.
_>Что касается нас, то кучи своих библиотек на Питоне у нас может и нет. Но допустим для сборки проектов (а они бывают сложные, многоступенчатые, с тестами, сборками хелпов, языков, инсталляторов и т.п.) у нас используется scons — скрипт сборки представляет собой просто нормальную Питон программу, работающую в особом окружение. Потом у Питона есть биндинги ко множеству полезных нам библиотек: криптография, гуи, сеть, базы данных.
Ну вставить хаскелл в scons конечно не получится ) Про библиотеки можно на hackage посмотреть. Правда там много чего сырого.
_>О, кстати, интересно... А кто-нибудь использует haskell в качестве серверных скриптов? По идее там многие недостатки будут скрыты за допустим fastcgi интерфейсом или чем-то подобным...
Вообще его обчыно вместе с вебсервером используют (всякие yesod/warp/happs), зачем каждый раз скрипт пускать
Здравствуйте, alexlz, Вы писали:
A>При таком подходе профи может быстро сделать качественный интерфейс. Но вот почему-то то, что творит большинство гуевых специалистов вызывает не очень положительные эмоции. Поменял размер окна -- и ты в заднице. Или чего-то не видно, или ещё что... А что, ведь когда автор мышом контролы двигал в редакторе, у него всё отлично смотрелось.
От плохих специалистов не застрахован ни один инструмент. Хотя Java/C# усиленно работают в данном направление. )))
_>>Т.е. у вас проектируют GUI в текстовом редакторе? Соболезную))) A>Кому? Не факт, что это требует больше времени или движений рук
Больше по крайне мере на время построения проекта (что бы хоть взглянуть что вышло и поправить может). Про удобство и т.п. я уже молчу...
Здравствуйте, Klapaucius, Вы писали:
K>Haskell — не ML семейство. Scala в него и то больше смысла записать. K>ML семейство и Scheme — это функциональные, императивные языки. Haskell функциональный и декларативный. В принципе, любой язык, в котором функции первоклассны — функциональный и таких ФЯ-инвалидов довольно много.
Воот, с такими формулировками я уже соглашусь.
Единственно что маленькую поправку введу: ФЯ-инвалиды — это обычно мультипарадигменные языки. Т.е. если смотреть с общей точки зрения, то это чистые ФЯ языки выглядят на их фоне инвалидами, т.к. могут только функциональщину, в то время как мультипарадигменные могут и её (возможно по инвалидному кто-то) и плюс ещё кучу всего. )))
Здравствуйте, alex_public, Вы писали:
_>Единственно что маленькую поправку введу: ФЯ-инвалиды — это обычно мультипарадигменные языки.
Все языки, которые я перечислил, тоже мультипарадигменные. ФЯ-инвалиды — это те, которые "могут и ФП, но как-то по инвалидному", например C#, Ruby, Python. В принципе, в императивных ФЯ типа Scheme и ML с ФП тоже некоторые сложности, но ФЯ-инвалидами они все-таки не являются.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, m e, Вы писали:
ME>нет, я убеждаю тебя, что написанный тобой код на жабе и шарпе не работает -- не проверяет статически равную длинну векторов
Но это не связано с полноценностью параметрического полиморфизма.
ME>а еще он может быть дыряв даже на хаскеле с расширениями
А пример можно? Только, чтоб именно с параметрическим полиморфизмом проблемы были, а не с помощью лазеек типа unsafeCoerce и прочих unsafe*.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, vshabanov, Вы писали:
V>А в хаскелле над этим думать не надо (если только в некоторых местах, где может быть переполнение ленивых thunk-ов, но со временем просто перестаешь писать код, где это возможно)
О, кстати, вспомнил ещё про yield в Питоне — тоже интересное решение для ленивости, в императивном стиле можно сказать. )))
V>Поскольку IO a -- это именно действие (и, между прочим, чистая ф-я), то их можно комбинировать также как и все остальные выражения. Т.е. можно сделать несколько таких спискок-print-ов и сливать их, можно сделать take/drop, да что угодно. Т.е. уровень работы с программой совершенно другой.
Это уже к метапрограммированию ближе получается...
V>Т.е. все эти "нечистые" монадные вычисления, на самом деле чистые, и по сути: V>do a <- b; c V>более простой способ записать V>\ w0 -> let (a, w1) = b w0 in c w1
Всё же синтаксис для этой "императивной" части мне не кажется удобным.
V>Про C++ сразу же вспоминается, почему Торвальдс не использовал C++ для Git (где-то был еще более развернутый ответ, чем по сслыке).
Хы, его отношение к C++ давно известно, так что тут это не совсем аргумент. ) А вот если посмотреть на чём написаны apache, nginx, lighttpd... Или те же самые виртуальные машины сами...
V>Ну вставить хаскелл в scons конечно не получится ) Про библиотеки можно на hackage посмотреть. Правда там много чего сырого.
Про scons я немного в другом смысле. Любая система построения может вызвывать скрипты на любом языке. У нас просто так совпало приятно, что внутренний язык системы построения оказался тем же самым языком, что мы выбрали для всех внутренних процессов. Т.е. мы выбрали не из-за scona'a, но в итоге получили подобный приятный бонус (не надо лишних сущностей). Но если бы мы предпочли другой язык (haskell например) для скриптов, то просто в scons'e/cmake'e был бы соответствующий вызов.
V>Вообще его обчыно вместе с вебсервером используют (всякие yesod/warp/happs), зачем каждый раз скрипт пускать
Собственно при fastcgi он и не запускается каждый раз. Но вообще да, отдельный сервер конечно лучше. Хотя мне всё равно пока как-то привычные доверять nginx/lighttpd... )))
Здравствуйте, Klapaucius, Вы писали:
K>Все языки, которые я перечислил, тоже мультипарадигменные. ФЯ-инвалиды — это те, которые "могут и ФП, но как-то по инвалидному", например C#, Ruby, Python. В принципе, в императивных ФЯ типа Scheme и ML с ФП тоже некоторые сложности, но ФЯ-инвалидами они все-таки не являются.
Тогда значит только OCaml у нас получается "самый самый"? ))) И в ФЯ не инвалид и всё остальное есть? )
Здравствуйте, alex_public, Вы писали:
_>Эээ, Lisp — это тоже анти Хаскель? )))
Лиспы вполне себе функциональные. Просто тут возникает путаница. Хаскель является языком чистого функционального программирования. Но апологеты почему-то забывают об этой особенности и любят говорить за все функциональное программирование, чем вызывают немалое раздражение.
Вообще, нет устоявшегося общепризнанного определения того, что является языком функционального программирования, а что нет. На эту тему можно много холиварить.
_>Как это какое? Это как раз основное преимущество Пролога в этой области. Без этого сопоставление с образцом является только синтаксическим сахаром к if/else лестнице. А с ним получается мощнейший инструмент. Ну на самом деле это просто у меня были завышенные ожидания к Хаскелю, что типа там собрали всё лучшее и если уж есть сопоставления с образцом, то "максимальное, прологовское". )
Здравствуйте, Курилка, Вы писали:
А>> Не надо каку с конфеткой сравнивать. У вас, похоже, весьма слабые представления о том, что такое DSL.
К>А есть какие-то внятные варианты таких DSL, но чтоб не были прибиты к MS (XAML) или Firefox (XUL) ?
Здравствуйте, dsorokin, Вы писали:
D>Здравствуйте, alex_public, Вы писали:
_>>Эээ, Lisp — это тоже анти Хаскель? )))
D>Лиспы вполне себе функциональные.
Лисп имеет некоторые функциональные возможности. Но называть его "функциональным языком" — сильное преувеличение. Лисп ни в коей мере не способствует написанию программ в функциональном стиле.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, vshabanov, Вы писали:
V>>А в хаскелле над этим думать не надо (если только в некоторых местах, где может быть переполнение ленивых thunk-ов, но со временем просто перестаешь писать код, где это возможно)
_>О, кстати, вспомнил ещё про yield в Питоне — тоже интересное решение для ленивости, в императивном стиле можно сказать. )))
Ну что уж поделать, коли генераторы через классы офигеешь писать.
V>>Поскольку IO a -- это именно действие (и, между прочим, чистая ф-я), то их можно комбинировать также как и все остальные выражения. Т.е. можно сделать несколько таких спискок-print-ов и сливать их, можно сделать take/drop, да что угодно. Т.е. уровень работы с программой совершенно другой.
_>Это уже к метапрограммированию ближе получается...
К метапрограммированию это не относится, т.к. никакой код не генерится. Но в целом появляется больше способов оперировать с действиями, по сравнению с обычным вызовом ф-ии
V>>Т.е. все эти "нечистые" монадные вычисления, на самом деле чистые, и по сути: V>>do a <- b; c V>>более простой способ записать V>>\ w0 -> let (a, w1) = b w0 in c w1
_>Всё же синтаксис для этой "императивной" части мне не кажется удобным.
Тут уж дело вкуса. Многим, например, нравится f(x,y,z), но не нравится f x y z.
V>>Про C++ сразу же вспоминается, почему Торвальдс не использовал C++ для Git (где-то был еще более развернутый ответ, чем по сслыке).
_>Хы, его отношение к C++ давно известно, так что тут это не совсем аргумент. ) А вот если посмотреть на чём написаны apache, nginx, lighttpd... Или те же самые виртуальные машины сами...
V>Я про то, что писать сборочные скрипты на хаскеле в scons не получится.
Странно что еще не написали сборочную систему на хаскеле, или уже?
V>А собирать хаскелл scons-ом -- это сильно, конечно. Две строчки в Makefile заменить на целую систему сборки.
make не сборочная система?
Две строчки будут в make только для "hello world".
Для кроссплатформенных или когда нужна многовариантная сборка scons или аналоги существенно проще чем маке.
Здравствуйте, Klapaucius, Вы писали:
K>Scheme — не лисп. Ну, или, если считать, что Scheme — лисп, то тогда Common Lisp — не лисп. Тут, знаете, как с римскими папами и антипапами ситуация. Они бы и жгли друг друга на кострах с удовольствием, да боятся, что огонь на бороду перекинется.
Откуда такое мнение? Оба являются равноправными диалектами Лиспа, хоть и сильно отличаются друг от друга.
K>Haskell — не ML семейство. Scala в него и то больше смысла записать. K>ML семейство и Scheme — это функциональные, императивные языки. Haskell функциональный и декларативный. В принципе, любой язык, в котором функции первоклассны — функциональный и таких ФЯ-инвалидов довольно много.
Мне больше нравятся критерии, озвученные в SICP: нормальный порядок вычислений и декомпозиция на потоки. Т.о. к (чисто) функциональным языкам можно отнести Miranda, Clean и Haskell например.
Здравствуйте, Klapaucius, Вы писали:
_>>Тогда значит только OCaml у нас получается "самый самый"?
K>К сожалению, не получается.
Ну да, в общем то не получается. Иначе бы мы давно на нём сидели, а не на C++. Но не получается совсем не по причинам охвата малого количества парадигм. )
_>> И в ФЯ не инвалид и всё остальное есть?
K>Что остальное?
Остальные парадигмы. Хотя логической конечно нет, но она вроде как реально только в Прологе и есть.
Ааа git тоже на C? ))) Я почему то подумал что там отказались от C++ в пользую чего-то динамического. Типа как Mercurial на Питоне работает. Ну это я тогда считаю аргументом в мою же пользу (аргументы товарища Торвальдса о другом) — код на C и C++ в целом обычно одинаково работает по скорости. Даже иногда С++ бывает быстрее за счёт инлайнов постоянных. Но это естественно в нормальных руках.
Хотя C++ тут уже оффтопик по сути.
Вообще лично мне пока только один язык приглянулся (из тех, что я рассматривал для фана) как потенциальная замена текущих инструментов. Это D. Но с ним свои проблемы с библиотеками, компиляторами и т.д. Так что это всё в далёком будущем, если вообще случится. Ещё были варианты с OCaml и Haskell, но это было пока не попробовал их непосредственно...
Здравствуйте, vshabanov, Вы писали:
V>А собирать хаскелл scons-ом -- это сильно, конечно. Две строчки в Makefile заменить на целую систему сборки.
Ну как бы там на языке этой сборки файл проекта и получется из двух строчек. Только он при этом умеет заметно больше make файла. Кроссплатоформенность, отслеживание изменение, отслеживание зависимостей и т.д..
Здравствуйте, Курилка, Вы писали:
FR>>Странно что еще не написали сборочную систему на хаскеле, или уже?
К>написали, конечно
Xa. Я порадовался (может хоть здесь применение найдётся, вдруг чем-то удобнее scons'a будет да и вообще всё же статически типизированые языки больше люблю) и полез смотреть сразу. В описание порекомендовали видео глянуть... Это http://vimeo.com/15465133. А там какая-то странная личность первым же слайдом вывела на экран "мегамонаду". Ну да, Хаскель, такой Хаскель... )))
Re[11]: Мифический Haskell
От:
Аноним
Дата:
17.02.12 16:11
Оценка:
Здравствуйте, alex_public, Вы писали:
FR>>>Странно что еще не написали сборочную систему на хаскеле, или уже?
К>>написали, конечно
_>Xa. Я порадовался (может хоть здесь применение найдётся, вдруг чем-то удобнее scons'a будет да и вообще всё же статически типизированые языки больше люблю) и полез смотреть сразу. В описание порекомендовали видео глянуть... Это http://vimeo.com/15465133. А там какая-то странная личность первым же слайдом вывела на экран "мегамонаду". Ну да, Хаскель, такой Хаскель... )))
Да я вроде уже писал про всё это в теме выше. Сейчас просто уже как шутка скорее. )))
А вообще я обозвал мегамонадами случаи, когда вся программа состоит только из одной монады (main соответственно) и всё. Т.е. никакого "нормального" код рядом.
Да, а в данном случае вообще странно. Я ожидал увидеть в начале презентации системы сборки что-то типа такого http://www.scons.org/doc/production/HTML/scons-user/c258.html — нормальный пример достигаемого эффекта. А то что вылезло там на экран первым делом (после приветствия)... Даже слов нет...
А в Хаскелле они в свою очередь позаимствованы из Miranda.
А в Miranda из KRC.
А в KRC из SASL.
А в SASL из SETL.
А в SETL из Соломона.
А в Соломоне из Давида царя
А в Давиде царя из Иессея.
А в Иессее из Овида
А в Овиде из Вооза от Рахавы
А в Воозе из Салмона
А в Салмоне из Наассона
А в Наассоне из Аминадава
А в Аминадаве из Арама
А в Араме из Есрома
А в Есроме из Фареса
А в Фаресе из Иуды братьев его.
А в Иуде из Иакова.
А в Иакове из Исаака.
А в Исааке из Авраама.
ME>>а еще он может быть дыряв даже на хаскеле с расширениями K>А пример можно? Только, чтоб именно с параметрическим полиморфизмом проблемы были, а не с помощью лазеек типа unsafeCoerce и прочих unsafe*.
"может быть" здесь я употребил в смысле "возможно, что... но доказать не могу"
ME>>нет, я убеждаю тебя, что написанный тобой код на жабе и шарпе не работает -- не проверяет статически равную длинну векторов K>Но это не связано с полноценностью параметрического полиморфизма.
что значит "полноценность"?
в принципе, данный код можно починить -- скажем, считать, что null означает "вектор у которого дальше идут все нули (т.е. 0-ли), поэтому вычисление скалярного умножения можно закончить прямо здесь"
оно, конечно, будет некрасиво и костыльно, и длина вектора будет не определена, но можно будет рассуждать о корректности данной программы и возможности ее сломать
Re[9]: Мифический Haskell
От:
Аноним
Дата:
17.02.12 20:15
Оценка:
Здравствуйте, alex_public, Вы писали: А>> Не надо каку с конфеткой сравнивать. У вас, похоже, весьма слабые представления о том, что такое DSL.
_>DSL надо использовать, если он в чём-то удобнее основного языка.
А DSL (если он хорошо спроектирован) всегда удобнее языка общего назначения. Просто по определению.
_> Что касается GUI, то в современных инструментах он в любом случае генерируется в визуальных редакторах.
Руки отрывать мышевозюкалам с их "визуальными редакторами". Особенно если мышевозюкалы свои гнусные привычки на всяких там уродливых Delphi заработали, и ни черта про layout manager-ы не слышали.
_> Соответственно что там генерируется на самом деле не особо и принципиально — всё равно руками это трогать скорее всего никто не будет.
Нуну. Вы, вероятно, из тех, кто HTML в Ворде верстает?
_> А если редактор имеет возможность генерировать напрямую код на языке, используемом для программирования логики, то это получается совсем хорошо, т.к. убирает все лишние сущности при построение проекта.
Чушь. Забористая такая, начисто лишенная смысла чушь.
А>> Scheme, Ruby, ML, Haskell
_>Т.е. Lisp (кажется в этой темке уже несколько человек сказали что Лисп совсем не функциональный), семейство ML (OCaml, Haskell, F#?) и Ruby (это с его то навязанным ООП???) — это функциональные, да? А скажем Scala, Python, Javascript уже нет? )))
Язык, на котором невозможно писать в чисто функциональном стиле функциональным не является. Нет хвостовой рекурсии — до свидания, следующий.
_>Что-то попытался вспомнить хоть одно популярное приложение работающее в два процесса (движок и интерфейс) на винде и так не смог.
Что-то не могу припомнить ни одно приличное популярное приложение. Попса она на то и попса.
_> Случаи интерфейсов к драйверам или сервисам естественно не рассматриваем, т.к. там это вынужденная мера. А вы говорите "абсолютно все". _> Может и MS Office так работает?
MS Office как раз таки образец редкостного говнища. На кой он нужен, когда есть TeX, когда есть Mathematica (кстати, типичный пример архитектуры с оторванным от гуя ядром).
А>> А, мсье мышевоз? Ну что-ж вы тогда в Хаскелль-то полезли, да и вообще в программирование? Ваше дело — дизайн, вот и дизайньте себе.
_>Т.е. у вас проектируют GUI в текстовом редакторе? Соболезную)))
Повторюсь — тем, кто гуйню рисует мышой, надо грабли отрывать без анастезии. Чтоб точно никогда больше не смогли гуйню рисовать.
Следует обратить внимание, что современный гуй под Windows (на XAML) как правило делается именно в текстовом редакторе. Ибо нефиг.
_>Но вот то что Lisp императивный — это довольно неожиданная новость. )))
лисп, скажем так, не шибко функциональный -- т.е. ленивость там можно сделать на макросах, и из персистентных структур данных ( http://en.wikipedia.org/wiki/Persistent_data_structure ) там только списки, да и то, емнип, там есть операции, деструктивно меняющие поля конс-ячейки
MM>Аргумент про type erasure не понятен для языков, компилирующихся в натив. В ассемблерном коде типов всё равно нет.
MM>Вот в Java, где типы сохраняются в байткоде, там да, там type erasure приводит к безумным спецэффектам — скажем, если есть interface I<T>, то нельзя одновременно в одном классе реализовать I<A> и I<B>. В шарпе можно, там не на стирании типов это дело завязано.
что мы имеем:
1. в яве стирание типов, эмулирующее ПП
2. в шарпе ПП
3. твое скалярное произведение пишется и на яве, и в шарпе
вывод -- тебе достаточно стирания типов, а не ПП, ведь так?
D>Вообще, нет устоявшегося общепризнанного определения того, что является языком функционального программирования, а что нет. На эту тему можно много холиварить.
ФП это чистые функции и персистентные структуры данных, не?
K>ML семейство и Scheme — это функциональные, императивные языки. Haskell функциональный и декларативный. В принципе, любой язык, в котором функции первоклассны — функциональный и таких ФЯ-инвалидов довольно много.
объясни, в каком месте хаскель более декларативен, чем язык из ML семейства
V>Соответственно, список из print-ов -- это именно список из print-ов ([IO ()]), а не список, в процессе вычисления которого что-то мимоходом напечаталось. Соответственно, его еще надо как-то запустить (вычислить), самое простое -- sequence_.
поподробнее насчет различия "список из print-ов -- это именно список из print-ов ([IO ()]), а не список, в процессе вычисления которого что-то мимоходом напечаталось"
скажем, почему список из объектов, у каждого из которых есть функция print, чем-то хуже "списка из print-ов"?
V>Поскольку IO a -- это именно действие (и, между прочим, чистая ф-я), то их можно комбинировать также как и все остальные выражения. Т.е. можно сделать несколько таких спискок-print-ов и сливать их, можно сделать take/drop, да что угодно. Т.е. уровень работы с программой совершенно другой.
Здравствуйте, Аноним, Вы писали:
_>> Может и MS Office так работает?
А> MS Office как раз таки образец редкостного говнища. На кой он нужен, когда есть TeX, когда есть Mathematica (кстати, типичный пример архитектуры с оторванным от гуя ядром).
Вообразим маленький эксперимент. Завхозу объяснили, как компьютер включать, и назначение некоторых клавиш. Бедолаге надо напечатать крупными буквами "Туалет закрыт из-за отсутствия воды". Есть возможность консультироваться по телефону (пока консультанту не надоест). Какой инструмент ему выбрать -- TeX или Word?
Здравствуйте, Аноним, Вы писали:
А> А DSL (если он хорошо спроектирован) всегда удобнее языка общего назначения. Просто по определению.
Кроме случая, когда вообще без разницы какой там язык.
А> Руки отрывать мышевозюкалам с их "визуальными редакторами". Особенно если мышевозюкалы свои гнусные привычки на всяких там уродливых Delphi заработали, и ни черта про layout manager-ы не слышали.
Хы, в наших инструментах как раз всё на layout manager'ах и работает. Но вставляем мы их в окошко кликом мышки в визуальном редакторе.
А> Нуну. Вы, вероятно, из тех, кто HTML в Ворде верстает?
Я лично ничего не верстаю вообще, как впрочем и GUI не рисую сам. Я больше занимаюсь проектирование архитектуры всего этого. И вот в рамках нашей архитектуры все html генерятся у нас с помощью xslt шаблонов из xml данных.
А> Чушь. Забористая такая, начисто лишенная смысла чушь.
Ну да, понятно.. Образование слабое... Про принцип Оккама никогда не слышали... Сочувствую..
А> Язык, на котором невозможно писать в чисто функциональном стиле функциональным не является. Нет хвостовой рекурсии — до свидания, следующий.
В мультипарадигменных языках это не обязательно, т.к. есть другие (и часто более эффективные) инструменты. Не "чистые" естественно.
А> Что-то не могу припомнить ни одно приличное популярное приложение. Попса она на то и попса.
... А> MS Office как раз таки образец редкостного говнища. На кой он нужен, когда есть TeX, когда есть Mathematica (кстати, типичный пример архитектуры с оторванным от гуя ядром).
Аааааааа, понятно... Тяжёлый случай запущенного красноглазия... Сочувствую...
Кстати, наш софт работает и под Win и под Маc и под Linux. Но последнее мы сделали только потому, что наши инструменты позволяли сделать это автоматически. Если же для этого требовались бы хоть какие-то специальные усилия, то точно не стали бы делать — нафиг нужен этот недорынок (я здесь не про серверный сегмент естественно)...
А> Повторюсь — тем, кто гуйню рисует мышой, надо грабли отрывать без анастезии. Чтоб точно никогда больше не смогли гуйню рисовать.
Так я так не понял, каким инструментом для создания GUI пользуетесь лично вы? )))
А> Следует обратить внимание, что современный гуй под Windows (на XAML) как правило делается именно в текстовом редакторе. Ибо нефиг.
Здравствуйте, m e, Вы писали:
ME>ФП это чистые функции и персистентные структуры данных, не?
ME>о чем тут можно холиварить?
А если язык позволяет писать в стиле "чистые функции и персистентные структуры данных", но не навязывает это как обязательное, то тогда он какой?
Кстати, тут есть и ещё вариант выбора. Есть языки не навязывающие этот стиль, но при этом позволяющие контролируемую компилятором (а не программистом) чистоту — это в D такое есть например. А есть не контролирующие чистоту, но позволяющие все остальные функциональные техники — тот же Питон.
Re[13]: Мифический Haskell
От:
Аноним
Дата:
18.02.12 09:09
Оценка:
Здравствуйте, alex_public, Вы писали:
_>А вообще я обозвал мегамонадами случаи, когда вся программа состоит только из одной монады (main соответственно) и всё. Т.е. никакого "нормального" код рядом. _>Да, а в данном случае вообще странно. Я ожидал увидеть в начале презентации системы сборки что-то типа такого http://www.scons.org/doc/production/HTML/scons-user/c258.html — нормальный пример достигаемого эффекта. А то что вылезло там на экран первым делом (после приветствия)... Даже слов нет...
Мне почему-то кажется, вы не поняли, что там было на первом слайде. Весь код на экране был в монаде Shake, а не IO.
Здравствуйте, alex_public, Вы писали:
_>Да, а в данном случае вообще странно. Я ожидал увидеть в начале презентации системы сборки что-то типа такого http://www.scons.org/doc/production/HTML/scons-user/c258.html — нормальный пример достигаемого эффекта. А то что вылезло там на экран первым делом (после приветствия)... Даже слов нет...
Вообще непонятно, чего ты ожидал от видео, озаглавненного "The theory behind an old version of Shake"
Все равно что говорить — посмотрел лекцию по С++, называется "алгоритмы оптимизации инстанцирования шаблонов", думал там Hello World на плюсах увидеть, а там какие-то ужасы и матан.
Здравствуйте, jazzer, Вы писали:
J>Вообще непонятно, чего ты ожидал от видео, озаглавненного "The theory behind an old version of Shake"
Я вроде показал в ссылке на scons что я ожидал увидеть на первом (!) слайде к системе сборки. Для тривиальных случаев она по любому должна быть проще чем даже make, иначе никакого смысла нет. Это потом, при добавление сложностей к проекту, могут уже появляться какие-то реальные коды на внутреннем языке системы. Я конечно понимаю, что тут доклад был не о "релизной версии комерческого софта", а о принципах, но не до такой же степени. )))
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, vshabanov, Вы писали:
V>>А собирать хаскелл scons-ом -- это сильно, конечно. Две строчки в Makefile заменить на целую систему сборки.
_>Ну как бы там на языке этой сборки файл проекта и получется из двух строчек. Только он при этом умеет заметно больше make файла. Кроссплатоформенность, отслеживание изменение, отслеживание зависимостей и т.д..
У хаскелла есть ghc --make, кроссплатформено, отслеживает изменения и зависимости. Одна строка. Вторая строчка -- опции, чтобы .o/.hi клал в отдельную папку. Я знаком с SCons, для большинства проектов -- это overkill (хотя, до autotools ему еще далеко .
Здравствуйте, vshabanov, Вы писали:
V>У хаскелла есть ghc --make, кроссплатформено, отслеживает изменения и зависимости. Одна строка. Вторая строчка -- опции, чтобы .o/.hi клал в отдельную папку. Я знаком с SCons, для большинства проектов -- это overkill (хотя, до autotools ему еще далеко .
Для Хаскеля возможно, не буду спорить. Я на нём пока компилировал только программки в один файл и на одной платформе и с одним компилятором. Но для C++ допустим это отличный инструмент. Ну и потом частенько бывает в рамках проекта собираются ещё какие-то дополнительные вещи (хелп например или файлы перевода), которые удобно собирать одной общей командой.
Здравствуйте, m e, Вы писали:
V>>Соответственно, список из print-ов -- это именно список из print-ов ([IO ()]), а не список, в процессе вычисления которого что-то мимоходом напечаталось. Соответственно, его еще надо как-то запустить (вычислить), самое простое -- sequence_.
ME>поподробнее насчет различия "список из print-ов -- это именно список из print-ов ([IO ()]), а не список, в процессе вычисления которого что-то мимоходом напечаталось"
В питоне для [f(x) for x in range (a, b)], если f(x) что-то еще печатает на экран, то он и напечатает (ну и какой-нить результат вернет заодно). В хаскеле так нельзя, 'f' должен быть действием (IO ()) и [f x | x <- [a..b]] будет списком таких действий.
ME>скажем, почему список из объектов, у каждого из которых есть функция print, чем-то хуже "списка из print-ов"?
Тем что это объекты с ф-ей print, а не просто ф-ия print сама по себе. Больше сущностей.
V>>Поскольку IO a -- это именно действие (и, между прочим, чистая ф-я), то их можно комбинировать также как и все остальные выражения. Т.е. можно сделать несколько таких спискок-print-ов и сливать их, можно сделать take/drop, да что угодно. Т.е. уровень работы с программой совершенно другой.
ME>каким образом IO a -- чистая ф-я?
Повторяю:
IO a = World -> (a, World)
Был один мир, открыли файл -- стал другой мир. Т.е. openFile -- вполне себе чистая ф-ия, оперирующая над миром.
Другое дело, что до этого World нельзя никак достучаться, т.к. он не открыт в интерфейсе (да и при компиляции тоже пропадает), но идея именно такая -- всё, что есть в хаскеле совершенно чистое, вычисления, порождающие вычисления, а вот main все-таки дергают снаружи, передавая ему World.
_>А если язык позволяет писать в стиле "чистые функции и персистентные структуры данных", но не навязывает это как обязательное, то тогда он какой?
тогда он на правильном пути
однако без поддержки компилятора это не имеет особой ценности
в частности, я считаю, что компилятор должен уметь проверять (хотя бы после объяснений программиста), что комбинация данных нечистых функций и/или нечистых структур данных выдает чистый результат
скажем, можно вспомнить мемоизацию результата чистой функции -- она мутирует данные, но тем не менее не мешает чистоте; с другой стороны, если компилятор не сможет отловить ошибку программиста в *этом* сценарии (когда, скажем, по ошибке из кэша выдается мемоизированный результат не для данной функции, а для другой) -- то это неполноценность языка
_>Кстати, тут есть и ещё вариант выбора. Есть языки не навязывающие этот стиль, но при этом позволяющие контролируемую компилятором (а не программистом) чистоту — это в D такое есть например.
D на правильном пути
_>А есть не контролирующие чистоту, но позволяющие все остальные функциональные техники — тот же Питон.
Здравствуйте, FR, Вы писали:
FR>Здравствуйте, vshabanov, Вы писали:
V>>Я про то, что писать сборочные скрипты на хаскеле в scons не получится.
FR>Странно что еще не написали сборочную систему на хаскеле, или уже?
Наверняка. Всегда находятся люди, которым нужна система сборки.
V>>А собирать хаскелл scons-ом -- это сильно, конечно. Две строчки в Makefile заменить на целую систему сборки.
FR>make не сборочная система?
Сборочная, но не стал бы называть один запускной файл системой.
FR>Две строчки будут в make только для "hello world".
Равно как и в SCons.
FR>Для кроссплатформенных или когда нужна многовариантная сборка scons или аналоги существенно проще чем маке.
Для больших C++ проектов, возможно. Для многих средних проектов make-а более чем достаточно.
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, m e, Вы писали:
ME>>каким образом IO a -- чистая ф-я?
V>Повторяю: V>IO a = World -> (a, World)
Чистая — значит детерминированная + без побочных эффектов. Если по поводу побочных эффектов можно филосовствовать (ну там что они появились в новом экземпляре World), то с детерминированностью тут точно проблемы.
Наоборот, main — чистая. чистая main строит нечистую функцию IO (). И эта нечистая функция-результат чистого main далее выполняется рантаймом. Я это себе так представляю.
Здравствуйте, samius, Вы писали:
ME>>>каким образом IO a -- чистая ф-я?
V>>Повторяю: V>>IO a = World -> (a, World)
S>Чистая — значит детерминированная + без побочных эффектов. Если по поводу побочных эффектов можно филосовствовать (ну там что они появились в новом экземпляре World), то с детерминированностью тут точно проблемы.
Все вполне детерминировано. Просто World каждый раз разный. А если World одинаковый (мир, на момент запуска программы), то и результат всегда будет один.
S>Наоборот, main — чистая. чистая main строит нечистую функцию IO (). И эта нечистая функция-результат чистого main далее выполняется рантаймом. Я это себе так представляю.
И main чистая и IO (), которое она вычисляет, тоже чистое, а вот рантайм уже да, во всю оперирует с миром.
В целом граница достаточно тонкая и условная, важно понять, что концептуально IO -- обычная чистая ф-ия, просто она работает с миром.
Вполне можно сделать какой-нить специальный "мир" для запуска программы, который, например, для getLine всегда будет подставлять одни и те же строки. Тогда (если программа, кроме как через getLine, с миром никак не реагирует) результат поведения программы будет детерминированным.
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
V>>>IO a = World -> (a, World)
S>>Чистая — значит детерминированная + без побочных эффектов. Если по поводу побочных эффектов можно филосовствовать (ну там что они появились в новом экземпляре World), то с детерминированностью тут точно проблемы.
V>Все вполне детерминировано. Просто World каждый раз разный. А если World одинаковый (мир, на момент запуска программы), то и результат всегда будет один.
Это значит что мы фиксируем мир, в котором я введу конкретную строку до того как "выполнилась" функция IO String, являющаяся результатом getLine? И в таком мире, в котором я ввожу лишь эту строку, считаем что функция-результат getLine детерминирована?
Я предпочитаю считать что в одном и том же мире я могу получить разный результат в IO a.
S>>Наоборот, main — чистая. чистая main строит нечистую функцию IO (). И эта нечистая функция-результат чистого main далее выполняется рантаймом. Я это себе так представляю.
V>И main чистая и IO (), которое она вычисляет, тоже чистое, а вот рантайм уже да, во всю оперирует с миром.
V>В целом граница достаточно тонкая и условная, важно понять, что концептуально IO -- обычная чистая ф-ия, просто она работает с миром.
Чистая — значит детерминированная уже сейчас.
V>Вполне можно сделать какой-нить специальный "мир" для запуска программы, который, например, для getLine всегда будет подставлять одни и те же строки. Тогда (если программа, кроме как через getLine, с миром никак не реагирует) результат поведения программы будет детерминированным.
Вот когда будет специальный "мир", тогда и будем считать результат IO a детерминированным. А до тех пор, пока мы подавая один и тот же экземпляр мира будем получать различные результаты — увы.
Здравствуйте, dsorokin, Вы писали:
D>Здравствуйте, alex_public, Вы писали:
_>>Эээ, Lisp — это тоже анти Хаскель? )))
D>Лиспы вполне себе функциональные. Просто тут возникает путаница. Хаскель является языком чистого функционального программирования. Но апологеты почему-то забывают об этой особенности и любят говорить за все функциональное программирование, чем вызывают немалое раздражение.
D>Вообще, нет устоявшегося общепризнанного определения того, что является языком функционального программирования, а что нет. На эту тему можно много холиварить.
In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.
Здравствуйте, vshabanov, Вы писали:
FR>>make не сборочная система?
V>Сборочная, но не стал бы называть один запускной файл системой.
Странный критерий для понятия "система".
По сложности разработки make и scons вполне сопоставимы.
FR>>Две строчки будут в make только для "hello world".
V>Равно как и в SCons.
Да, но в SCons для более сложных случаев строчек будет на порядок меньше.
FR>>Для кроссплатформенных или когда нужна многовариантная сборка scons или аналоги существенно проще чем маке.
V>Для больших C++ проектов, возможно. Для многих средних проектов make-а более чем достаточно.
Я когда активно использовал SCons применял как раз для небольших проектов, работы с ним было на порядок меньше
чем если бы использовал make.
Здравствуйте, samius, Вы писали:
S>Здравствуйте, vshabanov, Вы писали:
V>>Здравствуйте, samius, Вы писали:
V>>>>IO a = World -> (a, World)
S>>>Чистая — значит детерминированная + без побочных эффектов. Если по поводу побочных эффектов можно филосовствовать (ну там что они появились в новом экземпляре World), то с детерминированностью тут точно проблемы.
V>>Все вполне детерминировано. Просто World каждый раз разный. А если World одинаковый (мир, на момент запуска программы), то и результат всегда будет один. S>Это значит что мы фиксируем мир, в котором я введу конкретную строку до того как "выполнилась" функция IO String, являющаяся результатом getLine? И в таком мире, в котором я ввожу лишь эту строку, считаем что функция-результат getLine детерминирована?
Да. Т.к. getLine интересует только ввод строки, если наш "мир" (или рантайм) будет поставлять всегда одну и ту же строку, то getLine будет возвращать один и тот же результат.
S>Я предпочитаю считать что в одном и том же мире я могу получить разный результат в IO a.
Каким образом?
S>>>Наоборот, main — чистая. чистая main строит нечистую функцию IO (). И эта нечистая функция-результат чистого main далее выполняется рантаймом. Я это себе так представляю.
V>>И main чистая и IO (), которое она вычисляет, тоже чистое, а вот рантайм уже да, во всю оперирует с миром.
V>>В целом граница достаточно тонкая и условная, важно понять, что концептуально IO -- обычная чистая ф-ия, просто она работает с миром. S>Чистая — значит детерминированная уже сейчас.
Что значит детерминированная уже сейчас? Если мир будет одним и тем же, то и результаты будут одними и теми же. Другой вопрос, что сложно сделать один и тот же мир для программы.
V>>Вполне можно сделать какой-нить специальный "мир" для запуска программы, который, например, для getLine всегда будет подставлять одни и те же строки. Тогда (если программа, кроме как через getLine, с миром никак не реагирует) результат поведения программы будет детерминированным. S>Вот когда будет специальный "мир", тогда и будем считать результат IO a детерминированным. А до тех пор, пока мы подавая один и тот же экземпляр мира будем получать различные результаты — увы.
С какой стати мы будем получать разный результат? Если интересующая программу часть мира будет одинаковой, то и результат будет одинаковым. Возьми тот же компилятор или БД. Если исходные файлы одинаковые, то и результат будет один и тот же, какими бы императивными компилятор или БД не были.
Здравствуйте, m e, Вы писали:
ME>D на правильном пути
Ага. Вообще из всех языков что я пробовал и по делу и для фана, он мне понравился больше всего.
Хотя это тут оффтопик по идее — он явно не декларативный язык. ))) Но вообще если бы у D была бы возможность линковать C++ библиотеки или же были хотя бы сделаны биндинги к основным нужным библиотекам (у D с этим заметно хуже чем даже у Хаскеля, не говоря уже про Питон), то я бы перешёл на него в большинстве мест. А так, пока только надеюсь что в будущем он всё же получит толчок в развитие инфраструктуры...
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
S>>Это значит что мы фиксируем мир, в котором я введу конкретную строку до того как "выполнилась" функция IO String, являющаяся результатом getLine? И в таком мире, в котором я ввожу лишь эту строку, считаем что функция-результат getLine детерминирована?
V>Да. Т.к. getLine интересует только ввод строки, если наш "мир" (или рантайм) будет поставлять всегда одну и ту же строку, то getLine будет возвращать один и тот же результат.
Ключевое тут "если". А в том что оно так и будет, уверенности никакой нет.
S>>Я предпочитаю считать что в одном и том же мире я могу получить разный результат в IO a.
V>Каким образом?
Дело в том, что миру фиолетово, зафиксировал ли я его. Результат взаимодействия с ним недетерминирован. Можно сказать что по определению.
V>>>В целом граница достаточно тонкая и условная, важно понять, что концептуально IO -- обычная чистая ф-ия, просто она работает с миром. S>>Чистая — значит детерминированная уже сейчас.
V>Что значит детерминированная уже сейчас? Если мир будет одним и тем же, то и результаты будут одними и теми же. Другой вопрос, что сложно сделать один и тот же мир для программы.
Зачем сделать? Если считать что IO a есть World -> (a, World), то можно написать (представить) функцию
getLine2:: IO (a, a)
getLine2 w = ((a1, a2), w1)
where (a1, w1) = getLine w
(a2, _ ) = getLine w
Предполагаемая чистота IO a должна гарантировать что такую функцию написать возможно и что a1 будет всегда совпадать с a2.
Однако, боюсь что это невозможно даже теоретически. Определение чистоты утверждает что любое взаимодействие с I/O нечисто. Безотносительно того, считаем ли мы мир аргументом функции или нет.
S>>Вот когда будет специальный "мир", тогда и будем считать результат IO a детерминированным. А до тех пор, пока мы подавая один и тот же экземпляр мира будем получать различные результаты — увы.
V>С какой стати мы будем получать разный результат? Если интересующая программу часть мира будет одинаковой, то и результат будет одинаковым.
А с чего ему быть одинаковым? Только с того что мы о нем так подумали? V>Возьми тот же компилятор или БД. Если исходные файлы одинаковые, то и результат будет один и тот же, какими бы императивными компилятор или БД не были.
Работу компилятора легко представить деретминированной функцией. С БД уже сложнее, т.к. в запросе могут быть недетерминированные функции типа GETDATE.
Здравствуйте, samius, Вы писали:
S>Вот когда будет специальный "мир", тогда и будем считать результат IO a детерминированным. А до тех пор, пока мы подавая один и тот же экземпляр мира будем получать различные результаты — увы.
Эк Вы сурово. Вон в ООП вообще начинают с пустого мира, где даже дух божий не летает над водой -- и не пищат.
Здравствуйте, alexlz, Вы писали:
A>Здравствуйте, samius, Вы писали:
S>>Вот когда будет специальный "мир", тогда и будем считать результат IO a детерминированным. А до тех пор, пока мы подавая один и тот же экземпляр мира будем получать различные результаты — увы. A>Эк Вы сурово. Вон в ООП вообще начинают с пустого мира, где даже дух божий не летает над водой -- и не пищат.
В хаскеле мир не полнее
Здравствуйте, samius, Вы писали:
S>>>Это значит что мы фиксируем мир, в котором я введу конкретную строку до того как "выполнилась" функция IO String, являющаяся результатом getLine? И в таком мире, в котором я ввожу лишь эту строку, считаем что функция-результат getLine детерминирована?
V>>Да. Т.к. getLine интересует только ввод строки, если наш "мир" (или рантайм) будет поставлять всегда одну и ту же строку, то getLine будет возвращать один и тот же результат. S>Ключевое тут "если". А в том что оно так и будет, уверенности никакой нет.
Почему?
S>>>Я предпочитаю считать что в одном и том же мире я могу получить разный результат в IO a.
V>>Каким образом? S>Дело в том, что миру фиолетово, зафиксировал ли я его. Результат взаимодействия с ним недетерминирован. Можно сказать что по определению.
По какому такому определению? У тебя внешний мир прямо что-то совершенно неопределенное, но реально на любую программу влияет ограниченный набор воздействий, и если эти воздействия будут одинаковыми, то и поведение программы будет одинаковым.
В обычных языках мир учитывается только неявно, по-этому и вылезают всякие недетерминированности. В хаскеле мир учитывается явно, что и делает ф-ии, оперирующие с ним (т.е. IO a) чистыми. Другое дело, что сам исходный мир все время разный и результат этих чистых ф-ий разный. Но важно, что сама по себе IO является чистой ф-ией над миром, хотя и выглядит как "какой-то кривой императивный язык".
V>>>>В целом граница достаточно тонкая и условная, важно понять, что концептуально IO -- обычная чистая ф-ия, просто она работает с миром. S>>>Чистая — значит детерминированная уже сейчас.
V>>Что значит детерминированная уже сейчас? Если мир будет одним и тем же, то и результаты будут одними и теми же. Другой вопрос, что сложно сделать один и тот же мир для программы. S>Зачем сделать? Если считать что IO a есть World -> (a, World), то можно написать (представить) функцию S>
S>getLine2:: IO (a, a)
S>getLine2 w = ((a1, a2), w1)
S> where (a1, w1) = getLine w
S> (a2, _ ) = getLine w
S>
S>Предполагаемая чистота IO a должна гарантировать что такую функцию написать возможно и что a1 будет всегда совпадать с a2.
Было бы запросто возможно, если был бы свой ограниченный мир, который мы можем контролировать. Однако прогам надо работать с внешним миром, по-этому интерфейс к внутренностям IO закрыт и с ним можно работать только через монадный bind.
S>Однако, боюсь что это невозможно даже теоретически. Определение чистоты утверждает что любое взаимодействие с I/O нечисто. Безотносительно того, считаем ли мы мир аргументом функции или нет.
Там, кстати, не только теория. Работу с внешним миром из Хаскеля (IO/ffi/exceptions/concurrency) я изучал именно по ней. Да и когда, несколько лет спустя, почитал главу про семантику даже что-то понял. Т.е. по теоретической части там не сильный матан (все-таки Энтони Хоар помогал).
S>>>Вот когда будет специальный "мир", тогда и будем считать результат IO a детерминированным. А до тех пор, пока мы подавая один и тот же экземпляр мира будем получать различные результаты — увы.
V>>С какой стати мы будем получать разный результат? Если интересующая программу часть мира будет одинаковой, то и результат будет одинаковым. S>А с чего ему быть одинаковым? Только с того что мы о нем так подумали?
Именно
V>>Возьми тот же компилятор или БД. Если исходные файлы одинаковые, то и результат будет один и тот же, какими бы императивными компилятор или БД не были. S>Работу компилятора легко представить деретминированной функцией. С БД уже сложнее, т.к. в запросе могут быть недетерминированные функции типа GETDATE.
Хорошо, если GETDATE будет всегда возвращать одни и те же результаты? Полчуается, что внутри компилятора или БД будут всякие do/IO, но при этом они все равно будут чистыми ф-иями.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, dsorokin, Вы писали:
D>>Вообще, нет устоявшегося общепризнанного определения того, что является языком функционального программирования, а что нет. На эту тему можно много холиварить.
VE>https://en.wikipedia.org/wiki/Functional_programming
VE>In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.
VE>Всё предельно чётко.
Мне больше нравится "определение" из книги Hutton "Programming in Haskell", 2005:
"What is functional programming? Opinions differ, and it is difficult to give a
precise definition. Generally speaking, however, functional programming can
be viewed as a style of programming in which the basic method of computation
is the application of functions to arguments. In turn, a functional programming
language is one that supports and encourages the functional style."
Лично для меня Common Lisp вполне подходит под это определение.
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
V>>>Да. Т.к. getLine интересует только ввод строки, если наш "мир" (или рантайм) будет поставлять всегда одну и ту же строку, то getLine будет возвращать один и тот же результат. S>>Ключевое тут "если". А в том что оно так и будет, уверенности никакой нет.
V>Почему?
Потому что мир отличается от того, что мы вообразили о нём.
S>>>>Я предпочитаю считать что в одном и том же мире я могу получить разный результат в IO a.
V>>>Каким образом? S>>Дело в том, что миру фиолетово, зафиксировал ли я его. Результат взаимодействия с ним недетерминирован. Можно сказать что по определению.
V>По какому такому определению? У тебя внешний мир прямо что-то совершенно неопределенное, но реально на любую программу влияет ограниченный набор воздействий, и если эти воздействия будут одинаковыми, то и поведение программы будет одинаковым. http://en.wikipedia.org/wiki/Pure_function
In computer programming, a function may be described as pure if both these statements about the function hold:
The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change as program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices.
Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.
Но ты предлагаешь забыть о детерминированности лишь потому как мир можно представить внутренним полностью контроллируемым объектом
V>В обычных языках мир учитывается только неявно, по-этому и вылезают всякие недетерминированности. В хаскеле мир учитывается явно, что и делает ф-ии, оперирующие с ним (т.е. IO a) чистыми. Другое дело, что сам исходный мир все время разный и результат этих чистых ф-ий разный. Но важно, что сама по себе IO является чистой ф-ией над миром, хотя и выглядит как "какой-то кривой императивный язык".
Мне кажется, что ты неправильно себе это представляешь. Функции в хаскеле не оперируют с миром (я о тех, которые не считаются бэкдорами типа unsafePerformIO). Функции в хаскеле оперируют action-ами. Как оперирующие action-ами, они формально чисты. Но сами action-ы не являются чистыми.
S>>Зачем сделать? Если считать что IO a есть World -> (a, World), то можно написать (представить) функцию S>>
S>>getLine2:: IO (a, a)
S>>getLine2 w = ((a1, a2), w1)
S>> where (a1, w1) = getLine w
S>> (a2, _ ) = getLine w
S>>
S>>Предполагаемая чистота IO a должна гарантировать что такую функцию написать возможно и что a1 будет всегда совпадать с a2.
V>Было бы запросто возможно, если был бы свой ограниченный мир, который мы можем контролировать. Однако прогам надо работать с внешним миром, по-этому интерфейс к внутренностям IO закрыт и с ним можно работать только через монадный bind.
То есть ты говоришь что функции чисты, если представить такой хороший внутренний мир. Но тут же говоришь что им приходится работать с внешним миром. Тогда наверное они не так уж и чисты, как ты предлагаешь считать?
S>>Однако, боюсь что это невозможно даже теоретически. Определение чистоты утверждает что любое взаимодействие с I/O нечисто. Безотносительно того, считаем ли мы мир аргументом функции или нет.
V>Подпробнее про теорию можно почитать здесь http://research.microsoft.com/en-us/um/people/simonpj/papers/marktoberdorf/mark.pdf
в этой теории написано примерно то, о чем и я:
getCharis an I/O action that, when performed, reads a character from the standard input (thereby hav-
ing an effect on the world outside the program), and returns it to the program as the result of the action.
putCharis a function that takes a character and returns an action that, when performed, prints the char-
acter on the standard output (its effect on the external world), and returns the trivial value().
Т.е. когда action-ы перфомятся, тогда они взаимодействуют с миром, плодят эффекты, получают недетерминированные результаты. Но программе на хаскеле нет нужды их перфомить вручную. Это сделает рантам, получив экшн как результат чистого вычисления main и подав ему мир.
V>Там, кстати, не только теория. Работу с внешним миром из Хаскеля (IO/ffi/exceptions/concurrency) я изучал именно по ней. Да и когда, несколько лет спустя, почитал главу про семантику даже что-то понял. Т.е. по теоретической части там не сильный матан (все-таки Энтони Хоар помогал).
Угу.
V>>>С какой стати мы будем получать разный результат? Если интересующая программу часть мира будет одинаковой, то и результат будет одинаковым. S>>А с чего ему быть одинаковым? Только с того что мы о нем так подумали?
V>Именно
А что нам мешает так подумать с любой функцией в императивном программировании? Ну и что что мир не фигурирует в параметрах функции, считаем его зафиксированным, получаем что любая функция детерминирована
S>>Работу компилятора легко представить деретминированной функцией. С БД уже сложнее, т.к. в запросе могут быть недетерминированные функции типа GETDATE.
V>Хорошо, если GETDATE будет всегда возвращать одни и те же результаты? Полчуается, что внутри компилятора или БД будут всякие do/IO, но при этом они все равно будут чистыми ф-иями.
Если GETDATE будет возвращать один и тот же результат, нафиг она нужна, ее можно заменить константой!
Здравствуйте, m e, Вы писали:
ME>"может быть" здесь я употребил в смысле "возможно, что... но доказать не могу"
Ага. Т.е. это еще веселее чем в анекдоте про нашедшиеся ложки и оставшийся осадок: тут ложки даже и не терялись, но есть мнение, что потеряются.
ME>что значит "полноценность"?
В данном случае это означает, что код, который должен работать на языке с параметрическим полиморфизмом действительно работает.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, alex_public, Вы писали:
_>Ну да, в общем то не получается. Иначе бы мы давно на нём сидели, а не на C++. Но не получается совсем не по причинам охвата малого количества парадигм. )
В том числе и по этим причинам.
_>Остальные парадигмы.
Поддержка ФП, как я уже и говорил, ограничена. А ООП там не то чтобы хуже чем обычно, но странное. Любители ООП не оценят.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, korvin_, Вы писали:
_>Откуда такое мнение?
От схемеров и коммон-лисперов.
_>Мне больше нравятся критерии, озвученные в SICP: нормальный порядок вычислений и декомпозиция на потоки. Т.о. к (чисто) функциональным языкам можно отнести Miranda, Clean и Haskell например.
Ну, в моей двухосевой классификации (чисто) функциональные языки — декларативные, функциональные.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Разумеется, старые трюки там тоже есть, но ими дело не ограничивается. Монадический I/O и классы типов, например — это именно хаскельные инновации.
ME>вообще похоже хаскель появился только потому, что автор миранды хотел ее оставить пропраетарной
Хаскель появился по другой причине. А по этой причине он не является непосредственным развитием Миранды.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
S>>>>>Я предпочитаю считать что в одном и том же мире я могу получить разный результат в IO a.
V>>>>Каким образом? S>>>Дело в том, что миру фиолетово, зафиксировал ли я его. Результат взаимодействия с ним недетерминирован. Можно сказать что по определению.
V>>По какому такому определению? У тебя внешний мир прямо что-то совершенно неопределенное, но реально на любую программу влияет ограниченный набор воздействий, и если эти воздействия будут одинаковыми, то и поведение программы будет одинаковым. S>http://en.wikipedia.org/wiki/Pure_function S>
S>In computer programming, a function may be described as pure if both these statements about the function hold:
S> The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change as program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices.
S> Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.
S>Но ты предлагаешь забыть о детерминированности лишь потому как мир можно представить внутренним полностью контроллируемым объектом
Если внешний мир является параметром ф-ии и, измененный "побочным" эффектом, мир возвращается результатом (а больше ничего нигде не меняется), то ф-ия является чистой. У нее нет побочных эффектов -- она работает с миром как с обычным аргументом и результатом, а не опосредованно.
V>>В обычных языках мир учитывается только неявно, по-этому и вылезают всякие недетерминированности. В хаскеле мир учитывается явно, что и делает ф-ии, оперирующие с ним (т.е. IO a) чистыми. Другое дело, что сам исходный мир все время разный и результат этих чистых ф-ий разный. Но важно, что сама по себе IO является чистой ф-ией над миром, хотя и выглядит как "какой-то кривой императивный язык". S>Мне кажется, что ты неправильно себе это представляешь. Функции в хаскеле не оперируют с миром (я о тех, которые не считаются бэкдорами типа unsafePerformIO). Функции в хаскеле оперируют action-ами. Как оперирующие action-ами, они формально чисты. Но сами action-ы не являются чистыми.
Я в самом начале приводил ссылку на сорцы ghc-шной библиотеки, где, не считая некоторых внутренних оптимизаций, IO a = World -> (a, World). Т.е. эти самые action-ы тоже обычные ф-ии. И также формально чисты, т.к. явно принимают мир в качестве аргумента и возвращают измененный мир, не меняя ничего кроме.
S>>>Зачем сделать? Если считать что IO a есть World -> (a, World), то можно написать (представить) функцию S>>>
S>>>getLine2:: IO (a, a)
S>>>getLine2 w = ((a1, a2), w1)
S>>> where (a1, w1) = getLine w
S>>> (a2, _ ) = getLine w
S>>>
S>>>Предполагаемая чистота IO a должна гарантировать что такую функцию написать возможно и что a1 будет всегда совпадать с a2.
V>>Было бы запросто возможно, если был бы свой ограниченный мир, который мы можем контролировать. Однако прогам надо работать с внешним миром, по-этому интерфейс к внутренностям IO закрыт и с ним можно работать только через монадный bind. S>То есть ты говоришь что функции чисты, если представить такой хороший внутренний мир. Но тут же говоришь что им приходится работать с внешним миром. Тогда наверное они не так уж и чисты, как ты предлагаешь считать?
Поинт в том, что в Хаскеле даже монада IO, которая кажется нечистой, также является чистой ф-ей. Просто у нее аргумент необычный ), но формально она чистая.
S>>>Однако, боюсь что это невозможно даже теоретически. Определение чистоты утверждает что любое взаимодействие с I/O нечисто. Безотносительно того, считаем ли мы мир аргументом функции или нет.
V>>Подпробнее про теорию можно почитать здесь http://research.microsoft.com/en-us/um/people/simonpj/papers/marktoberdorf/mark.pdf S>в этой теории написано примерно то, о чем и я: S>
S>getCharis an I/O action that, when performed, reads a character from the standard input (thereby hav-
S>ing an effect on the world outside the program), and returns it to the program as the result of the action.
S>putCharis a function that takes a character and returns an action that, when performed, prints the char-
S>acter on the standard output (its effect on the external world), and returns the trivial value().
S>Т.е. когда action-ы перфомятся, тогда они взаимодействуют с миром, плодят эффекты, получают недетерминированные результаты. Но программе на хаскеле нет нужды их перфомить вручную. Это сделает рантам, получив экшн как результат чистого вычисления main и подав ему мир.
Ну и? Сама-то IO для хаскелла так и остается чистой ф-ией.
V>>>>С какой стати мы будем получать разный результат? Если интересующая программу часть мира будет одинаковой, то и результат будет одинаковым. S>>>А с чего ему быть одинаковым? Только с того что мы о нем так подумали?
V>>Именно S>А что нам мешает так подумать с любой функцией в императивном программировании? Ну и что что мир не фигурирует в параметрах функции, считаем его зафиксированным, получаем что любая функция детерминирована
Ничего не мешает. Важно то, что мир не является здесь параметром. Значит ф-ия зависит не только от своих аргументов, значит нечистая.
S>>>Работу компилятора легко представить деретминированной функцией. С БД уже сложнее, т.к. в запросе могут быть недетерминированные функции типа GETDATE.
V>>Хорошо, если GETDATE будет всегда возвращать одни и те же результаты? Полчуается, что внутри компилятора или БД будут всякие do/IO, но при этом они все равно будут чистыми ф-иями. S>Если GETDATE будет возвращать один и тот же результат, нафиг она нужна, ее можно заменить константой!
Хорошо, запускаем БД вместе с операционкой в эмуляторе железа. Хоп и всегда одинаковый результат )
Тут вопрос не в том, нужна/не нужна GETDATE. Важно, что принципиально для любой "нечистой" ф-ии (включая любую программу, набитую нечиствми ф-иями) можно сделать такой мир, что она будет выдавать один и тот же результат и менять мир одним и тем же образом. Тогда, если этот мир является параметром и результатом, то ф-ия является чистой (просто она мир использует/меняет, но он всего лишь один из агрументов).
Здравствуйте, Klapaucius, Вы писали:
K>В месте ссылочной прозрачности. И я не считаю, что "более декларативен" осмысленное словосочетание. Язык или декларативен или нет (императивен).
Слово "декларативный" все-таки тут плохо подходит, у него слишком много значений, ты слишком сужаешь его смысл.
Ни к чему кроме терминологических споров это не приведет.
Хотя термин подобрать сложно, "аппликативный" вроде тоже не совсем подходит, остается только громоздкое "чисто функциональный".
Здравствуйте, FR, Вы писали:
FR>Слово "декларативный" все-таки тут плохо подходит
Не согласен, подходит хорошо.
FR>, у него слишком много значений, ты слишком сужаешь его смысл.
Я не зауживаю его смысл. Лямбда-исчисление декларативно (это, по-моему, очевидное следствие теоремы Черча-Россера) в числе прочего. Но расширения, нарушающие ссылочную прозрачность типа мл-ных рефов делают его недекларативным — т.е. императивным. Поэтому хаскель, в котором таких расширений нет декларативен, а смл — императивен. Аналогично и с логическими языками: основа пролога — дизъюнкты Хорна — декларативна, но расширение красными катами делает пролог императивным языком (в отличие от меркюри, например).
FR>Ни к чему кроме терминологических споров это не приведет.
Но все прочие разделения на декларативное и императивное базируются исключительно на "восприятии программы программистом" т.е. и декларативным и императивным и даже и тем и другим одновременно можно назвать вообще все что угодно.
FR>Хотя термин подобрать сложно, "аппликативный" вроде тоже не совсем подходит, остается только громоздкое "чисто функциональный".
Но декларативными бывают не только чисто функциональные языки! С.м. выше.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
S>>In computer programming, a function may be described as pure if both these statements about the function hold:
S>> The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change as program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices. S>> Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices. S>>[/q]
V>Если внешний мир является параметром ф-ии и, измененный "побочным" эффектом, мир возвращается результатом (а больше ничего нигде не меняется), то ф-ия является чистой. У нее нет побочных эффектов -- она работает с миром как с обычным аргументом и результатом, а не опосредованно.
Ответь, дважды примененный результат getLine к одному и тому же миру даст одинаковый резльутат? Только без всяких "если".
S>>Мне кажется, что ты неправильно себе это представляешь. Функции в хаскеле не оперируют с миром (я о тех, которые не считаются бэкдорами типа unsafePerformIO). Функции в хаскеле оперируют action-ами. Как оперирующие action-ами, они формально чисты. Но сами action-ы не являются чистыми.
V>Я в самом начале приводил ссылку на сорцы ghc-шной библиотеки, где, не считая некоторых внутренних оптимизаций, IO a = World -> (a, World). Т.е. эти самые action-ы тоже обычные ф-ии. И также формально чисты, т.к. явно принимают мир в качестве аргумента и возвращают измененный мир, не меняя ничего кроме.
Они не могут быть формально чисты, т.к. кроме экземпляра World, портят и остальной мир, в том числе содержимое консоли. Кроме как от экземпляра World, зависят еще от того, что пользователь введет в консоль. И как меня не уговаривай, экземпляр World не имеет в себе отражения того, что происходит в реальном мире.
S>>То есть ты говоришь что функции чисты, если представить такой хороший внутренний мир. Но тут же говоришь что им приходится работать с внешним миром. Тогда наверное они не так уж и чисты, как ты предлагаешь считать?
V>Поинт в том, что в Хаскеле даже монада IO, которая кажется нечистой, также является чистой ф-ей. Просто у нее аргумент необычный ), но формально она чистая.
Если так, то функция из C
World *printf(LPCSTR, World *)
тоже становится формально чистой
И вообще тогда это определение чистоты никому особо не нужно, т.к. любую нечистоту можно убрать добавлением фиктивного параметра.
S>>Т.е. когда action-ы перфомятся, тогда они взаимодействуют с миром, плодят эффекты, получают недетерминированные результаты. Но программе на хаскеле нет нужды их перфомить вручную. Это сделает рантам, получив экшн как результат чистого вычисления main и подав ему мир.
V>Ну и? Сама-то IO для хаскелла так и остается чистой ф-ией.
Нет, и в указанной тобой книге об этом написано. В процитированном мной абзаце не написано о формальной чистоте, но написано что выполнение действий зависит от мира и оставляет эффект в мире в external world или world outside the program. Надеюсь, что очевидно, что они пишут не о World, который есть внутренность программы.
S>>А что нам мешает так подумать с любой функцией в императивном программировании? Ну и что что мир не фигурирует в параметрах функции, считаем его зафиксированным, получаем что любая функция детерминирована
V>Ничего не мешает. Важно то, что мир не является здесь параметром. Значит ф-ия зависит не только от своих аргументов, значит нечистая.
Выше я уже предложил добавить в printf фиктивный параметр. Она по-твоему стала формально чистой?
S>>Если GETDATE будет возвращать один и тот же результат, нафиг она нужна, ее можно заменить константой!
V>Хорошо, запускаем БД вместе с операционкой в эмуляторе железа. Хоп и всегда одинаковый результат )
Я по умолчанию функции рассматриваю не в эмуляторе, где она дает одинаковый результат.
V>Тут вопрос не в том, нужна/не нужна GETDATE. Важно, что принципиально для любой "нечистой" ф-ии (включая любую программу, набитую нечиствми ф-иями) можно сделать такой мир, что она будет выдавать один и тот же результат и менять мир одним и тем же образом. Тогда, если этот мир является параметром и результатом, то ф-ия является чистой (просто она мир использует/меняет, но он всего лишь один из агрументов).
Тут как раз важно то, что нечистые функции взаимодействуют с существующим миром, а не с тем, который можно сделать. Мысленный перенос мира в границы программы не позволяет просто так задвинуть на чистоту. Ведь после этого миру программы все равно придется взаимодействовать с миром вне программы. Т.е. мысленный перенос мира не влияет на I/O через границы программы.
Здравствуйте, samius, Вы писали:
V>>Если внешний мир является параметром ф-ии и, измененный "побочным" эффектом, мир возвращается результатом (а больше ничего нигде не меняется), то ф-ия является чистой. У нее нет побочных эффектов -- она работает с миром как с обычным аргументом и результатом, а не опосредованно. S>Ответь, дважды примененный результат getLine к одному и тому же миру даст одинаковый резльутат? Только без всяких "если".
Да
S>>>Мне кажется, что ты неправильно себе это представляешь. Функции в хаскеле не оперируют с миром (я о тех, которые не считаются бэкдорами типа unsafePerformIO). Функции в хаскеле оперируют action-ами. Как оперирующие action-ами, они формально чисты. Но сами action-ы не являются чистыми.
V>>Я в самом начале приводил ссылку на сорцы ghc-шной библиотеки, где, не считая некоторых внутренних оптимизаций, IO a = World -> (a, World). Т.е. эти самые action-ы тоже обычные ф-ии. И также формально чисты, т.к. явно принимают мир в качестве аргумента и возвращают измененный мир, не меняя ничего кроме. S>Они не могут быть формально чисты, т.к. кроме экземпляра World, портят и остальной мир, в том числе содержимое консоли. Кроме как от экземпляра World, зависят еще от того, что пользователь введет в консоль. И как меня не уговаривай, экземпляр World не имеет в себе отражения того, что происходит в реальном мире.
S>>>То есть ты говоришь что функции чисты, если представить такой хороший внутренний мир. Но тут же говоришь что им приходится работать с внешним миром. Тогда наверное они не так уж и чисты, как ты предлагаешь считать?
V>>Поинт в том, что в Хаскеле даже монада IO, которая кажется нечистой, также является чистой ф-ей. Просто у нее аргумент необычный ), но формально она чистая. S>Если так, то функция из C S>
S>World *printf(LPCSTR, World *)
S>
S>тоже становится формально чистой
Да.
S>И вообще тогда это определение чистоты никому особо не нужно, т.к. любую нечистоту можно убрать добавлением фиктивного параметра.
Только если в Си всегда можно подставить NULL или два раза вызвать на одном и том же "мире", то в хаскеле так нельзя. Мир подается аргументом только в main, и работать с ним можно только через bind. В результате чего мы продолжаем работать в чисто функциональном haskell, но при этом можем писать императивный код.
Т.е. в Си, добавив world к printf ничего не добъешься, т.к. язык не позволяет правильно ограничить его использование, а в хаскеле все чики-пики.
S>>>Т.е. когда action-ы перфомятся, тогда они взаимодействуют с миром, плодят эффекты, получают недетерминированные результаты. Но программе на хаскеле нет нужды их перфомить вручную. Это сделает рантам, получив экшн как результат чистого вычисления main и подав ему мир.
V>>Ну и? Сама-то IO для хаскелла так и остается чистой ф-ией. S>Нет, и в указанной тобой книге об этом написано. В процитированном мной абзаце не написано о формальной чистоте, но написано что выполнение действий зависит от мира и оставляет эффект в мире в external world или world outside the program. Надеюсь, что очевидно, что они пишут не о World, который есть внутренность программы.
Но в программе мир моделируется тем самым World.
S>>>А что нам мешает так подумать с любой функцией в императивном программировании? Ну и что что мир не фигурирует в параметрах функции, считаем его зафиксированным, получаем что любая функция детерминирована
V>>Ничего не мешает. Важно то, что мир не является здесь параметром. Значит ф-ия зависит не только от своих аргументов, значит нечистая. S>Выше я уже предложил добавить в printf фиктивный параметр. Она по-твоему стала формально чистой?
Если она ничего кроме этого праметра больше не трогает, то стала. Но, т.к. параметр по-сути фиктивный и меняется реальный мир, то, чтобы сохранить чистую семантику, требуется чтобы этот параметр нельзя было использовать более одного раза. Тут то и вылезают монады с do-нотацией, которые позволяют это удобно сделать.
S>>>Если GETDATE будет возвращать один и тот же результат, нафиг она нужна, ее можно заменить константой!
V>>Хорошо, запускаем БД вместе с операционкой в эмуляторе железа. Хоп и всегда одинаковый результат ) S>Я по умолчанию функции рассматриваю не в эмуляторе, где она дает одинаковый результат.
V>>Тут вопрос не в том, нужна/не нужна GETDATE. Важно, что принципиально для любой "нечистой" ф-ии (включая любую программу, набитую нечиствми ф-иями) можно сделать такой мир, что она будет выдавать один и тот же результат и менять мир одним и тем же образом. Тогда, если этот мир является параметром и результатом, то ф-ия является чистой (просто она мир использует/меняет, но он всего лишь один из агрументов). S>Тут как раз важно то, что нечистые функции взаимодействуют с существующим миром, а не с тем, который можно сделать. Мысленный перенос мира в границы программы не позволяет просто так задвинуть на чистоту. Ведь после этого миру программы все равно придется взаимодействовать с миром вне программы. Т.е. мысленный перенос мира не влияет на I/O через границы программы.
Здравствуйте, Klapaucius, Вы писали:
K>Аналогично и с логическими языками: основа пролога — дизъюнкты Хорна — декларативна, но расширение красными катами делает пролог императивным языком (в отличие от меркюри, например).
... K>Но декларативными бывают не только чисто функциональные языки! С.м. выше.
Кстати, Mercury обычно называют фунционально-логическим языком. И насколько я помню он потерял значительную часть мощи Пролога в этой своей мутации. Поэтому я отбросил его, когда я пытался найти современного наследника Пролога.
Вот здесь http://habrahabr.ru/blogs/prolog/122147/ например разница заметна. Там в начале статьи как раз ссылки на другие варианты есть... И можно сравнить Mercury vs Prolog vs Haskell.
Здравствуйте, Klapaucius, Вы писали:
K>От схемеров и коммон-лисперов.
Скорее от любителей холиваров, на них можно не обращать внимания. =)
K>Ну, в моей двухосевой классификации (чисто) функциональные языки — декларативные, функциональные.
По твоей классификации масло -- жирное, масляное. Так что ли?
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
S>>Ответь, дважды примененный результат getLine к одному и тому же миру даст одинаковый резльутат? Только без всяких "если".
V>Да
Это учитывая текущую "реализацию" мира?
S>>Если так, то функция из C S>>
S>>World *printf(LPCSTR, World *)
S>>
S>>тоже становится формально чистой
V>Да.
Не согласен
S>>И вообще тогда это определение чистоты никому особо не нужно, т.к. любую нечистоту можно убрать добавлением фиктивного параметра.
V>Только если в Си всегда можно подставить NULL или два раза вызвать на одном и том же "мире", то в хаскеле так нельзя. Мир подается аргументом только в main, и работать с ним можно только через bind. В результате чего мы продолжаем работать в чисто функциональном haskell, но при этом можем писать императивный код.
V>Т.е. в Си, добавив world к printf ничего не добъешься, т.к. язык не позволяет правильно ограничить его использование, а в хаскеле все чики-пики.
В хаскеле мы работаем чисто функционально не от того что мир нельзя подать дважды в один метод. А от того, что вручную не форсируем выполенение нечистых дейсвтвий, а лишь комбинируем нечистые действия чистым образом.
S>>Нет, и в указанной тобой книге об этом написано. В процитированном мной абзаце не написано о формальной чистоте, но написано что выполнение действий зависит от мира и оставляет эффект в мире в external world или world outside the program. Надеюсь, что очевидно, что они пишут не о World, который есть внутренность программы.
V>Но в программе мир моделируется тем самым World.
Это не модель. Это затычка, dummy.
При этом, как бы мир не моделировался в самой программе, программе все еще приходится взаимодействовать с миром вне программы. И любое такое взаимодействие нечисто по определению.
S>>Выше я уже предложил добавить в printf фиктивный параметр. Она по-твоему стала формально чистой?
V>Если она ничего кроме этого праметра больше не трогает, то стала. Но, т.к. параметр по-сути фиктивный и меняется реальный мир, то, чтобы сохранить чистую семантику, требуется чтобы этот параметр нельзя было использовать более одного раза. Тут то и вылезают монады с do-нотацией, которые позволяют это удобно сделать.
Монады — лишь способ протащить мир по вычислениям. В printf можно было бы ограничить использование мира другим способом, например проверкой его счетчика использований и возвратом кода ошибки (или исключения в C++). Механизм ограничения числа использований параметра вообще не имеет никакого отношения к определению чистоты.
Здравствуйте, samius, Вы писали:
S>Здравствуйте, vshabanov, Вы писали:
V>>Здравствуйте, samius, Вы писали:
S>>>Ответь, дважды примененный результат getLine к одному и тому же миру даст одинаковый резльутат? Только без всяких "если".
V>>Да S>Это учитывая текущую "реализацию" мира?
Нет
S>>>Если так, то функция из C S>>>
S>>>World *printf(LPCSTR, World *)
S>>>
S>>>тоже становится формально чистой
V>>Да. S>Не согласен
Ну что ж, ты конечно имеешь право на собственное мнение )
S>>>И вообще тогда это определение чистоты никому особо не нужно, т.к. любую нечистоту можно убрать добавлением фиктивного параметра.
V>>Только если в Си всегда можно подставить NULL или два раза вызвать на одном и том же "мире", то в хаскеле так нельзя. Мир подается аргументом только в main, и работать с ним можно только через bind. В результате чего мы продолжаем работать в чисто функциональном haskell, но при этом можем писать императивный код.
V>>Т.е. в Си, добавив world к printf ничего не добъешься, т.к. язык не позволяет правильно ограничить его использование, а в хаскеле все чики-пики.
S>В хаскеле мы работаем чисто функционально не от того что мир нельзя подать дважды в один метод. А от того, что вручную не форсируем выполенение нечистых дейсвтвий, а лишь комбинируем нечистые действия чистым образом.
Да да, конечно )
S>>>Нет, и в указанной тобой книге об этом написано. В процитированном мной абзаце не написано о формальной чистоте, но написано что выполнение действий зависит от мира и оставляет эффект в мире в external world или world outside the program. Надеюсь, что очевидно, что они пишут не о World, который есть внутренность программы.
V>>Но в программе мир моделируется тем самым World. S>Это не модель. Это затычка, dummy. S>При этом, как бы мир не моделировался в самой программе, программе все еще приходится взаимодействовать с миром вне программы. И любое такое взаимодействие нечисто по определению.
Безусловно )
S>>>Выше я уже предложил добавить в printf фиктивный параметр. Она по-твоему стала формально чистой?
V>>Если она ничего кроме этого праметра больше не трогает, то стала. Но, т.к. параметр по-сути фиктивный и меняется реальный мир, то, чтобы сохранить чистую семантику, требуется чтобы этот параметр нельзя было использовать более одного раза. Тут то и вылезают монады с do-нотацией, которые позволяют это удобно сделать. S>Монады — лишь способ протащить мир по вычислениям. В printf можно было бы ограничить использование мира другим способом, например проверкой его счетчика использований и возвратом кода ошибки (или исключения в C++). Механизм ограничения числа использований параметра вообще не имеет никакого отношения к определению чистоты.
Возврат ошибки/исключение на том же мире -- уже нечистая ф-ия.
Покури unique types что-ли. Может так станет понятно про то, что при гарантии однократного использования параметра/результата сохраняется ссылочная прозрачность.
Здравствуйте, korvin_, Вы писали:
_>Скорее от любителей холиваров, на них можно не обращать внимания. =)
А что, кто-то не любит холиворы? Быть такого не может!
_>По твоей классификации масло -- жирное, масляное. Так что ли?
Разве ж это я виноват, что бывает еще и масло обезжиренное? Под "функциональный" я понимаю все-таки поддержку для функциональной декомпозиции в языке — т.е. первоклассные функции. Ограничивать число функциональных языков чисто функциональными (которых всего-то три, не считая совсем экспериментальные, причем один уже мертв, а второй собирается стать диалектом третьего) я считаю неправильным. Практики из этих языков, путь с оговорками, костылями и подпорками все-таки применимы в ФЯ в широком смысле.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
>Ответь, дважды примененный результат getLine к одному и тому же миру даст одинаковый резльутат?
Да.
>Только без всяких "если".
Если ты передал в функнию значение уникального типа, ты его не получишь назад.
Если ты передал в функнию значение уникального типа, ты его не получишь назад.
И твоя голова всегда в ответе за то, куда сядет твой зад.
"Ссылка на мир всегда одна" —
Это сказал Филипп Вадлер,
или какой-то другой
Профессор с бородой.
Здравствуйте, alex_public, Вы писали:
_>А? ) Кстати, xslt как раз очень хорошо относится к этому форуму — явно декларативный. )))
Лично я плохо отношусь к решению задач через жопу. И к xslt тоже. Потому что делать язык программирования из языка разметки, который изначально предназначался для машины, это тоже через жопу.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Лично я плохо отношусь к решению задач через жопу. И к xslt тоже. Потому что делать язык программирования из языка разметки, который изначально предназначался для машины, это тоже через жопу.
В смысле синтаксиса согласен. Но других то инструментов с сопоставимыми возможностями не видно. Только всякие кривые домашние велосипеды. А у xslt один docbook чего стоит. )
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Если речь едёт о генерации http-страничек, то инструмент с сопоставимы возможностями называется похапе.
Ээээ, наверное имелось в виду html-страничек? А то http-странички — это что-то мне не знакомое. )))
А так, ничего даже близкого на php (даже если забыть, что это жутчайший язык сам по себе) нет. Скажем на xslt у меня код в две строки генерирует chm файл из xml файла. Другие две строки генерируют полноценный "сайт" (с навигацией и т.п.) из того же xml файла. А ещё другие две строки (ну там может чуть больше) генерируют красиво оформленный pdf из того же xml файла. Не знаю как сделать тоже самое на php таким же количеством кода. )))
Здравствуйте, alex_public, Вы писали:
_>А? ) Кстати, xslt как раз очень хорошо относится к этому форуму — явно декларативный. )))
Ага, "язык функционального программирования с нечеловеческим лицом" (автора не помню)
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
S>>>>Ответь, дважды примененный результат getLine к одному и тому же миру даст одинаковый резльутат?
V>>>Да S>>Это учитывая текущую "реализацию" мира?
V>Нет
окей. Т.е. повторение результата где-то в голове, но текущая реализация мира этому не способствует. Спасаемся тем, что нам не позволяют юзать один мир дважды.
S>>>>Если так, то функция World *printf(LPCSTR, World *) тоже становится формально чистой
V>>>Да. S>>Не согласен
V>Ну что ж, ты конечно имеешь право на собственное мнение )
Да, иногда пользуюсь этим правом.
Хотелось бы избежать любой напряженности, потому сделаю отступление в этом месте.
Мнение мое сложилось отнюдь не в результате глубокого изучения вопроса или применения каких-то аналитических способностей. Просто в данный момент я так вижу и так понимаю. Право иметь собственное мнение немного для меня стоит без того что бы проверить это мнение а прочность путем столкнования с отличными от моего мнениями. Но в то же время, для меня это не спорт. Т.е. предпочту иметь более правильное, аргументированное мнение, чем свое собственное.
Так же признаю за оппонентом право не способствовать мне в проверке прочности моего мнения и приму любую причину этого не делать, в том числе нежелание или отсутствие времени/возможности. А при наличии желания+возможности предлагаю в неспешном комфортном режиме навести ясность в этом вопросе (не исключаю, что и в моей голове).
V>>>Т.е. в Си, добавив world к printf ничего не добъешься, т.к. язык не позволяет правильно ограничить его использование, а в хаскеле все чики-пики.
S>>В хаскеле мы работаем чисто функционально не от того что мир нельзя подать дважды в один метод. А от того, что вручную не форсируем выполенение нечистых дейсвтвий, а лишь комбинируем нечистые действия чистым образом.
V>Да да, конечно )
Отлично. Исходная точка найдена. Теперь надо выяснить, имеет ли способ ограничения повторного использования мира к определению чистоты. Имеет ли значение, как ограничивается повторное использование, будь это возможность компилятора, библиотеки, или честное слово в документации?
V>>>Но в программе мир моделируется тем самым World. S>>Это не модель. Это затычка, dummy. S>>При этом, как бы мир не моделировался в самой программе, программе все еще приходится взаимодействовать с миром вне программы. И любое такое взаимодействие нечисто по определению.
V>Безусловно )
Рад, еще одна точка понимания. Дальше ход такой: имеет ли значение, как называется тип мира? Метафора, конечно важна. Но чисто формально наш мир мог бы называться Dummy, Foo, или Foo9278345987. И чисто формально уже не важно, какую связь с миром имеет этот параметр. Важно лишь то, что каким-то образом ограничена возможность повторного использования экземпляра.
V>>>Если она ничего кроме этого праметра больше не трогает, то стала. Но, т.к. параметр по-сути фиктивный и меняется реальный мир, то, чтобы сохранить чистую семантику, требуется чтобы этот параметр нельзя было использовать более одного раза. Тут то и вылезают монады с do-нотацией, которые позволяют это удобно сделать. S>>Монады — лишь способ протащить мир по вычислениям. В printf можно было бы ограничить использование мира другим способом, например проверкой его счетчика использований и возвратом кода ошибки (или исключения в C++). Механизм ограничения числа использований параметра вообще не имеет никакого отношения к определению чистоты.
V>Возврат ошибки/исключение на том же мире -- уже нечистая ф-ия.
Согласен, формально это уже impure. Но, при стечении хороших обстоятельств, при правильном использовании, мы избежим кодов возврата, исключений и т.п. Тогда сможем говорить о некой относительной чистоте. Но на самом деле мы говорим о нечистоте вывода. Т.е. я убежден в том, что никакие средства, в том числе изменения компилятора, не позволят сделать ввод/вывод или взаимодействие с внешним миром относительно самой программы чистым.
V>Покури unique types что-ли. Может так станет понятно про то, что при гарантии однократного использования параметра/результата сохраняется ссылочная прозрачность.
Тут опять таки есть разные точки приложения ссылочной прозрачности. По определению ссылочная прозрачность выражения — это возможность заменить вычисление выражения результатом без изменения поведения программы.
Важно понимать, что при замене функций ввода-вывода со внешним миром значениями, программа перестанет взаимодействовать с внешним миром. Внешнее поведение программы изменится. Потому, ни о какой ссылочной прозрачности в отношении внешнего поведения программы речи быть не может.
О какой же тогда ссылочной прозрачности толкуют в uniqueness typing? ИМХО, они говорят о ссылочной прозрачности в отношении внутреннего поведения программы. Уникальные типы позволяют делать деструктивное обновление параметра в случае World->(a, World), и только лишь. Для внутренностей программы и только для них, все выглядит как будто бы мы получили новый экземпляр мира (пусть и на месте старого). Для внутренностей программы не имеет значения, вызывали ли мы взаимодействие со внешним миром, или просто получили вместо старого мира кортеж с новым миром. В этом отношении ссылочная прозрачность имеет место. Но как только мы вспоминаем о взаимодействии с внешним миром, оказывается что такая замена не является прозрачной.
Итого, uniqueness typing это лишь один из способов гарантировать невозможность повторного использования экземпляра с приятной особенностью, позволяющей делать замену по месту. Никакой чистоты при взаимодействии с внешним миром оно не обеспечивает.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Здравствуйте, samius, Вы писали:
>>Ответь, дважды примененный результат getLine к одному и тому же миру даст одинаковый резльутат?
ПМ>Да.
>>Только без всяких "если".
ПМ>Если ты передал в функнию значение уникального типа, ты его не получишь назад. ПМ>Если ты передал в функнию значение уникального типа, ты его не получишь назад.
В хаскеле ведь тип не уникален. Согласен, что монада нам нужна лишь для того что бы не юзать дважды один мир. Но будь возможность заюзать мир дважды, сохранение результата не гарантируется.
ПМ>И твоя голова всегда в ответе за то, куда сядет твой зад. ПМ>"Ссылка на мир всегда одна" - ПМ>Это сказал Филипп Вадлер, ПМ>или какой-то другой ПМ>Профессор с бородой.
Это лирика, если считать что представление внешнего мира внутри программы ничем не лучше чем Foo.
Здравствуйте, alex_public, Вы писали:
_>В смысле синтаксиса согласен. Но других то инструментов с сопоставимыми возможностями не видно. Только всякие кривые домашние велосипеды. А у xslt один docbook чего стоит. )
Здравствуйте, alex_public, Вы писали:
_>А так, ничего даже близкого на php (даже если забыть, что это жутчайший язык сам по себе) нет. Скажем на xslt у меня код в две строки генерирует chm файл из xml файла. Другие две строки генерируют полноценный "сайт" (с навигацией и т.п.) из того же xml файла. А ещё другие две строки (ну там может чуть больше) генерируют красиво оформленный pdf из того же xml файла. Не знаю как сделать тоже самое на php таким же количеством кода. )))
Здравствуйте, FR, Вы писали:
FR>Ну вроде инструменты есть:
Инструментов-то дофига, только лиц с xml-лом головного мозга проще сразу в склиф отправлять, чем использовать инструменты для решения искственно созданных проблем.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Здравствуйте, samius, Вы писали:
S>>В хаскеле ведь тип не уникален. S>>Но будь возможность заюзать
ПМ>Подразумевается, что такой возможности нет. Значит таки уникален
Хорошо, пусть будет уникальным. Как уникальность типа внутреннего представления мира хаскеля гарантирует чистоту при взаимодействии action с внешним миром во время выполнения действия?
Здравствуйте, samius, Вы писали:
ПМ>>Подразумевается, что такой возможности нет. Значит таки уникален S>Хорошо, пусть будет уникальным. Как уникальность типа внутреннего представления мира хаскеля гарантирует чистоту при взаимодействии action с внешним миром во время выполнения действия?
Чистота — это свойство функции возвращать тот же результат при тех же исходных данных (т.е. вызов функции можно безопасно заменить её вычисленным значением). Дыкть вот, уникальные типы — это такой хак, который не позволяет дважды вызвать функцию с одним и тем же значением.
А дальше всё зависит от твоих религиозных взглядов — являешься ли ты ультрафинитистом, веришь ли в закон исключенного третьего, поклоняешься ли аксиоматике Цермело—Френкеля...
Т.е. ни на Хаскелле, ни на языках с уникальными типами невозможно (без unsafe-хаков) написать функцию, которая бы возвращала разные значения при одинаковых аргументах, это касается и типа *World (программа принимает его при запуске, ничего не знает про его внутреннее устройство и ввиду уникальности не может создавать на него ссылки). Разумеется, то, что функция возвращает одинаковые значения, ты тоже проверить не сможешь, т.к. не можешь создавать экземпляры или ссылки на *World. Но можно ли считать ли функцию чистой, если невозможно убедиться в обратном, это уже вопрос религии, а не математики.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Здравствуйте, samius, Вы писали:
ПМ>>>Подразумевается, что такой возможности нет. Значит таки уникален S>>Хорошо, пусть будет уникальным. Как уникальность типа внутреннего представления мира хаскеля гарантирует чистоту при взаимодействии action с внешним миром во время выполнения действия?
ПМ>Чистота — это свойство функции возвращать тот же результат при тех же исходных данных (т.е. вызов функции можно безопасно заменить её вычисленным значением). Дыкть вот, уникальные типы — это такой хак, который не позволяет дважды вызвать функцию с одним и тем же значением.
Чистота — это не только детерминированность, это еще и отсутствие побочных эффектов. Но даже детерминированности при взаимодействии со внешним миром уникальные типы обеспечить не могут. Не говоря о побочных эффектах. Я говорю о чистоте при взаимодействии с внешним миром, а не экземпляром World.
ПМ>А дальше всё зависит от твоих религиозных взглядов — являешься ли ты ультрафинитистом, веришь ли в закон исключенного третьего, поклоняешься ли аксиоматике Цермело—Френкеля...
не являюсь, не верю, не поклоняюсь. Грешен тем, что смотрел в определение чистой функции в википедии.
ПМ>Т.е. ни на Хаскелле, ни на языках с уникальными типами невозможно (без unsafe-хаков) написать функцию, которая бы возвращала разные значения при одинаковых аргументах, это касается и типа *World (программа принимает его при запуске, ничего не знает про его внутреннее устройство и ввиду уникальности не может создавать на него ссылки). Разумеется, то, что функция возвращает одинаковые значения, ты тоже проверить не сможешь, т.к. не можешь создавать экземпляры или ссылки на *World. Но можно ли считать ли функцию чистой, если невозможно убедиться в обратном, это уже вопрос религии, а не математики.
Возвращать одинаковые значения при одинаковых аргументах — не единственное условие даже для детерминированности.
S>Чистота — это не только детерминированность, это еще и отсутствие побочных эффектов.
Является ли нагрев процессора побочным эффектом? Без формального определения побочного эффекта мы снова скатываемся в религию, поэтому мне интересно говорить только о наблюдаемых побочных эффектах. Если программа не может "наблюсти" побочный эффект вызова какой-либо функции, то можно считать, что его нет, потому что на дальнейший ход вычислений он не влияет. А "наблюсти" побочный эффект можно только с помощью недетерминированной функции, читающей "мир". Поэтому я буду считать детерминированность достаточным условием чистоты.
S>Я говорю о чистоте при взаимодействии с внешним миром, а не экземпляром World.
Ну вот со всем утверждениями относительно Реального Мира, Бога и Того, Как Там Всё На Самом Деле — нужно обращаться не ко мне, а к Архитектору Матрицы.
S>Возвращать одинаковые значения при одинаковых аргументах — не единственное условие даже для детерминированности.
Да ну? А что еще надо? Кровь китайских девственниц?
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Т.е. ни на Хаскелле, ни на языках с уникальными типами невозможно (без unsafe-хаков) написать функцию, которая бы возвращала разные значения при одинаковых аргументах
Тут нужно очень аккуратно определять, что считать ее аргументами. Она же может быть замыканием, содержащим ref'ы, например.
Здравствуйте, Паблик Морозов, Вы писали:
S>>Чистота — это не только детерминированность, это еще и отсутствие побочных эффектов.
ПМ>Является ли нагрев процессора побочным эффектом? Без формального определения побочного эффекта мы снова скатываемся в религию, поэтому мне интересно говорить только о наблюдаемых побочных эффектах. Если программа не может "наблюсти" побочный эффект вызова какой-либо функции, то можно считать, что его нет, потому что на дальнейший ход вычислений он не влияет. А "наблюсти" побочный эффект можно только с помощью недетерминированной функции, читающей "мир". Поэтому я буду считать детерминированность достаточным условием чистоты.
Такую чистоту предлагаю тебе обсуждать с теми, кто считает так же.
S>>Я говорю о чистоте при взаимодействии с внешним миром, а не экземпляром World.
ПМ>Ну вот со всем утверждениями относительно Реального Мира, Бога и Того, Как Там Всё На Самом Деле — нужно обращаться не ко мне, а к Архитектору Матрицы.
Это ты ко мне обратился
. Причем с такими утверждениями, что мои мне кажутся невинными на их фоне.
S>>Возвращать одинаковые значения при одинаковых аргументах — не единственное условие даже для детерминированности.
ПМ>Да ну? А что еще надо? Кровь китайских девственниц?
Когда я писал ответ, не знал что у тебя свое понимание чистоты и наверное детерминированности. Так что не обращай внимания, я о чем-то другом.
Здравствуйте, D. Mon, Вы писали:
DM>Тут нужно очень аккуратно определять, что считать ее аргументами. Она же может быть замыканием, содержащим ref'ы, например.
Операции с рефами тоже принимают мир или не сбегают из монады (STRef)
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Здравствуйте, samius, Вы писали:
S>>Такую чистоту предлагаю тебе обсуждать с теми, кто считает так же.
ПМ>А какую чистоты ты предлагаешь обсудить?
Я обсуждал чистоту в ее общедоступном определении с википедии.
ПМ>Повторяю вопрос — функция, в результате которой нагревается процессор времени чистая или нет?
Этот вопрос ты мне еще не задавал. Постом выше был вопрос о том, является ли нагрев процессора побочным эффектом. Там же ты продемонстрировал нежелание использовать термины в общеупотребимом смысле и неинтерес к обсуждению их. Потому отвечать тебе на твои вопросы в терминах википедии я не вижу смысла, а использовать твою терминологию, в свою очередь, нет желания у меня.
Так что направлю тебя к Архитектору твоей Матрицы.
Здравствуйте, alex_public, Вы писали:
_>Ну и откуда я возьму эти функции в php?
А откуда я возьму две строчки на xslt, которые генерируют pdf? Особенно если учесть, что pdf — бинарный формат.
_>Т.е. вы предлагаете писателям документации и переводчикам работать с программным кодом, а не с текстом? )
Вы ведь тоже предлагаете им работать с каким-то xml-ем, а не с текстом.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>А откуда я возьму две строчки на xslt, которые генерируют pdf? Особенно если учесть, что pdf — бинарный формат.
http://docbook.sourceforge.net — здесь. Причём pdf — это на самом деле не самое интересное то...
_>>Т.е. вы предлагаете писателям документации и переводчикам работать с программным кодом, а не с текстом? )
ПМ>Вы ведь тоже предлагаете им работать с каким-то xml-ем, а не с текстом.
ME>>скажем, почему список из объектов, у каждого из которых есть функция print, чем-то хуже "списка из print-ов"? V>Тем что это объекты с ф-ей print, а не просто ф-ия print сама по себе. Больше сущностей.
гы-гы
а разве в хаскеле нет разницы между функцией и замыканием? афайк print легко может быть замыканием, и нет способа отличить замыкание от настоящей функции
а замыкание -- это тот же объект, только хуже оно так же как и объект, держит (т.е. не дает собрать мусорщику) данные, которые держат другие данные, и т.д.; при этом если про поля, захваченные замыканием, мы ничего не знаем, то поля объекта могут быть в явном виде ограничены, скажем, путем объявления класса final в java
ME>>каким образом IO a -- чистая ф-я?
V>Повторяю: V>IO a = World -> (a, World)
ммм... а мы про хаскель говорим или про что?
data IO a = ... -- abstractinstance Functor IO where
fmap f x = x >>= (return . f)
instance Monad IO where
(>>=) = ...
return = ...
fail s = ioError (userError s)
а "World -> (a, World)" это какие-то рассказы, которые могут и не соответствовать истине
p.s. to Klapaucius и остальным -- дойду постепенно и до ваших ответов
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, Паблик Морозов, Вы писали:
ПМ>>А откуда я возьму две строчки на xslt, которые генерируют pdf? Особенно если учесть, что pdf — бинарный формат.
_>http://docbook.sourceforge.net — здесь. Причём pdf — это на самом деле не самое интересное то...
_>>>Т.е. вы предлагаете писателям документации и переводчикам работать с программным кодом, а не с текстом? )
ПМ>>Вы ведь тоже предлагаете им работать с каким-то xml-ем, а не с текстом.
_>http://www.syntext.com/products/serna-free/ не, с текстом.
Если речь идёт о верстке документов и использовании каких-то тулзов для конвертации их в различные форматы, то пусть хоть в ворде верстают, в этом ничего плохого нет. Просто из
[quote]
Я лично ничего не верстаю вообще, как впрочем и GUI не рисую сам. Я больше занимаюсь проектирование архитектуры всего этого. И вот в рамках нашей архитектуры все html генерятся у нас с помощью xslt шаблонов из xml данных.
[/quote]
Я сделал вывод, что речь идёт о каком-то веб-интерфейсе, где сервер генерируюет xml, который самописными xslt-шками конвертируется в веб-страницы. Такую придурь я встречал и именно с неё я . А если программисту не приходится трогать xml и xslt, то норм, не трогаешь — не пахнет.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Если речь идёт о верстке документов и использовании каких-то тулзов для конвертации их в различные форматы, то пусть хоть в ворде верстают, в этом ничего плохого нет. Просто из
ПМ>[quote] ПМ>Я лично ничего не верстаю вообще, как впрочем и GUI не рисую сам. Я больше занимаюсь проектирование архитектуры всего этого. И вот в рамках нашей архитектуры все html генерятся у нас с помощью xslt шаблонов из xml данных. ПМ>[/quote]
ПМ>Я сделал вывод, что речь идёт о каком-то веб-интерфейсе, где сервер генерируюет xml, который самописными xslt-шками конвертируется в веб-страницы. Такую придурь я встречал и именно с неё я . А если программисту не приходится трогать xml и xslt, то норм, не трогаешь — не пахнет.
На сайтах тоже всё через xslt (и как раз самописными), но всё в статике (а динамика вся через ajax) — даже не на сервере, а на компах разработчиков.
Т.е. в целом схема такая:
— пишущие текст люди имеют дело с текстом в спец. редакторах (геренирующих xml).
— программисты в теории (см ниже) занимаются только серверными (python) и клиентскими скриптами (jquery) и там обмен не через xml.
— дизайнеры делают шаблон для статики в xslt.
На практике программисты делают ещё перевод с "художественного языка" на xslt.
Плюс документация для сайта и в дистрибутив (chm) генерируется уже с помощью не самописных (docbook) xslt.
Здравствуйте, m e, Вы писали:
ME>>>скажем, почему список из объектов, у каждого из которых есть функция print, чем-то хуже "списка из print-ов"? V>>Тем что это объекты с ф-ей print, а не просто ф-ия print сама по себе. Больше сущностей.
ME>гы-гы
ME>а разве в хаскеле нет разницы между функцией и замыканием? афайк print легко может быть замыканием, и нет способа отличить замыкание от настоящей функции
Вот ты сам и ответил на свой вопрос -- отличить нельзя. Сущность с точки зрения программиста одна.
ME>а замыкание -- это тот же объект, только хуже оно так же как и объект, держит (т.е. не дает собрать мусорщику) данные, которые держат другие данные, и т.д.; при этом если про поля, захваченные замыканием, мы ничего не знаем, то поля объекта могут быть в явном виде ограничены, скажем, путем объявления класса final в java
Пиши на java, кто тебе не дает
ME>>>каким образом IO a -- чистая ф-я?
V>>Повторяю: V>>IO a = World -> (a, World)
ME>ммм... а мы про хаскель говорим или про что?
ME>
ME> data IO a = ... -- abstract
ME> instance Functor IO where
ME> fmap f x = x >>= (return . f)
ME> instance Monad IO where
ME> (>>=) = ...
ME> return = ...
ME> fail s = ioError (userError s)
ME>
ME>а "World -> (a, World)" это какие-то рассказы, которые могут и не соответствовать истине
ME>>а разве в хаскеле нет разницы между функцией и замыканием? афайк print легко может быть замыканием, и нет способа отличить замыкание от настоящей функции
V>Вот ты сам и ответил на свой вопрос -- отличить нельзя. Сущность с точки зрения программиста одна.
сущность одна с точки зрения *хаскеля*, но не программиста, которому надо еще и о ресурсах заботиться
так что уж не хаскелистам наезжать на объекты
V>Пиши на java, кто тебе не дает
что же касается настоящих функций, а не замыканий и не объектов, как членов массива -- этого в яве тоже нет, так что остается только с++11, где появилось слово final.
Здравствуйте, samius, Вы писали:
S>Здравствуйте, vshabanov, Вы писали:
V>>Здравствуйте, samius, Вы писали:
S>>>>>Ответь, дважды примененный результат getLine к одному и тому же миру даст одинаковый резльутат?
V>>>>Да S>>>Это учитывая текущую "реализацию" мира?
V>>Нет S>окей. Т.е. повторение результата где-то в голове, но текущая реализация мира этому не способствует. Спасаемся тем, что нам не позволяют юзать один мир дважды.
S>>>>>Если так, то функция World *printf(LPCSTR, World *) тоже становится формально чистой
V>>>>Да. S>>>Не согласен
V>>Ну что ж, ты конечно имеешь право на собственное мнение ) S>Да, иногда пользуюсь этим правом. S>Хотелось бы избежать любой напряженности, потому сделаю отступление в этом месте. S>Мнение мое сложилось отнюдь не в результате глубокого изучения вопроса или применения каких-то аналитических способностей. Просто в данный момент я так вижу и так понимаю. Право иметь собственное мнение немного для меня стоит без того что бы проверить это мнение а прочность путем столкнования с отличными от моего мнениями. Но в то же время, для меня это не спорт. Т.е. предпочту иметь более правильное, аргументированное мнение, чем свое собственное. S>Так же признаю за оппонентом право не способствовать мне в проверке прочности моего мнения и приму любую причину этого не делать, в том числе нежелание или отсутствие времени/возможности. А при наличии желания+возможности предлагаю в неспешном комфортном режиме навести ясность в этом вопросе (не исключаю, что и в моей голове).
V>>>>Т.е. в Си, добавив world к printf ничего не добъешься, т.к. язык не позволяет правильно ограничить его использование, а в хаскеле все чики-пики.
S>>>В хаскеле мы работаем чисто функционально не от того что мир нельзя подать дважды в один метод. А от того, что вручную не форсируем выполенение нечистых дейсвтвий, а лишь комбинируем нечистые действия чистым образом.
V>>Да да, конечно ) S>Отлично. Исходная точка найдена. Теперь надо выяснить, имеет ли способ ограничения повторного использования мира к определению чистоты. Имеет ли значение, как ограничивается повторное использование, будь это возможность компилятора, библиотеки, или честное слово в документации?
Если компилятор не бьет по рукам, то считай никакого ограничения нет.
V>>>>Но в программе мир моделируется тем самым World. S>>>Это не модель. Это затычка, dummy. S>>>При этом, как бы мир не моделировался в самой программе, программе все еще приходится взаимодействовать с миром вне программы. И любое такое взаимодействие нечисто по определению.
V>>Безусловно ) S>Рад, еще одна точка понимания. Дальше ход такой: имеет ли значение, как называется тип мира? Метафора, конечно важна. Но чисто формально наш мир мог бы называться Dummy, Foo, или Foo9278345987. И чисто формально уже не важно, какую связь с миром имеет этот параметр. Важно лишь то, что каким-то образом ограничена возможность повторного использования экземпляра.
Эмм, как называется тип вообще не имеет значения.
Мне что-то кажется, что ты никак не можешь отделить программу от ее выполнения. Вот смотри, возьмем мы какой-нить sin x. Чистая ф-ия. Но при ее вычислении процессор выполняет инструкции, грязно меняющие содержимое памяти, счетчик команд, кеши. Программа может прочесть что-нить из файла (если x получен из getContents, или из какого-нить memory mapped file-а), что-нить может отсвопиться, отвалиться по out of memory. Ну и проц может сгореть, в конце концов.
Т.е. даже sin, при сильном влезании в детали, очень нечистая ф-ия. Но все-таки, мы считаем ее чистой.
Также и с IO. В хаскелле нет нечистых ф-ий: не встроены в него переменные и взаимодействие с внешним миром. Однако работать с внешним миром все-таки надо. Чтобы не ломать чистоту стали считать мир обычным значением. Получается что какая-нить readLine :: World -> (String, World) обычная чистая хаскельная ф-ия, ведь она берет String не откуда-то а из аргумента. Т.е. глядя на тип такой ф-ии никак нельзя понять, что она не чистая (да и нет в хаскеле нечистых ф-ий).
Однако, в реальных программах тип World -- это все-таки не список строк stdin-а и stdout-а (хотя мог бы им быть в простых программах), а настоящий мир. А с реальным миром работать как с обычным хаскельным значением не получится. Мы не можем наплодить кучу миров и не можем использовать один и тот же мир дважды. Т.е. чтобы, при сохранении формальной чистоты, работать с реальным миром, а не заглушкой, необходимо, чтобы в любой момент времени в программе существовало только одно значение мира (тогда оно может меняться внутри, но программа этого все равно не заметит). Вот для того, чтобы значение было только одно, к нему не дают прямого доступа, только через bind/return/main.
Т.е. формально хаскелл полностью чистый язык. То, что writeFile что-то пишет на диск, программу на хаскеле не волнует никаким образом, для нее writeFile -- чистая ф-ия, преобразующая значение типа World. И, если ей подсунуть еще раз тот же мир (мысленно перенесясь назад во времени, например), то она выдаст тот же результат.
V>>>>Если она ничего кроме этого праметра больше не трогает, то стала. Но, т.к. параметр по-сути фиктивный и меняется реальный мир, то, чтобы сохранить чистую семантику, требуется чтобы этот параметр нельзя было использовать более одного раза. Тут то и вылезают монады с do-нотацией, которые позволяют это удобно сделать. S>>>Монады — лишь способ протащить мир по вычислениям. В printf можно было бы ограничить использование мира другим способом, например проверкой его счетчика использований и возвратом кода ошибки (или исключения в C++). Механизм ограничения числа использований параметра вообще не имеет никакого отношения к определению чистоты.
V>>Возврат ошибки/исключение на том же мире -- уже нечистая ф-ия. S>Согласен, формально это уже impure. Но, при стечении хороших обстоятельств, при правильном использовании, мы избежим кодов возврата, исключений и т.п. Тогда сможем говорить о некой относительной чистоте. Но на самом деле мы говорим о нечистоте вывода. Т.е. я убежден в том, что никакие средства, в том числе изменения компилятора, не позволят сделать ввод/вывод или взаимодействие с внешним миром относительно самой программы чистым.
Я выше попытался в очередной раз объяснить, как это возможно
V>>Покури unique types что-ли. Может так станет понятно про то, что при гарантии однократного использования параметра/результата сохраняется ссылочная прозрачность. S>Тут опять таки есть разные точки приложения ссылочной прозрачности. По определению ссылочная прозрачность выражения — это возможность заменить вычисление выражения результатом без изменения поведения программы. S>Важно понимать, что при замене функций ввода-вывода со внешним миром значениями, программа перестанет взаимодействовать с внешним миром. Внешнее поведение программы изменится. Потому, ни о какой ссылочной прозрачности в отношении внешнего поведения программы речи быть не может. S>О какой же тогда ссылочной прозрачности толкуют в uniqueness typing? ИМХО, они говорят о ссылочной прозрачности в отношении внутреннего поведения программы. Уникальные типы позволяют делать деструктивное обновление параметра в случае World->(a, World), и только лишь. Для внутренностей программы и только для них, все выглядит как будто бы мы получили новый экземпляр мира (пусть и на месте старого). Для внутренностей программы не имеет значения, вызывали ли мы взаимодействие со внешним миром, или просто получили вместо старого мира кортеж с новым миром. В этом отношении ссылочная прозрачность имеет место. Но как только мы вспоминаем о взаимодействии с внешним миром, оказывается что такая замена не является прозрачной.
S>Итого, uniqueness typing это лишь один из способов гарантировать невозможность повторного использования экземпляра с приятной особенностью, позволяющей делать замену по месту. Никакой чистоты при взаимодействии с внешним миром оно не обеспечивает.
А реальный мир постоянно меняется, вне зависимости от выполнения программы и мы можем спокойно "заменять по месту" значения мира на текущее
А>> MS Office как раз таки образец редкостного говнища. На кой он нужен, когда есть TeX, когда есть Mathematica (кстати, типичный пример архитектуры с оторванным от гуя ядром). A>Вообразим маленький эксперимент. Завхозу объяснили, как компьютер включать, и назначение некоторых клавиш. Бедолаге надо напечатать крупными буквами "Туалет закрыт из-за отсутствия воды". Есть возможность консультироваться по телефону (пока консультанту не надоест). Какой инструмент ему выбрать -- TeX или Word?
правильный ответ -- html, notepad и любой браузер
в ворде слишком легко запутаться, нажать не те кнопки, и потом х.з. сколько времени безуспешно пытаться объяснить консультанту, что случилось, а консультанту пытаться догадаться, как это исправить
ТеХ плох тем, что будет сыпать ошибками от незакрытых скобок
в блокноте достаточно написать "<html><h1>Туалет закрыт из-за отсутствия воды", сохранить как .htm, открыть в браузере, распечатать (и дальше ножницами обрезать, если не по центру)
ты-то сам ходил по своей ссылки? там четко написано "Portability : non-portable (GHC Extensions)", значит ты должен был ответить "мы говорим не о хаскеле, а о GHC Extensions" и не путать меня и окружающих
_>Xa. Я порадовался (может хоть здесь применение найдётся, вдруг чем-то удобнее scons'a будет да и вообще всё же статически типизированые языки больше люблю) и полез смотреть сразу.
и правда, хорошо бы сборочную систему на статически типизированном языке
K>Ага. Т.е. это еще веселее чем в анекдоте про нашедшиеся ложки и оставшийся осадок: тут ложки даже и не терялись, но есть мнение, что потеряются.
это неправильная аналогия
правильная будет такой: пришел MigMit и стал всем продавать нетеряющиеся ложки -- серебрянные на яве и шарпе, и золотые на хаскеле
я продемонстрировал, как теряются серебрянные ложки, и отсюда вывел *правдоподобное* заключение, что и золотые тоже могут потеряться
а вся тяжесть доказательства того, что ложки не теряются -- т.е. что скалярное произведение будет работать всегда -- лежит на MigMit, так как он опубликовал пост с этим утверждением, а вовсе не на мне (вспоминаем золотой чайник, летающий вокруг земли)
ME>>что значит "полноценность"?
K>В данном случае это означает, что код, который должен работать на языке с параметрическим полиморфизмом действительно работает.
он пыхтит, работает, и вообще очень старается, но вычисление скалярного произведия не гарантирует. супер!
ME>>объясни, в каком месте хаскель более декларативен, чем язык из ML семейства
K>В месте ссылочной прозрачности. И я не считаю, что "более декларативен" осмысленное словосочетание. Язык или декларативен или нет (императивен).
слово декларативность относится не только к комбинации "декларативен-императивен"
скажем, общепринято считать, что sql декларативен по той причине, что он описывает *что* надо селектировать, а не *как* это сделать; наличие update-ов не мешает ему считаться декларативным (и в update-ах можно пользоваться декларативными возможностями select-а)
именно поэтому я и употребляю словосочетание "более декларативен"
если же ты не согласен, и настаиваешь на своем, самобытном определении слова "декларативность", то пожалуста выпиши это определение полностью
_>Мне больше нравятся критерии, озвученные в SICP: нормальный порядок вычислений и декомпозиция на потоки. Т.о. к (чисто) функциональным языкам можно отнести Miranda, Clean и Haskell например.
_>>О, кстати, вспомнил ещё про yield в Питоне — тоже интересное решение для ленивости, в императивном стиле можно сказать. )))
V>Ну что уж поделать, коли генераторы через классы офигеешь писать.
интересует конкретный пример генератора, который неудобно пишется
V>Поинт в том, что в Хаскеле даже монада IO, которая кажется нечистой, также является чистой ф-ей. Просто у нее аргумент необычный ), но формально она чистая.
более интересно то, почему в ghc внезапно стали писать библиотеку, где IO a = IO (State# RealWorld -> (#State# RealWorld, a#)) ?
и кстати что тут означают решетки?
раньше вроде бы всем говорили "ребята, работа с RealWorld это удобное объяснение для начинающих, а на самом деле все сделано на continuations", и вот внезапно решили сделать монаду как в объяснении для начинающих? что случилось?
K>>В месте ссылочной прозрачности. И я не считаю, что "более декларативен" осмысленное словосочетание. Язык или декларативен или нет (императивен).
FR>Слово "декларативный" все-таки тут плохо подходит, у него слишком много значений, ты слишком сужаешь его смысл. FR>Ни к чему кроме терминологических споров это не приведет.
можно попытаться придумать ссылочнопрозрачный язык с каким-то дефектом, например который принципиально не позволит записать, скажем 7-tuples, а позволяет 2-tuples... или еще что-то в этом роде, что заставляет напрямую выписывать то, что можно было бы просто декларировать
FR>Хотя термин подобрать сложно, "аппликативный" вроде тоже не совсем подходит, остается только громоздкое "чисто функциональный".
ПМ> Разумеется, то, что функция возвращает одинаковые значения, ты тоже проверить не сможешь, т.к. не можешь создавать экземпляры или ссылки на *World. Но можно ли считать ли функцию чистой, если невозможно убедиться в обратном, это уже вопрос религии, а не математики.
че-то не вижу тут никакой религии
функция чистая при допущениях, которые мы сами добровольно собрались поддерживать, а вне этих допущений может творится черти-что, и что?
Здравствуйте, m e, Вы писали:
_>>>О, кстати, вспомнил ещё про yield в Питоне — тоже интересное решение для ленивости, в императивном стиле можно сказать. )))
V>>Ну что уж поделать, коли генераторы через классы офигеешь писать.
ME>интересует конкретный пример генератора, который неудобно пишется
Здравствуйте, m e, Вы писали:
V>>Поинт в том, что в Хаскеле даже монада IO, которая кажется нечистой, также является чистой ф-ей. Просто у нее аргумент необычный ), но формально она чистая.
ME>более интересно то, почему в ghc внезапно стали писать библиотеку, где IO a = IO (State# RealWorld -> (#State# RealWorld, a#)) ?
(# #) -- unboxed tuple, оптимизация
State# встроенный полиморфный тип для IO/ST монад (может еще для чего). Тоже видимо для оптимизаций используется
ME>раньше вроде бы всем говорили "ребята, работа с RealWorld это удобное объяснение для начинающих, а на самом деле все сделано на continuations", и вот внезапно решили сделать монаду как в объяснении для начинающих? что случилось?
Кто говорил? Какие еще continuations? Причем тут начинающие?
Здравствуйте, m e, Вы писали:
V>>Если компилятор не бьет по рукам, то считай никакого ограничения нет.
ME>неправильно
ME>почти правильно вот так:
ME>Если компилятор не бьет по рукам, когда мы его об этом попросили, то считай никакого ограничения нет.
Здравствуйте, alex_public, Вы писали: _> А есть не контролирующие чистоту, но позволяющие все остальные функциональные техники — тот же Питон.
Таки не все — например сопоставление с образцом в нём не доступно, соответственно затруднено применение алгебраических типов данных...
Здравствуйте, alex_public, Вы писали: _>Генераторы это [ f x | x <- xs ] _>В Питоне записываем как что-то типа [f(x) for x in xs] _>Ну и дальше там есть аналоги буквальные на guards и так далее.
В python-е нет прямого аналога ленивых списков haskell.
То, что ты написал — это списковое дополнение для генерации списка. Т. е. после выполнения это будет обычный список все значения в котором уже вычислены.
Вот если заменить скобки с квадратных на круглые, получишь объект итератора.
Генераторы python реализуют только одну сторону ленивости: значение генерится только тогда, когда его потребовали.
Но как только генератор сгенерировал значение, он его забывает и нет возможности запросить его повторно.
Чтобы понять, в чём проблема, попробуй реализовать простое слияние принимающее 2 итератора и отдающее итератор выходной последовательности.
merge x [] = x
merge [] y = y
merge xxs@(x:xs) yys@(y:ys)
| x < y = x : merge xs yys
| x > y = y : merge xxs ys
| otherwise = x : y : merge xs ys
Или попробуй написать синтаксический разбор на итераторах.
Так же код может себя по разному вести в зависимости от конкретного типа итератора. И много других интересностей и разностей...
Так что итератор/генератор python-а изрядно низкоуровневая конструкция по сравнению со списками haskell.
Ну а сами выражения списковых дополнений — это всего лишь синтаксический сахар. И он да, почти полностью совпадает по выразительности.
Здравствуйте, MigMit, Вы писали: MM>Нет, это особенность РАБОТЫ. MM>Я как-то приводил пример: http://migmit.livejournal.com/32688.html MM>В языках с настоящим ПП — работает (и должно). MM>В языках с макросистемой, т.е., в плюсах — не работает (и не может).
Ты так и не определил, чем отличается макросистема от ПП.
В плюсах статический параметрический полиморфизм. Инстансирование шаблона может проходить только на этапе компиляции.
Это и демонстрирует твой пример.
Причём его можно существенно упростить. Код ниже не скомпилируется точно по той же причине:
#include <iostream>
template <int N> struct Test {};
int main() {
std::cout << "Enter a number: ";
int val;
std::cin >> val;
Test<val> t;
}
Естественно, в Java и C# этого требования нету — и код работает.
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
S>>Отлично. Исходная точка найдена. Теперь надо выяснить, имеет ли способ ограничения повторного использования мира к определению чистоты. Имеет ли значение, как ограничивается повторное использование, будь это возможность компилятора, библиотеки, или честное слово в документации?
S>>Рад, еще одна точка понимания. Дальше ход такой: имеет ли значение, как называется тип мира? Метафора, конечно важна. Но чисто формально наш мир мог бы называться Dummy, Foo, или Foo9278345987. И чисто формально уже не важно, какую связь с миром имеет этот параметр. Важно лишь то, что каким-то образом ограничена возможность повторного использования экземпляра.
V>Эмм, как называется тип вообще не имеет значения.
V>Мне что-то кажется, что ты никак не можешь отделить программу от ее выполнения. Вот смотри, возьмем мы какой-нить sin x. Чистая ф-ия. Но при ее вычислении процессор выполняет инструкции, грязно меняющие содержимое памяти, счетчик команд, кеши. Программа может прочесть что-нить из файла (если x получен из getContents, или из какого-нить memory mapped file-а), что-нить может отсвопиться, отвалиться по out of memory. Ну и проц может сгореть, в конце концов.
V>Т.е. даже sin, при сильном влезании в детали, очень нечистая ф-ия. Но все-таки, мы считаем ее чистой.
Мы не считаем синус чистой функцией потому что она синус. Мы считаем ее чистой по выполнению формальных условий чистоты:
1) детерминированность. Будь функция хоть трижди синус, но если ее результат будет зависеть не только от аргумента, мы не сможем считать ее чистой. Ее результат так же не должен зависеть от внешнего взаимодействия с I/O девайсами. Это условие детерминированности придумал не я.
2) отсутствие семантически обозримых побочных эффектов и вывода через девайсы I/O.
То что нагревается процессор, грязно меняет счетчик команд, кэши, кочегар толкающий уголь в печь — это не является семантически обозримыми побочными эффектами, а так же вводом/выводом через I/O. Хотя я допускаю возможность построения таких систем, где нагревание процессора и т.п. будет семантически заметным. Но, т.к. я не знаю таких систем, не вижу повода заострять на этом внимание.
V>Также и с IO. В хаскелле нет нечистых ф-ий: не встроены в него переменные и взаимодействие с внешним миром. Однако работать с внешним миром все-таки надо. Чтобы не ломать чистоту стали считать мир обычным значением.
Выше ты со мной согласился, что в отношении ввода-вывода хаскель чистый уже потому, что не дает средств (за исключением бэкдоров) для прямого вызова взаимодействия с I/O. Поэтому я не вижу ни малейшего повода для ослабления формальных критериев чистой функции по причине "что бы не ломать чистоту".
Даже считая действия нечистыми, хаскель чист, т.к. позволяет лишь комбинировать их, но не выполнять. Так ради чего ломать критерий чистоты?
Я написал одно и то же дважды разными словами в надежде что мою мысль будет проще понять.
V>Получается что какая-нить readLine :: World -> (String, World) обычная чистая хаскельная ф-ия, ведь она берет String не откуда-то а из аргумента.
Она не чиста по определению. Нет повода считать что String берется откуда-то из экземпляра мира. V>Т.е. глядя на тип такой ф-ии никак нельзя понять, что она не чистая (да и нет в хаскеле нечистых ф-ий).
По ее сигнатуре мы действительно не можем утверждать что она не является чистой. Но по сути процессов, происходящих при ее выполнении — вполне можем утверждать что она не чистая.
V>Однако, в реальных программах тип World -- это все-таки не список строк stdin-а и stdout-а (хотя мог бы им быть в простых программах), а настоящий мир. А с реальным миром работать как с обычным хаскельным значением не получится. Мы не можем наплодить кучу миров и не можем использовать один и тот же мир дважды. Т.е. чтобы, при сохранении формальной чистоты, работать с реальным миром, а не заглушкой, необходимо, чтобы в любой момент времени в программе существовало только одно значение мира (тогда оно может меняться внутри, но программа этого все равно не заметит). Вот для того, чтобы значение было только одно, к нему не дают прямого доступа, только через bind/return/main.
Никакого отношения формальная чистота не имеет к возможности создания экземпляров более одного. Возможность меняться внутри — это не про хаскель, на сколько мне известно. А отсутствие прямого доступа к миру я могу объяснить более жизненными причинами чем мнимая чистота: невозможность прямого выполнения действия; обеспечение последовательности выполнения действий с побочными эффектами. Мир здесь играет роль лишь эстафетной палочки.
Давай предположим, что я согласился с тобой в вопросе чистоты действий. Тогда встает вопрос о том, почему хаскель запрещает (кроме бэкдоров) прямое выполнение действий? Они ведь "чистые", значит ничего испортить не могут и повлиять ни на что не должны!!! В чем тогда смысл?
V>Т.е. формально хаскелл полностью чистый язык. То, что writeFile что-то пишет на диск, программу на хаскеле не волнует никаким образом, для нее writeFile -- чистая ф-ия, преобразующая значение типа World. И, если ей подсунуть еще раз тот же мир (мысленно перенесясь назад во времени, например), то она выдаст тот же результат.
Это не так. writeFile не пишет на диск, не принимает World. writeFile чиста не потому что выдает тот же результат, приняв мир, а потому что ее результат никак в мире не отражается.
S>>Согласен, формально это уже impure. Но, при стечении хороших обстоятельств, при правильном использовании, мы избежим кодов возврата, исключений и т.п. Тогда сможем говорить о некой относительной чистоте. Но на самом деле мы говорим о нечистоте вывода. Т.е. я убежден в том, что никакие средства, в том числе изменения компилятора, не позволят сделать ввод/вывод или взаимодействие с внешним миром относительно самой программы чистым.
V>Я выше попытался в очередной раз объяснить, как это возможно
Ты объяснил только то, что пользуешься искаженным определением чистоты. А я выше попытался в очередной раз объяснить почему хаскель не нуждается в искажении чистоты.
S>>Итого, uniqueness typing это лишь один из способов гарантировать невозможность повторного использования экземпляра с приятной особенностью, позволяющей делать замену по месту. Никакой чистоты при взаимодействии с внешним миром оно не обеспечивает.
V>А реальный мир постоянно меняется, вне зависимости от выполнения программы и мы можем спокойно "заменять по месту" значения мира на текущее
К счастью, та затычка не нуждается в какой-либо синхронизации с миром. Даже думать о ней как о затычке мира вредно. Это лишь способ организации последовательности выполнения грязных действий.
Кстати, если ты считаешь действия чистыми, то для чего нужно организовывать порядок их выполнения?
Здравствуйте, samius, Вы писали:
V>>Также и с IO. В хаскелле нет нечистых ф-ий: не встроены в него переменные и взаимодействие с внешним миром. Однако работать с внешним миром все-таки надо. Чтобы не ломать чистоту стали считать мир обычным значением. S>Выше ты со мной согласился, что в отношении ввода-вывода хаскель чистый уже потому, что не дает средств (за исключением бэкдоров) для прямого вызова взаимодействия с I/O. Поэтому я не вижу ни малейшего повода для ослабления формальных критериев чистой функции по причине "что бы не ломать чистоту".
Какое такое ослабление критериев? Как раз наоборот, ничего не ослабляли. Вставили IO сохранив чистоту.
S>Даже считая действия нечистыми, хаскель чист, т.к. позволяет лишь комбинировать их, но не выполнять. Так ради чего ломать критерий чистоты? S>Я написал одно и то же дважды разными словами в надежде что мою мысль будет проще понять.
Ну я уже раз 5 написал, если не больше )
V>>Получается что какая-нить readLine :: World -> (String, World) обычная чистая хаскельная ф-ия, ведь она берет String не откуда-то а из аргумента. S>Она не чиста по определению. Нет повода считать что String берется откуда-то из экземпляра мира.
Да почему же не чиста? Для одного и того же мира вернет одну и ту же строку и один и тот же измененный мир. Побочных эффектов нет.
V>>Т.е. глядя на тип такой ф-ии никак нельзя понять, что она не чистая (да и нет в хаскеле нечистых ф-ий). S>По ее сигнатуре мы действительно не можем утверждать что она не является чистой. Но по сути процессов, происходящих при ее выполнении — вполне можем утверждать что она не чистая.
Каких таких процессов? Она меняет мир, а мир у нее является аргументом и результатом. Все чисто.
V>>Однако, в реальных программах тип World -- это все-таки не список строк stdin-а и stdout-а (хотя мог бы им быть в простых программах), а настоящий мир. А с реальным миром работать как с обычным хаскельным значением не получится. Мы не можем наплодить кучу миров и не можем использовать один и тот же мир дважды. Т.е. чтобы, при сохранении формальной чистоты, работать с реальным миром, а не заглушкой, необходимо, чтобы в любой момент времени в программе существовало только одно значение мира (тогда оно может меняться внутри, но программа этого все равно не заметит). Вот для того, чтобы значение было только одно, к нему не дают прямого доступа, только через bind/return/main. S>Никакого отношения формальная чистота не имеет к возможности создания экземпляров более одного. Возможность меняться внутри — это не про хаскель, на сколько мне известно. А отсутствие прямого доступа к миру я могу объяснить более жизненными причинами чем мнимая чистота: невозможность прямого выполнения действия; обеспечение последовательности выполнения действий с побочными эффектами. Мир здесь играет роль лишь эстафетной палочки.
S>Давай предположим, что я согласился с тобой в вопросе чистоты действий. Тогда встает вопрос о том, почему хаскель запрещает (кроме бэкдоров) прямое выполнение действий? Они ведь "чистые", значит ничего испортить не могут и повлиять ни на что не должны!!! В чем тогда смысл?
Потому что они чистые пока у нас только один используемый экземпляр мира в программе. При прямой работе можно запросто наплодить этих миров или вызывать ту же getLine несколько раз на одном и том же мире.
V>>Т.е. формально хаскелл полностью чистый язык. То, что writeFile что-то пишет на диск, программу на хаскеле не волнует никаким образом, для нее writeFile -- чистая ф-ия, преобразующая значение типа World. И, если ей подсунуть еще раз тот же мир (мысленно перенесясь назад во времени, например), то она выдаст тот же результат. S>Это не так. writeFile не пишет на диск, не принимает World. writeFile чиста не потому что выдает тот же результат, приняв мир, а потому что ее результат никак в мире не отражается.
А появившийся в мире файл -- это не результат?
S>>>Согласен, формально это уже impure. Но, при стечении хороших обстоятельств, при правильном использовании, мы избежим кодов возврата, исключений и т.п. Тогда сможем говорить о некой относительной чистоте. Но на самом деле мы говорим о нечистоте вывода. Т.е. я убежден в том, что никакие средства, в том числе изменения компилятора, не позволят сделать ввод/вывод или взаимодействие с внешним миром относительно самой программы чистым.
V>>Я выше попытался в очередной раз объяснить, как это возможно S>Ты объяснил только то, что пользуешься искаженным определением чистоты. А я выше попытался в очередной раз объяснить почему хаскель не нуждается в искажении чистоты.
И где же мое понятие чистоты искажено? Все строго по определению, ф-ии детерминированы (один и тот же результат на одном и том же World) и не имеют побочных эффектов (они в World).
S>>>Итого, uniqueness typing это лишь один из способов гарантировать невозможность повторного использования экземпляра с приятной особенностью, позволяющей делать замену по месту. Никакой чистоты при взаимодействии с внешним миром оно не обеспечивает.
V>>А реальный мир постоянно меняется, вне зависимости от выполнения программы и мы можем спокойно "заменять по месту" значения мира на текущее S>К счастью, та затычка не нуждается в какой-либо синхронизации с миром. Даже думать о ней как о затычке мира вредно. Это лишь способ организации последовательности выполнения грязных действий. S>Кстати, если ты считаешь действия чистыми, то для чего нужно организовывать порядок их выполнения?
Возьмем какой-нить sin x^2. Здесь важно сначала квадрат, потом синус, а не наоборот.
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
S>>Выше ты со мной согласился, что в отношении ввода-вывода хаскель чистый уже потому, что не дает средств (за исключением бэкдоров) для прямого вызова взаимодействия с I/O. Поэтому я не вижу ни малейшего повода для ослабления формальных критериев чистой функции по причине "что бы не ломать чистоту".
V>Какое такое ослабление критериев? Как раз наоборот, ничего не ослабляли. Вставили IO сохранив чистоту.
а куда дели взаимодействие с I/O девайсами? Только не надо про World писать. Я же вижу побочные эффекты извне программы.
S>>Даже считая действия нечистыми, хаскель чист, т.к. позволяет лишь комбинировать их, но не выполнять. Так ради чего ломать критерий чистоты? S>>Я написал одно и то же дважды разными словами в надежде что мою мысль будет проще понять.
V>Ну я уже раз 5 написал, если не больше )
Ты как-то вроде соглашаешься, а потом гнешь свою линию. Причем я никаких комментариев по поводу моего понимания предмета не вижу. Ты просто продавливаешь что "один раз не считается" и что "побочные эффекты остаются в мире".
V>>>Получается что какая-нить readLine :: World -> (String, World) обычная чистая хаскельная ф-ия, ведь она берет String не откуда-то а из аргумента. S>>Она не чиста по определению. Нет повода считать что String берется откуда-то из экземпляра мира.
V>Да почему же не чиста? Для одного и того же мира вернет одну и ту же строку и один и тот же измененный мир. Побочных эффектов нет.
Она не чиста потому что читает строку извне программы. Она читает строку, которую я ввожу. Эта строка к World-у не имеет никакого отношения. Он ее угадать не мог.
S>>По ее сигнатуре мы действительно не можем утверждать что она не является чистой. Но по сути процессов, происходящих при ее выполнении — вполне можем утверждать что она не чистая.
V>Каких таких процессов? Она меняет мир, а мир у нее является аргументом и результатом. Все чисто.
Аргументом и результатом у нее не мир, а затычка. Изменения, кстати, отражаются вовне программы. А в затычке как раз не отражаются.
S>>Давай предположим, что я согласился с тобой в вопросе чистоты действий. Тогда встает вопрос о том, почему хаскель запрещает (кроме бэкдоров) прямое выполнение действий? Они ведь "чистые", значит ничего испортить не могут и повлиять ни на что не должны!!! В чем тогда смысл?
V>Потому что они чистые пока у нас только один используемый экземпляр мира в программе. При прямой работе можно запросто наплодить этих миров или вызывать ту же getLine несколько раз на одном и том же мире.
Они не чистые, даже учитывая единичность экземпляра. Портят окружающую среду.
S>>Это не так. writeFile не пишет на диск, не принимает World. writeFile чиста не потому что выдает тот же результат, приняв мир, а потому что ее результат никак в мире не отражается.
V>А появившийся в мире файл -- это не результат?
Нет. Прочитай пожалуйста внимательно следующее:
Появившийся в мире файл есть побочный эффект от выполнения действия IO (). writeFile не выполняла это действие. Она его построила. Таким образом, результатом writeFile является действие. При повторном вызове writeFile с теми же аргументами вернется точно такое же действие, при этом в мирах (в реальном и в World так же) ничего не изменится. В реальном — потому что действие не выполняли, а только построили. В World не изменится потому как writeFile его не принимает и с ним не работает. Я доступно изъясняюсь?
Как ты объясняешь появление файла на диске, учитывая "отсутствие" побочных эффектов у действия?
S>>Ты объяснил только то, что пользуешься искаженным определением чистоты. А я выше попытался в очередной раз объяснить почему хаскель не нуждается в искажении чистоты.
V>И где же мое понятие чистоты искажено? Все строго по определению, ф-ии детерминированы (один и тот же результат на одном и том же World) и не имеют побочных эффектов (они в World).
Побочные эффекты снаружи программы (в файловой системе, в консоли, в БД, и т.п.), а World является частью программы.
S>>К счастью, та затычка не нуждается в какой-либо синхронизации с миром. Даже думать о ней как о затычке мира вредно. Это лишь способ организации последовательности выполнения грязных действий. S>>Кстати, если ты считаешь действия чистыми, то для чего нужно организовывать порядок их выполнения?
V>Возьмем какой-нить sin x^2. Здесь важно сначала квадрат, потом синус, а не наоборот.
потому что композиция.
А в случае с миром и IO — я вообще не понимаю, зачем выполнять какие-то действия, если у них "нет побочных эффектов" и их результат никому не нужен?
Свои объяснения я прекрасно понимаю. У тебя — какие-то сказки про мир.
Здравствуйте, samius, Вы писали:
S>>>Выше ты со мной согласился, что в отношении ввода-вывода хаскель чистый уже потому, что не дает средств (за исключением бэкдоров) для прямого вызова взаимодействия с I/O. Поэтому я не вижу ни малейшего повода для ослабления формальных критериев чистой функции по причине "что бы не ломать чистоту".
V>>Какое такое ослабление критериев? Как раз наоборот, ничего не ослабляли. Вставили IO сохранив чистоту. S>а куда дели взаимодействие с I/O девайсами? Только не надо про World писать. Я же вижу побочные эффекты извне программы.
Ты может чего-то и видишь извне программы, а для программы всё это IO -- просто последовательность значений World, передаваемых между ф-иями.
Пока не могу придумать новых способов, как тебе это объяснить.
S>>>Даже считая действия нечистыми, хаскель чист, т.к. позволяет лишь комбинировать их, но не выполнять. Так ради чего ломать критерий чистоты? S>>>Я написал одно и то же дважды разными словами в надежде что мою мысль будет проще понять.
V>>Ну я уже раз 5 написал, если не больше ) S>Ты как-то вроде соглашаешься, а потом гнешь свою линию. Причем я никаких комментариев по поводу моего понимания предмета не вижу. Ты просто продавливаешь что "один раз не считается" и что "побочные эффекты остаются в мире".
Приходится "продавливать", т.к. ты постоянно пытаешься это опровергнуть.
V>>>>Получается что какая-нить readLine :: World -> (String, World) обычная чистая хаскельная ф-ия, ведь она берет String не откуда-то а из аргумента. S>>>Она не чиста по определению. Нет повода считать что String берется откуда-то из экземпляра мира.
V>>Да почему же не чиста? Для одного и того же мира вернет одну и ту же строку и один и тот же измененный мир. Побочных эффектов нет. S>Она не чиста потому что читает строку извне программы. Она читает строку, которую я ввожу. Эта строка к World-у не имеет никакого отношения. Он ее угадать не мог.
Эта строка имеет отношение к World, т.к. именно он в программе и моделирует реальный мир.
S>>>По ее сигнатуре мы действительно не можем утверждать что она не является чистой. Но по сути процессов, происходящих при ее выполнении — вполне можем утверждать что она не чистая.
V>>Каких таких процессов? Она меняет мир, а мир у нее является аргументом и результатом. Все чисто. S>Аргументом и результатом у нее не мир, а затычка. Изменения, кстати, отражаются вовне программы. А в затычке как раз не отражаются.
А программе то какое до этого дело? Для программы соблюдены все критерии чистоты, а то что World -- затычка, это уже неважно.
S>>>Давай предположим, что я согласился с тобой в вопросе чистоты действий. Тогда встает вопрос о том, почему хаскель запрещает (кроме бэкдоров) прямое выполнение действий? Они ведь "чистые", значит ничего испортить не могут и повлиять ни на что не должны!!! В чем тогда смысл?
V>>Потому что они чистые пока у нас только один используемый экземпляр мира в программе. При прямой работе можно запросто наплодить этих миров или вызывать ту же getLine несколько раз на одном и том же мире. S>Они не чистые, даже учитывая единичность экземпляра. Портят окружающую среду.
Да насрать на окружающую среду, для программы она не отличается от какого-нить Integer или Map. Обычное значение, ну с чуть ограниченным набором действий.
S>>>Это не так. writeFile не пишет на диск, не принимает World. writeFile чиста не потому что выдает тот же результат, приняв мир, а потому что ее результат никак в мире не отражается.
V>>А появившийся в мире файл -- это не результат? S>Нет. Прочитай пожалуйста внимательно следующее: S>Появившийся в мире файл есть побочный эффект от выполнения действия IO (). writeFile не выполняла это действие. Она его построила. Таким образом, результатом writeFile является действие. При повторном вызове writeFile с теми же аргументами вернется точно такое же действие, при этом в мирах (в реальном и в World так же) ничего не изменится. В реальном — потому что действие не выполняли, а только построили. В World не изменится потому как writeFile его не принимает и с ним не работает. Я доступно изъясняюсь?
Аа, в этом смысле да. writeFile ф-ия возвращающая действие. Но я говорю про внутренности этого действия.
S>Как ты объясняешь появление файла на диске, учитывая "отсутствие" побочных эффектов у действия?
Оно же меняет мир. Ты как-то никак не можешь понять, что если мир считать аргументом и результатом, то ф-ии становятся чистыми. Где у ф-ии побочный эффект -- во внешнем мире, а если этот мир является агрументом и результатом ф-ии?
S>>>Ты объяснил только то, что пользуешься искаженным определением чистоты. А я выше попытался в очередной раз объяснить почему хаскель не нуждается в искажении чистоты.
V>>И где же мое понятие чистоты искажено? Все строго по определению, ф-ии детерминированы (один и тот же результат на одном и том же World) и не имеют побочных эффектов (они в World). S>Побочные эффекты снаружи программы (в файловой системе, в консоли, в БД, и т.п.), а World является частью программы.
Тут взгяд идет от программы. Программа не знает о мире ничего
S>>>К счастью, та затычка не нуждается в какой-либо синхронизации с миром. Даже думать о ней как о затычке мира вредно. Это лишь способ организации последовательности выполнения грязных действий. S>>>Кстати, если ты считаешь действия чистыми, то для чего нужно организовывать порядок их выполнения?
V>>Возьмем какой-нить sin x^2. Здесь важно сначала квадрат, потом синус, а не наоборот. S>потому что композиция. S>А в случае с миром и IO — я вообще не понимаю, зачем выполнять какие-то действия, если у них "нет побочных эффектов" и их результат никому не нужен?
Как же не нужен, нам нужен измененный мир
S>Свои объяснения я прекрасно понимаю. У тебя — какие-то сказки про мир.
Здравствуйте, m e, Вы писали:
ME>именно поэтому я и употребляю словосочетание "более декларативен"
И вы, наверное, обладаете секретной техникой, которая позволяет вам упорядочивать языки по декларативности.
ME>если же ты не согласен, и настаиваешь на своем, самобытном определении слова "декларативность", то пожалуста выпиши это определение полностью
Я вовсе не настаиваю на "своем, самобытном" определении. Я пользуюсь общепринятым. Без ссылочной прозрачности определение декларативности станет чисто психологическим, т.е. будет характеризовать не объект классификации, а мозговые тараканы классификатора. Не знаю как вам — а мне до последних нет дела.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, m e, Вы писали:
ME>это неправильная аналогия
Аналогия правильная. Вам стало обидно, что C++ обижают и вы написали, что хотя и не знаете, будут ли проблемы с полиморфизмом в хаскеле-2010-с-расширениями (который называется просто хаскель), но думаете, что будут.
ME>правильная будет такой: пришел MigMit и стал всем продавать нетеряющиеся ложки
Нет. MigMit раньше исходил из ошибочного предположения, что в C++ есть параметрический полиморфизм. Но параметрический полиморфизм означает, что forall a. Box a описывает бесконечное кол-во типов. В C++ же кол-во типов всегда конечно, поэтому полиморфизм там только ad-hoc. Выяснив, как дела обстоят на самом деле, MigMit решил предостеречь остальных от следования предположению, которое на практике оказалось ошибочным.
ME>я продемонстрировал, как теряются серебрянные ложки, и отсюда вывел *правдоподобное* заключение, что и золотые тоже могут потеряться
Вы атаковали пример, проверяющий на наличие ПП в языке и иллюстрирующий проблему со стороны, не имеющей никакого отношения к ПП и иллюстрирующий проблемы с сабтайпингом и неразмеченными объединениями (вопросов нет — это настоящий заповедник грабель). В хаскеле неразмеченных объединений нет, а значит нет и такого направления для атаки, поэтому вы высосали из пальца наспекулировали несуществующую проблему.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, m e, Вы писали:
ME>там четко написано "Portability : non-portable (GHC Extensions)", значит ты должен был ответить "мы говорим не о хаскеле, а о GHC Extensions" и не путать меня и окружающих
Думаю, что окружающие в курсе, что "стандартный хаскель с расширениями" называется просто хаскель. А когда говорят о стандартном, всегда явно это указывают: haskell 98 или haskell 2010.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
V>>>Какое такое ослабление критериев? Как раз наоборот, ничего не ослабляли. Вставили IO сохранив чистоту. S>>а куда дели взаимодействие с I/O девайсами? Только не надо про World писать. Я же вижу побочные эффекты извне программы.
V>Ты может чего-то и видишь извне программы, а для программы всё это IO -- просто последовательность значений World, передаваемых между ф-иями.
А как ты считаешь, программы на C видят побочные эффекты извне программы, или для них это просто последоватльность функций внутри программы?
V>Пока не могу придумать новых способов, как тебе это объяснить.
Не нужно новых способов. Я понимаю, что ты хочешь мне объяснить, просто в корне не согласен с этим.
В свою очередь ты вроде соглашаешься с тем что я объясняю, но все равно пытаешься мне объяснить то, с чем я не согласен.
S>>>>Даже считая действия нечистыми, хаскель чист, т.к. позволяет лишь комбинировать их, но не выполнять. Так ради чего ломать критерий чистоты? S>>>>Я написал одно и то же дважды разными словами в надежде что мою мысль будет проще понять.
S>>Ты как-то вроде соглашаешься, а потом гнешь свою линию. Причем я никаких комментариев по поводу моего понимания предмета не вижу. Ты просто продавливаешь что "один раз не считается" и что "побочные эффекты остаются в мире".
V>Приходится "продавливать", т.к. ты постоянно пытаешься это опровергнуть.
Я пытаюсь понять. Зачем нужно забивать на побочные эффекты вне программы что бы объяснить чистоту хаскеля, если ее можно объяснить с учетом наличия побочных эффектов?
S>>Она не чиста потому что читает строку извне программы. Она читает строку, которую я ввожу. Эта строка к World-у не имеет никакого отношения. Он ее угадать не мог.
V>Эта строка имеет отношение к World, т.к. именно он в программе и моделирует реальный мир.
Мы заходим на очередной круг. Вот что будет дальше: я скажу что World нифига не моделирует и не знает строку, которую я введу в консоль.
V>А программе то какое до этого дело? Для программы соблюдены все критерии чистоты, а то что World -- затычка, это уже неважно.
Для программы нарушаются критерии чистоты, потому как наблюдаются побочные эффекты извне программы.
S>>>>Давай предположим, что я согласился с тобой в вопросе чистоты действий. Тогда встает вопрос о том, почему хаскель запрещает (кроме бэкдоров) прямое выполнение действий? Они ведь "чистые", значит ничего испортить не могут и повлиять ни на что не должны!!! В чем тогда смысл?
V>Да насрать на окружающую среду, для программы она не отличается от какого-нить Integer или Map. Обычное значение, ну с чуть ограниченным набором действий.
Нет, не насрать, потому как определение чистоты не срет на окружающую среду.
S>>Появившийся в мире файл есть побочный эффект от выполнения действия IO ()....
V>Аа, в этом смысле да. writeFile ф-ия возвращающая действие. Но я говорю про внутренности этого действия.
Вот. Ты опять со мной согласился. Теперь вопрос (уже который раз). Зачем нужно считать это действие чистым? Хаскель вэ том не нуждается. Нечистота этого действия не бросает тень на чистоту хаскеля, т.к. выполнять действие мы "не можем". Так зачем нужно выдумывать что побочных эффектов от этого действия нет, если они остаются в файловой системе? Зачем выдумывать что изменения происходят лишь во внутреннем World программы и что программе чихать на их наличие во внешнем мире. Ведь есть определение чистой функции и там нет ничего про внутренний мир программы, но есть ввод/вывод в I/O и он считается прямым признаком грязи.
S>>Как ты объясняешь появление файла на диске, учитывая "отсутствие" побочных эффектов у действия?
V>Оно же меняет мир. Ты как-то никак не можешь понять, что если мир считать аргументом и результатом, то ф-ии становятся чистыми. Где у ф-ии побочный эффект -- во внешнем мире, а если этот мир является агрументом и результатом ф-ии?
Ты как-то никак не хочешь понять, что связи между внутренним миром World и внешним миром программы нет никакой. Ты хочешь убедить меня в том, что во внутреннем World происходят какие-то изменения, которые на самом деле происходят во внешнем мире и никак не связаны с внутренним World. Я уверен, что во внутреннем World нет никакой файловой системы, сетевых интерфейсов, консоли в конце концов
Мне кажется ты сам не должен верить в то что изменения внешнего мира как-то отражаются на внутреннем World. Дело даже не в этом. Я уже писал что World нужен как эстафетная палочка для прогонки по действиям с целью обеспечить порядок их выполнения. Порядок этот нужен для того, что бы побочные эффекты во внешнем мире легли в нужном порядке. Сам World при этом никаких изменений не претерпевает. Наверняка сказать не могу, я не добрался еще до этого. Но уверен на 120%.
S>>Побочные эффекты снаружи программы (в файловой системе, в консоли, в БД, и т.п.), а World является частью программы.
V>Тут взгяд идет от программы. Программа не знает о мире ничего.
А ей нужно что-то знать? Суть программы на хаскеле — построить действие. Действие само спросит у мира что ему надо и испортит его как сможет. Но это уже к программе на хаскеле отношения не имеет, т.к. main лишь возвращает действие, но не пытается его выполнить.
S>>А в случае с миром и IO — я вообще не понимаю, зачем выполнять какие-то действия, если у них "нет побочных эффектов" и их результат никому не нужен?
V>Как же не нужен, нам нужен измененный мир
Внутренний? Он не меняется. В нем нечему меняться. Меняется внешний мир. И его изменения — это побочный эффект выполнения действия.
S>>Свои объяснения я прекрасно понимаю. У тебя — какие-то сказки про мир. V>Жалко, что ты не понимаешь моих объяснений )
Я их понимаю, я их не могу принять.
Предлагаю взглянуть на определение pure function из википедии. Ты согласен с этим определением? Если нет, дай ссылку на то определение, с которым ты согласен.
Если согласен, то что ты подразумеваешь под external input from I/O devices и output to I/O devices соответственно? Только не надо опять про World, ведь в нем никаких девайсов нет.
Здравствуйте, samius, Вы писали:
S>Если согласен, то что ты подразумеваешь под external input from I/O devices и output to I/O devices соответственно? Только не надо опять про World, ведь в нем никаких девайсов нет.
А ты ответь, что понимаешь под semantically observable.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Если согласен, то что ты подразумеваешь под external input from I/O devices и output to I/O devices соответственно? Только не надо опять про World, ведь в нем никаких девайсов нет.
VE>А ты ответь, что понимаешь под semantically observable.
Семантически обозримые эффекты — изменения, которые обнаружимы средствами языка. Типично для императивных языков — глобальные переменные, любые изменяемые состояния.
ME>>там четко написано "Portability : non-portable (GHC Extensions)", значит ты должен был ответить "мы говорим не о хаскеле, а о GHC Extensions" и не путать меня и окружающих
K>Думаю, что окружающие в курсе, что "стандартный хаскель с расширениями" называется просто хаскель. А когда говорят о стандартном, всегда явно это указывают: haskell 98 или haskell 2010.
очень, очень любопытно
так хаскелем у тебя называется хаскель с какими расширениями? с расширениями любого из почти 10 компиляторов? или только с расширениями ghс? если так, то какого хрена такая дискриминация?
K>Аналогия правильная. Вам стало обидно, что C++ обижают и вы написали, что хотя и не знаете, будут ли проблемы с полиморфизмом в хаскеле-2010-с-расширениями (который называется просто хаскель), но думаете, что будут.
с++ обижают не зря -- полностью проверяемого компилятором ПП там, действительно, похоже, нет (хотя можно легко сделать частично проверяемый)
*НО* при этом эмуляция зависимых типов параметрическим полиморфизмом -- это примерно как коленвал ДВС из дерева
K>Вы атаковали пример, проверяющий на наличие ПП в языке и иллюстрирующий проблему со стороны, не имеющей никакого отношения к ПП и иллюстрирующий проблемы с сабтайпингом и неразмеченными объединениями (вопросов нет — это настоящий заповедник грабель). В хаскеле неразмеченных объединений нет, а значит нет и такого направления для атаки, поэтому вы высосали из пальца наспекулировали несуществующую проблему.
зато там есть _|_
надо попробовать его использовать как вектор атаки
да и вообще -- есть можно сказать большое число векторов атаки, и хрен докажешь, что ПП че-то-там гарантирует, если есть расширения, которые не рассматривались специально на тему "а не сломаем ли мы вот это..."
Здравствуйте, m e, Вы писали:
ME>>>там четко написано "Portability : non-portable (GHC Extensions)", значит ты должен был ответить "мы говорим не о хаскеле, а о GHC Extensions" и не путать меня и окружающих
K>>Думаю, что окружающие в курсе, что "стандартный хаскель с расширениями" называется просто хаскель. А когда говорят о стандартном, всегда явно это указывают: haskell 98 или haskell 2010.
ME>очень, очень любопытно
ME>так хаскелем у тебя называется хаскель с какими расширениями? с расширениями любого из почти 10 компиляторов? или только с расширениями ghс? если так, то какого хрена такая дискриминация?
Учитывая, что ghc разрабатывается основным автором хаскелла и является по сути единственным активно используемым компилятором промышленного качества, то под хаскеллом обычно понимают текущую его реализацию в ghc (если только не говорят прямо haskell 98 / 2010).
Здравствуйте, samius, Вы писали:
VE>>А ты ответь, что понимаешь под semantically observable. S>Семантически обозримые эффекты — изменения, которые обнаружимы средствами языка. Типично для императивных языков — глобальные переменные, любые изменяемые состояния.
Ну осталось выяснить, что есть побочный эффект. Вывод строки функцией, единственной целью которой и является вывод строки, это побочный эффект или основной?
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>Ну осталось выяснить, что есть побочный эффект. Вывод строки функцией, единственной целью которой и является вывод строки, это побочный эффект или основной?
Основным эффектом вычисления функции является то, что она возвращает в качестве результата. Все остальное — побочный.
Здравствуйте, samius, Вы писали:
VE>>Ну осталось выяснить, что есть побочный эффект. Вывод строки функцией, единственной целью которой и является вывод строки, это побочный эффект или основной? S>Основным эффектом вычисления функции является то, что она возвращает в качестве результата. Все остальное — побочный.
Ну и putStrLn, например, возвращает IO (). Для входного "test" всегда один и тот же. Значит, функция чистая?
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Основным эффектом вычисления функции является то, что она возвращает в качестве результата. Все остальное — побочный.
VE>Ну и putStrLn, например, возвращает IO (). Для входного "test" всегда один и тот же. Значит, функция чистая?
В точности так. одно и то же — значит детерминирована. Не портит окружающую среду при выполнении — значит без побочных эффектов. Чистая по формальному определению.
Здравствуйте, samius, Вы писали:
VE>>Ну и putStrLn, например, возвращает IO (). Для входного "test" всегда один и тот же. Значит, функция чистая? S>В точности так. одно и то же — значит детерминирована. Не портит окружающую среду при выполнении — значит без побочных эффектов. Чистая по формальному определению.
Здравствуйте, samius, Вы писали:
V>>Жалко, что ты не понимаешь моих объяснений )
S>Я их понимаю, я их не могу принять.
Так, похоже у нас просто разногласие в определениях.
Если считать, что ф-ия, занимающаяся внешним I/O, нечистая, то какая-нить getLine :: World -> (a, World) будет нечистой. Это твое мнение, исходящее из определения в википедии. Оке, при таком определении, getLine действительно нечистая.
Мое определение чистоты сильно короче. Чистая ф-ия возвращает один и тот же результат на одних и тех же аргументах. Всё. С таким определением getLine становится чистой, т.к. на одном и том же аргументе (мире, где пользователь вводит одну и ту же строку) ф-ия возвращает один и тот же результат, равно как и какой нить putLine на одом и том же мире и строке всегда возвращает один и тот же мир с добавленной строкой.
Т.е., если выбросить из определения детали про I/O, что, похоже, для тебя принципиально важно, то getLine чистая (мое мнение из моего определения чистоты), если же оставить, то грязная (твое мнение из твоего определения).
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>Ну и putStrLn, например, возвращает IO (). Для входного "test" всегда один и тот же. Значит, функция чистая? S>>В точности так. одно и то же — значит детерминирована. Не портит окружающую среду при выполнении — значит без побочных эффектов. Чистая по формальному определению.
VE>А о чём тогда спор?
Спор о чистоте действия, которое возвращает putStrLn.
Моя позиция заключается в том, что действие IO a в общем случае не чисто. Я не исключаю существования чистых IO a, но то что заворачивается в IO a, как правило носит нечистый характер.
Результат getChar :: IO Char недетерминирован, т.к. принимает символ извне
Результат putStrLn :: IO () имеет побочный эффект — портит консоль в реальном мире.
Владимир считает что действия чисты, т.к. работают с World, который якобы представляет собой отражение реального мира. И нечистота этих действий не интересует программу изнутри программы.
Здравствуйте, vshabanov, Вы писали:
V>Здравствуйте, samius, Вы писали:
V>Так, похоже у нас просто разногласие в определениях.
Везет мне на разногласия в определениях.
V>Если считать, что ф-ия, занимающаяся внешним I/O, нечистая, то какая-нить getLine :: World -> (a, World) будет нечистой. Это твое мнение, исходящее из определения в википедии. Оке, при таком определении, getLine действительно нечистая.
(*)
все-таки getLine :: IO String является чистой.
А уж IO String, которая раскрывается в IO a :: World -> (a, World) нечистая. Мне кажется что ты понимаешь, о чем я, просто уточняю на всякий случай.
V>Мое определение чистоты сильно короче. Чистая ф-ия возвращает один и тот же результат на одних и тех же аргументах. Всё. С таким определением getLine становится чистой, т.к. на одном и том же аргументе (мире, где пользователь вводит одну и ту же строку) ф-ия возвращает один и тот же результат, равно как и какой нить putLine на одом и том же мире и строке всегда возвращает один и тот же мир с добавленной строкой.
Вот в этом и проблема. Это только твое определение, или кто-то еще им пользуется? На самом деле я на рсдн не первый раз встречаю форумчан с собственными определениями. И в отношении чистоты уже как минимум двое кроме тебя пользуются схожим с твоим определением чистоты. Но мне кажется что это неправильное определение, т.к. в литературе я его в таком виде не встречал.
V>Т.е., если выбросить из определения детали про I/O, что, похоже, для тебя принципиально важно, то getLine чистая (мое мнение из моего определения чистоты), если же оставить, то грязная (твое мнение из твоего определения).
V>Идет?
С поправкой выше (*) — идет.
V>Учитывая, что ghc разрабатывается основным автором хаскелла и является по сути единственным активно используемым компилятором промышленного качества, то под хаскеллом обычно понимают текущую его реализацию в ghc (если только не говорят прямо haskell 98 / 2010).
для обсуждения на форуме вполне нормально назвать хаскелем скажем ghc7-хаскель
но вот в качестве ответа на вопрос
ME>>ммм... а мы про хаскель говорим или про что?
называть хаскелем скажем ghc7-хаскель уже будет неверно
в мире с++ на аналогичный вопрос ответили бы "мы говорим о с++ с расширениями g++", а не "мы говорим о с++"; если бы кто-то начал выпендриваться, защищая название "с++" для g++, его бы быстро поставили на место
очень жаль, что такой дурной тон апологеты хаскеля пытаются защищать, забывая о стандартах
з.ы. "текущая" реализация -- это тоже недостаточно определенное понятие
Здравствуйте, samius, Вы писали:
S>Спор о чистоте действия, которое возвращает putStrLn.
А какая разница? Хаскель от этого грязнее не становится. А World — это какое-то божество, в Haskell непредставимое, содержащее информацию обо всей вселенной. Ну и сколлапсирует ли волновая функция в одно и то же при одинаковых World — это лишь вопрос веры.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Спор о чистоте действия, которое возвращает putStrLn.
VE>А какая разница? Хаскель от этого грязнее не становится.
Разницы действительно никакой. И хаскель от этого не станет ни чище ни грязнее. Причем, позиция в этом вопросе никак качественно не повлияет на код реальных приложений. Это как в ситуации когда ОО-программисту с 15-летним стажем открывают глаза на тот факт что перегрузка методов тоже есть полиморфизм. Куча охов-ахов, флейма и т.п. Но по сути ничего не меняется.
VE>А World — это какое-то божество, в Haskell непредставимое, содержащее информацию обо всей вселенной. Ну и сколлапсирует ли волновая функция в одно и то же при одинаковых World — это лишь вопрос веры.
Не принимаю мистику в программировании. Я могу понимать что чего-то не понимаю, но заставлять себя думать что там божество не могу и не хочу. Тем более что в случае с World-ом — там лишь одна вывеска.
Здравствуйте, Klapaucius, Вы писали:
K>Нет. MigMit раньше исходил из ошибочного предположения, что в C++ есть параметрический полиморфизм. Но параметрический полиморфизм означает, что forall a. Box a описывает бесконечное кол-во типов. В C++ же кол-во типов всегда конечно, поэтому полиморфизм там только ad-hoc. Выяснив, как дела обстоят на самом деле, MigMit решил предостеречь остальных от следования предположению, которое на практике оказалось ошибочным.
В С++ количество описываемых шаблонами типов бесконечно. Но бесконечно только на этапе компиляции — именно в это время идут вычисления над типами.
На этапе же выполнения остаётся только код скомпилированный для конечного числа типов. Либо компиляция не завершается.
См. мой комментарий
Здравствуйте, m e, Вы писали:
ME>*НО* при этом эмуляция зависимых типов параметрическим полиморфизмом -- это примерно как коленвал ДВС из дерева
Никакие зависимые типы тут не "эмулируются". Типы в этих примерах не зависят от значений.
И, разумеется, для решения реальных задач такое не часто используется. Но это ведь просто иллюстрация проблем, которые в других областях проявлятся будут. Как проблемы с раздельной компиляцией, например.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Tonal-, Вы писали:
T>В С++ количество описываемых шаблонами типов бесконечно.
Это совсем не очевидно, скорее очевидно обратное. Число типов, описываемых шаблоном — это число инстанциаций шаблона с разными типами. Т.е. понятно, что с одинаковым кодом можно как-то бороться, но семантика именно такая — для каждого подстаялемого в шаблон типа — своя реализация. Т.е. это ad-hoc полиморфизм, а не параметрический.
T>Но бесконечно только на этапе компиляции — именно в это время идут вычисления над типами. T>На этапе же выполнения остаётся только код скомпилированный для конечного числа типов. Либо компиляция не завершается.
Рантайм-то тут вообще причем? Обсуждаемые примеры работают на этапе компиляции, что на хаскеле — что на C#. Динамические возможности CLR используются в C# для генерации специализаций для анбоксед-типов (т.е. для оптимизации, на семантику это не влияет). В хаскеле и яве полиморфизм бывает только для боксед, но и тут есть некоторые обходные пути для оптимизации. Понятно, что C++ не может позволить себе полиморфизм ни ценой боксинга всего, ни ценой JIT. Потому-то там параметрического полиморфизма и нет.
T>См. мой комментарий
Здравствуйте, m e, Вы писали:
ME>так хаскелем у тебя называется хаскель с какими расширениями?
Да почему у меня-то? Это позиция авторов языка изложенная в статье по истории разработки хаскеля. Хаскелем называется любое надмножество стандарта 98-го года. Если же речь идет о конкртеном стандарте, то это указывается явно. Но только речь о стандартах 98-го и 10-го года не идет почти никогда, кроме вот такого вот форумного диспута, потому что стандарты эти ни практического ни теоретического интереса не представляют.
ME> с расширениями любого из почти 10 компиляторов? или только с расширениями ghс? если так, то какого хрена такая дискриминация?
С расширениями актуальных версий любого из почти одного компилятора. Если речь пойдет о каком-то особенно новом расширении — укажут версию GHC, все расширения до 7.0 включительно подразумеваются по умолчанию. Это вообще характерно для языков с одной флагманской реализацией. Были, конечно, когда-то времена, когда было две используемых реализации (по той же причине что и у SML сейчас — в одной есть REPL, в другой — нормальный компилятор), но hugs умер где-то в 06-ом и теперь реализация одна (кое-как ведутся работы по превращению компилятора Clean в компилятор хаскеля, что, понятно, единственный шанс на выживание этого компилятора, но вероятность того, что эта реализация все-таки будет кем-то использоваться я считаю призрачной).
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Не принимаю мистику в программировании.
K>Тогда откуда это мистическое отличие необнаруживаемого из программы сайд-эффекта с изменением файла от сайд-эффекта с нагревом процессора?
Из определения чистоты.
Здравствуйте, samius, Вы писали:
K>>Тогда откуда это мистическое отличие необнаруживаемого из программы сайд-эффекта с изменением файла от сайд-эффекта с нагревом процессора? S>Из определения чистоты.
И где в этом определении описано, почему одни ненаблюдаемые сайд-эффекты равнее других? По моему, когда говорят о сайд-эффектах, имеют в виду наблюдаемые, с помощью которых можно ссылочную прозрачность нарушить. А ненаблюдаемые сайд-эффекты у любой функции есть.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>>>Тогда откуда это мистическое отличие необнаруживаемого из программы сайд-эффекта с изменением файла от сайд-эффекта с нагревом процессора? S>>Из определения чистоты.
K>И где в этом определении описано, почему одни ненаблюдаемые сайд-эффекты равнее других? По моему, когда говорят о сайд-эффектах, имеют в виду наблюдаемые, с помощью которых можно ссылочную прозрачность нарушить. А ненаблюдаемые сайд-эффекты у любой функции есть.
В определениях не пишут "почему". В определениях пишут о том, что является определяемым понятием, а что — нет. Функции с вводом выводом чистыми не являются по определению.
А вот то, греет функция с вводом выводом процессор, или нет, однозначно и не разобрать. Вполне может и остужать.
Здравствуйте, samius, Вы писали:
S>В определениях не пишут "почему". В определениях пишут о том, что является определяемым понятием, а что — нет. Функции с вводом выводом чистыми не являются по определению.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>В определениях не пишут "почему". В определениях пишут о том, что является определяемым понятием, а что — нет. Функции с вводом выводом чистыми не являются по определению.
VE>Каков критерий ввода-вывода?
http://en.wikipedia.org/wiki/Input/output
Добавлю только (от себя), что работу всякого рода механизмов, обеспечивающие работу "памяти" прозрачно для самой программы (вроде виртуальной памяти), я не рассматриваю как ввод вывод в отношении выполняемой программы. Наверное, следует отнести их к вводу/выводу операционной системы.
Здравствуйте, Klapaucius, Вы писали: T>>В С++ количество описываемых шаблонами типов бесконечно. K>Это совсем не очевидно, скорее очевидно обратное. Число типов, описываемых шаблоном — это число инстанциаций шаблона с разными типами. Т.е. понятно, что с одинаковым кодом можно как-то бороться, но семантика именно такая — для каждого подстаялемого в шаблон типа — своя реализация. Т.е. это ad-hoc полиморфизм, а не параметрический.
Ежели число описываемых типов не бесконечно, значит оно ограничено для любых условий и можно привести оценку сверху этого ограничения (M мало, возмём N).
Приведи хотя бы для простейшего случая:
С другой стороны ты прав в том, что число воплощений (инстанций) шаблона в конкретной программе всегда ограничено.
Но это и понятно, ведь шаблоны вводились в С++ не как универсальный язык общего назначения, а как средство генерирования кода.
T>>Но бесконечно только на этапе компиляции — именно в это время идут вычисления над типами. T>>На этапе же выполнения остаётся только код скомпилированный для конечного числа типов. Либо компиляция не завершается. K>Рантайм-то тут вообще причем? Обсуждаемые примеры работают на этапе компиляции, что на хаскеле — что на C#. Динамические возможности CLR используются в C# для генерации специализаций для анбоксед-типов (т.е. для оптимизации, на семантику это не влияет). В хаскеле и яве полиморфизм бывает только для боксед, но и тут есть некоторые обходные пути для оптимизации. Понятно, что C++ не может позволить себе полиморфизм ни ценой боксинга всего, ни ценой JIT. Потому-то там параметрического полиморфизма и нет.
C++ Не может позволить себе динамический ПП. Статический, времени компиляции, ПП в нём есть. T>>См. мой комментарий
K>Это вообще нерелевантно обсуждаемой теме. Мы говорим о случаях, когда все что нужно известно на этапе компиляции.
Вроде обсуждали вот этот пост.
Я его упростил.
И в коде уважаемого MigMit, и в моём с помощью шаблонов описывается бесконечное количество типов.
Но конкретный тип (множество типов в примере MigMit) для воплощения на этапе компиляции не задаётся, на чём компилятор С++ и обламывается.
В случае Java/.net это решается тем, что у них есть возможность компиляции в рантайме. Это не JIT это ближе к интерпретации.
Причём, я не помню есть ли в С++ требования непременного воплощения всех типов используемых для воплощения указанного — вроде нет.
Так что вполне возможны компиляторы, которые не будут создавать временные воплощения используемые только во время вычислений на типах.
Но и это не сделает работоспособными обсуждаемые примеры — ведь конкретное воплощение таки задаётся в рантайме...
Здравствуйте, samius, Вы писали:
S>Функции с вводом выводом чистыми не являются по определению.
Если под общепринятым определением понимается википедийное, то там ведь явным образом оговаривается, что сайд-эффект должен быть "semantically observable". Нет?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Tonal-, Вы писали:
T>Ежели число описываемых типов не бесконечно, значит оно ограничено для любых условий и можно привести оценку сверху этого ограничения (M мало, возмём N). T>Приведи хотя бы для простейшего случая: T>
Это неполный код, который вообще не имеет смысла и не может быть скомпилирован (иначе, чем просто выкинут) без инстанциаций. Если вы приведете полный код с инстанциациями для N типов, то верхняя оценка будет N. В языке с ПП похожий по виду (но не по смыслу) код полностью самодостаточен и может быть скомпилирован.
T>С другой стороны ты прав в том, что число воплощений (инстанций) шаблона в конкретной программе всегда ограничено.
То-то и оно.
T>Но это и понятно, ведь шаблоны вводились в С++ не как универсальный язык общего назначения, а как средство генерирования кода.
И снова верно. Но причем тут ПП?
T>C++ Не может позволить себе динамический ПП.
"Динамического" ПП не существует.
T> Статический, времени компиляции, ПП в нём есть.
Нет.
T>Вроде обсуждали вот этот пост. T>Я его упростил.
Вы написали код, требующий зависимых типов. МигМит написал код, для которого достаточно параметрического полиморфизма в стиле ML (и с ограниченной квантификацией). Следовательно, об упрощении говорить не приходится.
T>В случае Java/.net это решается тем, что у них есть возможность компиляции в рантайме. Это не JIT это ближе к интерпретации.
Вы, похоже, недостаточно внимательно прочли мой пост, на который отвечаете. Компиляция в рантайме в C# решает в данном случае только часть проблем с производительностью, она для оптимизации используется. И если мне беспамятство не изменяет, в первых бетах CLR 2 такой оптимизации не было. В Java рантайм-компиляция тут вообще ничего не решает: в рантайме компилируется вовсе не Ява, а язык, в котором параметрического полиморфизма нет. В случае с хаскелем, ни сам он, ни какой из промежуточных языков в рантайме не компилируется. Никакая компиляция в рантайме для ПП вообще не обязательна.
T>конкретное воплощение таки задаётся в рантайме...
Нет, конкретное воплощение задается не в рантайме, а в компайлтайме.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Функции с вводом выводом чистыми не являются по определению.
K>Если под общепринятым определением понимается википедийное, то там ведь явным образом оговаривается, что сайд-эффект должен быть "semantically observable". Нет?
Чего гадать то?
Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.
S>Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.
Ну, о чем я и говорил.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>
S>>Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.
K>Ну, о чем я и говорил.
А это о чем я говорил. (подчеркнул)
S>>>Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.
S>А это о чем я говорил. (подчеркнул)
Очевидно, что "semantically observable" относится и к side effect и к output. Иначе такое определение просто не имеет смысла.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>Очевидно, что "semantically observable" относится и к side effect и к output. Иначе такое определение просто не имеет смысла.
Я не понимаю, что такое semantically observable output. Пояснишь?
Здравствуйте, samius, Вы писали:
K>>Очевидно, что "semantically observable" относится и к side effect и к output. Иначе такое определение просто не имеет смысла. S>Я не понимаю, что такое semantically observable output. Пояснишь?
Странно, тут рядом ваше сообщение, из которого можно сделать вывод, что понимаете. Т.е. функция форсирующая список, не помещающийся в память выполняет "семантически ненаблюдаемый" I/O. Но с помощью трюков с уникальным значением RealWorld как раз и достигается семантическая ненаблюдаемость (невозможность нарушить ссылочную прозрачность) для любого I/O. А иначе — за что боролись? — если I/O производит семантически наблюдаемые сайд-эффекты, много ли смыла ограничивать возможность создать "семантически наблюдаемый" сайд-эффект без I/O? У GHC даже прайм-опов нарушающих ссылочную прозрачность нет, нарушить может только их "неудачная" комбинация, что и решается абстрактным типом IO для которого все комбинаторы дают только "удачные" комбинации.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>>>Очевидно, что "semantically observable" относится и к side effect и к output. Иначе такое определение просто не имеет смысла. S>>Я не понимаю, что такое semantically observable output. Пояснишь?
K>Странно, тут рядом ваше сообщение, из которого можно сделать вывод, что понимаете. Т.е. функция форсирующая список, не помещающийся в память выполняет "семантически ненаблюдаемый" I/O.
Я написал, что вообще такое за I/O не считаю. Не программа себя сбрасывает в свап.
K>Но с помощью трюков с уникальным значением RealWorld как раз и достигается семантическая ненаблюдаемость (невозможность нарушить ссылочную прозрачность) для любого I/O.
Как это? Т.е. мы заменяем акцию с выводом в файл ее результатом и получаем на диске файл?
K> А иначе — за что боролись? — если I/O производит семантически наблюдаемые сайд-эффекты, много ли смыла ограничивать возможность создать "семантически наблюдаемый" сайд-эффект без I/O?
I/O производит I/O. Я не понимаю, как можно сематнически наблюдать отправленные байты по сетевому протоколу.
K>У GHC даже прайм-опов нарушающих ссылочную прозрачность нет, нарушить может только их "неудачная" комбинация, что и решается абстрактным типом IO для которого все комбинаторы дают только "удачные" комбинации.
Дык речь не о чистоте комбинирования IO, а о чистоте выполнения акций.
Здравствуйте, samius, Вы писали:
S>Как это? Т.е. мы заменяем акцию с выводом в файл ее результатом и получаем на диске файл?
Мы берем мир без файла на диске и возвращаем мир с файлом на диске.
S>I/O производит I/O. Я не понимаю, как можно сематнически наблюдать отправленные байты по сетевому протоколу.
Если мы можем сравнить мир в котором байты идут по протоколу и мир в котором не идут, мы сможем произвести семантическое наблюдение. Но мы не можем.
S>Дык речь не о чистоте комбинирования IO, а о чистоте выполнения акций.
Речь тут все время о чистоте выполнения акций. Комбинирование — оно любое будет чистым. А вот скомбинированная акция может и не быть, если уникальность RealWorld нарушается. Потому оставляются только такие комбинаторы, результат которых будет чистым.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
VE>>Каков критерий ввода-вывода?
S>http://en.wikipedia.org/wiki/Input/output S>Добавлю только (от себя), что работу всякого рода механизмов, обеспечивающие работу "памяти" прозрачно для самой программы (вроде виртуальной памяти), я не рассматриваю как ввод вывод в отношении выполняемой программы. Наверное, следует отнести их к вводу/выводу операционной системы.
А хард — это ввод-вывод? А USB-вентилятор? А вентилятор внутри системного блока? А нагревательный USB-прибор? А нагревательный прибор "процессор" внутри системного блока?
Какое-то интуитивное понятие, не формализованное.
Я бы вообще не использовал понятие "чистота", а только ссылочную прозрачность. Есть у нас гарантия, что factorial вернёт одно и то же, так пусть хоть через голубиную почту получает.
Здравствуйте, Klapaucius, Вы писали: T>>Ежели число описываемых типов не бесконечно, значит оно ограничено для любых условий и можно привести оценку сверху этого ограничения (M мало, возмём N). T>>Приведи хотя бы для простейшего случая: T>>
K>Это неполный код, который вообще не имеет смысла и не может быть скомпилирован (иначе, чем просто выкинут) без инстанциаций. Если вы приведете полный код с инстанциациями для N типов, то верхняя оценка будет N. В языке с ПП похожий по виду (но не по смыслу) код полностью самодостаточен и может быть скомпилирован.
Ещё раз.
В С++ вычисления проводятся в 2е стадии.
1. Во время компиляции проводятся вычисления констант и вычисления на типах: разрешения совмещений (перегрузки), определения конкретных типов шаблонов которые требуют воплощения.
Всё это делается для построения конкретного кода.
2. Во время выполнения работает построенный на первом этапе код.
Вычисления в первой фазе — иммутабельные, чисто функциональные. Именно здесь работает статический полиморфизм. Причём совмещения — это Ad hos, а шаблоны ПП.
В фазе выполнения почти никакая информация о типах не сохраняется. Работает динамический полиморфизм (виртуальность) через таблицы методов.
Т. е. приведённый мной код действительно не имеет смысла в рантайме но вполне может быть востребован во время компиляции.
Впрочем как и большинство кода stl.
T>>Вроде обсуждали вот этот пост. T>>Я его упростил. K>Вы написали код, требующий зависимых типов. МигМит написал код, для которого достаточно параметрического полиморфизма в стиле ML (и с ограниченной квантификацией). Следовательно, об упрощении говорить не приходится.
Я выделил ту часть, которая не даёт этим примерам скомпилироватся в С++.
Вне зависимости от типа полиморфизма эта часть общая.
T>>конкретное воплощение таки задаётся в рантайме... K>Нет, конкретное воплощение задается не в рантайме, а в компайлтайме.
Значит у нас разные понятия что такое воплощение.
У меня С++ное, а у тебя?
K>Вообще, эта ветка уже начинает напоминать уморительно смешной тред МигМита на ухогорлоносе.
О, спасибо за ссыль.
Я не поленился и перепёр haskell в шаблоны один в один:
//data Nil = Nilstruct Nil {};
//data Cons a = Cons Integer atemplate <int N, class A> struct Cons {
static const int val = N;
typedef A Rest;
};
//class ScalarProduct a where
// scalarProduct :: a -> a -> Integertemplate <class T1, class T2> struct scalarProd;
//instance ScalarProduct Nil where
// scalarProduct Nil Nil = 0template <> struct scalarProd<Nil, Nil> {
static const int res = 0;
};
//instance ScalarProduct a => ScalarProduct (Cons a) where
// scalarProduct (Cons n1 a1) (Cons n2 a2) = n1 * n2 + scalarProduct a1 a2template <int N1, int N2, class A1, class A2>
struct scalarProd<Cons<N1, A1>, Cons<N2, A2> > {
static const int res = N1 * N2 + scalarProd<A1, A2>::res;
};
//main' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integertemplate <int N, int I, class AS, class BS> struct make_quot;
//main' 0 _ as bs = scalarProduct as bstemplate <int I, class AS, class BS> struct make_quot<0, I, AS, BS> {
static const int res = scalarProd<AS, BS>::res;
};
//main' n i as bs = main' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)template <int N, int I, class AS, class BS> struct make_quot {
static const int res = make_quot<N - 1, I + 1, Cons<2*I + 1, AS>, Cons<I*I, BS> >::res;
};
//main :: Integer -> Integer
//main n = main' n 0 Nil Nil wheretemplate <int N> struct make {
static const int res = make_quot<N, 0, Nil, Nil>::res;
};
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>Каков критерий ввода-вывода?
S>>http://en.wikipedia.org/wiki/Input/output
VE>А хард — это ввод-вывод? А USB-вентилятор? А вентилятор внутри системного блока? А нагревательный USB-прибор? А нагревательный прибор "процессор" внутри системного блока?
Если программа посылает данные USB вентилятору, то это определенно вывод. Если принимает — определенно ввод. С другими девайсами аналогично.
VE>Какое-то интуитивное понятие, не формализованное.
Да, и что?
VE>Я бы вообще не использовал понятие "чистота", а только ссылочную прозрачность. Есть у нас гарантия, что factorial вернёт одно и то же, так пусть хоть через голубиную почту получает.
Если факториал считать голубинной почтой, то это ввод, вывод, + зависимость от голубей, коршунов, погоды, и прочей хрени. Детерминированным такой результат нельзя назвать даже интуитивно.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Как это? Т.е. мы заменяем акцию с выводом в файл ее результатом и получаем на диске файл?
K>Мы берем мир без файла на диске и возвращаем мир с файлом на диске.
Что за чудеса? Мир — внутренний объект программы, файл — внешний.
этак можно сказать что fprintf принимает неявно внешний мир и возвращает длину строки и новый мир с файлом.
S>>I/O производит I/O. Я не понимаю, как можно сематнически наблюдать отправленные байты по сетевому протоколу.
K>Если мы можем сравнить мир в котором байты идут по протоколу и мир в котором не идут, мы сможем произвести семантическое наблюдение. Но мы не можем.
S>>Дык речь не о чистоте комбинирования IO, а о чистоте выполнения акций.
K>Речь тут все время о чистоте выполнения акций. Комбинирование — оно любое будет чистым. А вот скомбинированная акция может и не быть, если уникальность RealWorld нарушается. Потому оставляются только такие комбинаторы, результат которых будет чистым.
Речь о том, что акция может не быть чистой не потому что уникальность мира, а потому что она делает ввод/вывод.
Здравствуйте, Tonal-, Вы писали:
T>В фазе выполнения почти никакая информация о типах не сохраняется.
Еще раз. Мы не говорим о времени выполнения. Вообще. Только о времени компиляции. О компиляции говорим, а о выполнении — наоборот — не говорим. Речь идет о компиляции. О компиляции речь идет. О компиляции. Понятно?
T>Т. е. приведённый мной код действительно не имеет смысла в рантайме но вполне может быть востребован во время компиляции.
Вот в том и дело, что может быть "востребован". А скомпилировать без какой-то конкретной специализации его нельзя. Можно только прекомпилировать — т.е. распарсить и сериализовать AST, например. Или скомпилировать в код, который порождает исходный из другого исходного. А просто скомпилировать, в код, который просто работает, его нельзя. А вот
id x = x
скомпилировать — наоборот — можно. И он будет лежать в скомпилированном модуле и ждать своего часа. Потому, что параметрический полиморфизм — это когда код один для любого типа (или для множества типов (бесконечного), соотествующих определенному предикату). Любого числа типов. Написанных до этого кода и после него. Но в c++ параметрического полиморфизма нет — там не бывает кода для любого типа — только для конечного набора предзаданных конкретных — т.е. ad-hoc.
T>Впрочем как и большинство кода stl.
Вот именно.
T>Я выделил ту часть, которая не даёт этим примерам скомпилироватся в С++. T>Вне зависимости от типа полиморфизма эта часть общая.
Вы не выделяли общей части — у этих примеров нет ничего общего. Они решают разные задачи разными способами.
При этом вы последовательно игнорируете все мои замечания по существу. Про то, что в случае Java язык в котором есть ПП компилируется статически, а тот, что JIT-ится — ПП не имеет. И то, что GHC вовсе нет JIT — так что ни на какую "динамику" и "интерпретацию" тут не свалить.
T>Я не поленился и перепёр haskell в шаблоны один в один:
Ох. Лучше бы вы не поленились читать то, что писал МигМит в том треде и что сейчас пишу я.
Вот смотрите:
module Main where
import System.Environment
data Nil = Nil
data Cons a = Cons Integer a
class ScalarProduct a where scalarProduct :: a -> a -> Integer
instance ScalarProduct Nil where
scalarProduct Nil Nil = 0
instance ScalarProduct a => ScalarProduct (Cons a) where
scalarProduct (Cons n1 a1) (Cons n2 a2) = n1 * n2 + scalarProduct a1 a2
test :: Integer -> Integer
test n = test' n 0 Nil Nil where
test' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
test' 0 _ as bs = scalarProduct as bs
test' n i as bs = test' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
-- test' n i as bs = test' (n-1) (i+1) as (Cons (i^2) bs)
main = print . test . read . head =<< getArgs
Видите, этот код принимает число как аргумент командной строки. Он компилируется. НЕ интерпретируется. И типы проверяет статически. Закомментированная строчка вызовет ошибку. Потому, что проверяются не абсолютные размеры списков, а то, что списки одинакового размера. А это известно на этапе компиляции, завит только от написанного кода, а не входных данных. И зависимые типы для этого не нужны — нужен параметрический полиморфизм в стиле ML + ограниченная квантификация (в Haskell — классы типов, в Java/C# констрейнты), которого в C++ нет.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Потому, что параметрический полиморфизм — это когда код один для любого типа (или для множества типов (бесконечного), соотествующих определенному предикату). Любого числа типов. Написанных до этого кода и после него. Но в c++ параметрического полиморфизма нет — там не бывает кода для любого типа — только для конечного набора предзаданных конкретных — т.е. ad-hoc.
В определении полиморфизма нет ни слова о времени компиляции и вообще компиляции.
K>При этом вы последовательно игнорируете все мои замечания по существу. Про то, что в случае Java язык в котором есть ПП компилируется статически, а тот, что JIT-ится — ПП не имеет. И то, что GHC вовсе нет JIT — так что ни на какую "динамику" и "интерпретацию" тут не свалить.
Т.е. в C# нет ПП потому что джитится?
Здравствуйте, samius, Вы писали:
S>В определении полиморфизма нет ни слова о времени компиляции и вообще компиляции.
Это, конечно, верно. И в языке, на котором нет возможности написать код такого типа, как обсуждаемый здесь (какой-нибудь ML, с оговорками) параметрический полимoрфизм вполне можно реализовать c помощью кодогенерации и анализа всей программы (как в каком-нибудь MLton, с оговорками). Раздельную компиляцию мы потеряем, но иначе как в длительности компиляции это не проявится. Написать код, который вроде бы должен тайпчекаться, при условии что у нас есть ПП, но не тайпчекается мы не можем. А не пойман — не вор. Не будь в C++ сабтайпинга — обнаружить указанную проблему было бы невозможно. По крайней мере таким способом. Ну а проблемы с раздельной компиляцией без каких-то семантических эффектов на уровне языка — это просто детали реализации и говорить о проблемах с ПП повода не дают.
S>Т.е. в C# нет ПП потому что джитится?
Нет, почему же? В C# ПП есть, а то, что JIT генерирует специализации для value-типов — это деталь реализации. Это просто был контрдовод против утверждения Tonal- (довольно странного), что ПП может работать только при условии динамической компиляции специализаций или интерпретации. Это, разумеется, не верно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>В определении полиморфизма нет ни слова о времени компиляции и вообще компиляции.
K>Это, конечно, верно. И в языке, на котором нет возможности написать код такого типа, как обсуждаемый здесь (какой-нибудь ML, с оговорками) параметрический полимoрфизм вполне можно реализовать c помощью кодогенерации и анализа всей программы (как в каком-нибудь MLton, с оговорками). Раздельную компиляцию мы потеряем, но иначе как в длительности компиляции это не проявится. Написать код, который вроде бы должен тайпчекаться, при условии что у нас есть ПП, но не тайпчекается мы не можем. А не пойман — не вор. Не будь в C++ сабтайпинга — обнаружить указанную проблему было бы невозможно. По крайней мере таким способом. Ну а проблемы с раздельной компиляцией без каких-то семантических эффектов на уровне языка — это просто детали реализации и говорить о проблемах с ПП повода не дают.
Да. Понятно. Просто увидел много слов на тему компиляции и подумал, что упор на нее.
S>>Т.е. в C# нет ПП потому что джитится?
K>Нет, почему же? В C# ПП есть, а то, что JIT генерирует специализации для value-типов — это деталь реализации. Это просто был контрдовод против утверждения Tonal- (довольно странного), что ПП может работать только при условии динамической компиляции специализаций или интерпретации. Это, разумеется, не верно.
Согласен.
Все-таки, хотелось бы понять, почему вы считаете что в C# ПП есть, а в C++ нет? Предлагаю взять тривиальную функцию id и посмотреть на нее безотносительно времени компиляции/джита в контексте утвреждения
Но в c++ параметрического полиморфизма нет — там не бывает кода для любого типа
Что в C++, что в C#, найдутся типы, для которых id работать (и даже компилироваться) не будет.
Здравствуйте, Паблик Морозов, Вы писали:
ПМ>Старнное заявление в разделе /decl. Вроде у всех более-менее декларативных и функциональны эзыков синтаксис весьма далёк от сишкоподобного. Если, конечно, это не эрзацы вроде Скалы или Менерле.
Дык все эти ваши языки и вымрут как динозавры, не оставив потомства.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, alex_public, Вы писали:
_>Теперь даже и не знаю где искать новую "серебряную пулю" для фана. Ну а о замене основных инструментов похоже пока даже и речи нет...
Серебряных пуль (панацеи) не бывает.
А для фана можешь попробовать нашу "игрушку". Она и изучается намного легче (людьми пришедшими из императивного мира), и в сто раз более практична.
Для практического использования тебе может не подойти дотнет. Но для изучения оно точно будет полезен. К тому же мы намереваемся сделать язык мультиплатфорным в будущем.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, samius, Вы писали:
VE>>А хард — это ввод-вывод? А USB-вентилятор? А вентилятор внутри системного блока? А нагревательный USB-прибор? А нагревательный прибор "процессор" внутри системного блока? S>Если программа посылает данные USB вентилятору, то это определенно вывод. Если принимает — определенно ввод. С другими девайсами аналогично.
А если программа посылает данные в оперативную память? А потом принимает. А потом результат получается детерминированный.
А со стороны сидит Вася и смотрит на эту всю чехарду с памятью, наблюдает, как туда-сюда данные бегают. Это ввод-вывод?
А если "оперативная память" находится на удалённом компьютере и мы шлём прямо такие команды "read 0x10", "write 0x10 2", а результат всё равно детерминированный (про подмену значений не надо упоминать, их и в памяти на текущем компе менять можно). Это ввод-вывод?
VE>>Какое-то интуитивное понятие, не формализованное. S>Да, и что?
Ну ничаво. Тогда вы до гробовой доски не договоритесь. Я вот включение вентилятора считаю ничем не отличающимся от нагрева проца. Человек видит результат обоих действий, программа — не видит (если нет функции включенЛиВентилятор, например). С т.з. программы и человека между этими действиями отличия нет. Так почему одно ввод-вывод, а другое — нет?
S>Если факториал считать голубинной почтой, то это ввод, вывод, + зависимость от голубей, коршунов, погоды, и прочей хрени. Детерминированным такой результат нельзя назвать даже интуитивно.
Речь не о детерминированности. Если я рядом напишу математическое доказательство "мамой клянус", что голубь не наврёт, то пусть он будет и детерминированным, но всё равно с вводом-выводом же в твоём понимании, ибо голуби летают туда-суда. Ты ещё скажи, что работа с оперативной памятью ни от чего не зависит, и потому у нас какие-то там гарантии. Зависит от тех же коршунов и погоды, но почему-то не ввод-вывод.
Но тогда в чём отличие от засирании оперативки при вычислении списка? Со стороны это видно, изнутри программы — нет.
Здравствуйте, VladD2, Вы писали:
VD>Дык все эти ваши языки и вымрут как динозавры, не оставив потомства.
Они уже оставили. Почти все нынешние языки впитали их гены и многие (в том числе Nemerle) являются потомками в том числе функциональных языков. А вот оставят ли потомство ваши, это ещё большой вопрос.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>А хард — это ввод-вывод? А USB-вентилятор? А вентилятор внутри системного блока? А нагревательный USB-прибор? А нагревательный прибор "процессор" внутри системного блока? S>>Если программа посылает данные USB вентилятору, то это определенно вывод. Если принимает — определенно ввод. С другими девайсами аналогично.
VE>А если программа посылает данные в оперативную память? А потом принимает. А потом результат получается детерминированный.
В википедии все написано. процессор и память процесса считаются внутренностями программы. VE>А со стороны сидит Вася и смотрит на эту всю чехарду с памятью, наблюдает, как туда-сюда данные бегают. Это ввод-вывод?
Зависит от того, в чью память данные бегают. VE>А если "оперативная память" находится на удалённом компьютере и мы шлём прямо такие команды "read 0x10", "write 0x10 2", а результат всё равно детерминированный (про подмену значений не надо упоминать, их и в памяти на текущем компе менять можно). Это ввод-вывод?
судя по википедии — да. Во всяком случае для программы, выполняющейся на конкретном компьютере. Если рассматривать систему из множества компьютеров как одну, то обмен данными между ними не будет считаться вводом/выводом. Но это уже я додумываю.
VE>>>Какое-то интуитивное понятие, не формализованное. S>>Да, и что?
VE>Ну ничаво. Тогда вы до гробовой доски не договоритесь. Я вот включение вентилятора считаю ничем не отличающимся от нагрева проца. Человек видит результат обоих действий, программа — не видит (если нет функции включенЛиВентилятор, например). С т.з. программы и человека между этими действиями отличия нет. Так почему одно ввод-вывод, а другое — нет?
Ни нагрев процессора, ни включение вентилятора не является вводом/выводом в случае, если программа не управляет включением вентилятора посылкой специальных команд.
Если рассмотреть систему, в которой отправка данных во внешний мир осуществляется с помощью включения/выключения вентилятора и аналога азбуки Морзе, то я согласен рассматривать нагревание проца для включения вентилятора вводом/выводом.
S>>Если факториал считать голубинной почтой, то это ввод, вывод, + зависимость от голубей, коршунов, погоды, и прочей хрени. Детерминированным такой результат нельзя назвать даже интуитивно.
VE>Речь не о детерминированности. Если я рядом напишу математическое доказательство "мамой клянус", что голубь не наврёт, то пусть он будет и детерминированным, но всё равно с вводом-выводом же в твоём понимании, ибо голуби летают туда-суда.
Окей, ты просто вместо "мамой клянус" напиши что ты вместо обычной детерминированности подразумеваешь голубинную детерминированность и доказывать ничего не надо будет. А если не написал, то по умолчанию под детерминированностью будет считаться то, о чем пишут в википедии, или каком другом общедоступном источнике. Можно поговорить о том, в каком именно источнике описано определение, которое бы тебя устроило.
VE>Ты ещё скажи, что работа с оперативной памятью ни от чего не зависит, и потому у нас какие-то там гарантии. Зависит от тех же коршунов и погоды, но почему-то не ввод-вывод. VE>Но тогда в чём отличие от засирании оперативки при вычислении списка? Со стороны это видно, изнутри программы — нет.
Память процесса считается внутренним хозяйством программы по википедии, потому обмен данными с ней не считается вводом/выводом. Конечно, если речь не идет об MMF или обменом данными с памятью другого процесса.
Видишь, я практически ничего не выдумываю и призываю оппонентов к тому же. Тем не менее, у каждого второго на rsdn свое понимание и определение чистоты. А один даже считает чистыми функции, которые явно изменяют свои мутабельные аргументы.
Здравствуйте, samius, Вы писали:
S>Здравствуйте, VoidEx, Вы писали:
VE>>Здравствуйте, samius, Вы писали:
VE>>А со стороны сидит Вася и смотрит на эту всю чехарду с памятью, наблюдает, как туда-сюда данные бегают. Это ввод-вывод? S>Зависит от того, в чью память данные бегают.
В обе. Две памяти, одна снаружи, другая внутри. В какую именно полетят данные — решает ОСь, а не программа. Причём внешняя память вся представлена на табло, на которое смотрит Вася.
S>судя по википедии — да. Во всяком случае для программы, выполняющейся на конкретном компьютере. Если рассматривать систему из множества компьютеров как одну, то обмен данными между ними не будет считаться вводом/выводом. Но это уже я додумываю.
Чем дальше в лес, тем больше придётся додумывать, поэтому это определение непрактично и не нужно. С ним только во флеймы скатываться подобные текущему.
S>Ни нагрев процессора, ни включение вентилятора не является вводом/выводом в случае, если программа не управляет включением вентилятора посылкой специальных команд.
А если вентилятор включается, но по каким-то косвенным причинам? Т.е. программа вычисляет себе список, вдруг бац! Выводится hello, включается вентилятор, но программа этого явно не просила, хотя знала, что такое может произойти (как и выделение памяти), даже программист знал (как и в случае с памятью). Это тогда что?
S>Окей, ты просто вместо "мамой клянус" напиши что ты вместо обычной детерминированности подразумеваешь голубинную детерминированность и доказывать ничего не надо будет.
Не, предположим, я математически доказал, что голубь никогда не допустит ошибки (это ж всё же пример), и будет такая математическая детерминированность.
S>Память процесса считается внутренним хозяйством программы по википедии, потому обмен данными с ней не считается вводом/выводом. Конечно, если речь не идет об MMF или обменом данными с памятью другого процесса.
Ну если разобрать компьютер и разложить всё на ковре, то что является внутренностью? Вставленная в мать память — внутренняя, а подсоединённая через USB — уже нет? А в чём разница? Допустим, внутри программы мы не знаем, какой памятью пользуемся, там мы просто вычисляем список.
S>Видишь, я практически ничего не выдумываю и призываю оппонентов к тому же. Тем не менее, у каждого второго на rsdn свое понимание и определение чистоты. А один даже считает чистыми функции, которые явно изменяют свои мутабельные аргументы.
Поэтому я и написал, что определение чистоты бессмысленно. Есть referential transparency, его и надо использовать.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>А со стороны сидит Вася и смотрит на эту всю чехарду с памятью, наблюдает, как туда-сюда данные бегают. Это ввод-вывод? S>>Зависит от того, в чью память данные бегают.
VE>В обе. Две памяти, одна снаружи, другая внутри. В какую именно полетят данные — решает ОСь, а не программа. Причём внешняя память вся представлена на табло, на которое смотрит Вася.
Можно развести такую же философию вокруг темы "являются ли внутренние органы внутренними, если мы можем их наблюдать снаружи".
Я полагаю, что имеет значение то, взаимодействует ли программа с данными вне ее, а не то, видим ли мы внутреннее состояние памяти программы или нет.
S>>судя по википедии — да. Во всяком случае для программы, выполняющейся на конкретном компьютере. Если рассматривать систему из множества компьютеров как одну, то обмен данными между ними не будет считаться вводом/выводом. Но это уже я додумываю.
VE>Чем дальше в лес, тем больше придётся додумывать, поэтому это определение непрактично и не нужно. С ним только во флеймы скатываться подобные текущему.
Такое ощущение, что из всего моего ответа ты увидел лишь слово "додумываю".
S>>Ни нагрев процессора, ни включение вентилятора не является вводом/выводом в случае, если программа не управляет включением вентилятора посылкой специальных команд.
VE>А если вентилятор включается, но по каким-то косвенным причинам? Т.е. программа вычисляет себе список, вдруг бац! Выводится hello, включается вентилятор, но программа этого явно не просила, хотя знала, что такое может произойти (как и выделение памяти), даже программист знал (как и в случае с памятью). Это тогда что?
Я же ответил. Могу еще раз. Косвенное включение вентилятора при нагреве процессора/чего хочешь не является посылкой данных вовне. Естественно, без специальных оговорок по поводу обратного. Хочешь рассматривать включение вентилятора сайд эффектом — пожалуйста. Только не забудь сказать об этом коллегам.
VE>Не, предположим, я математически доказал, что голубь никогда не допустит ошибки (это ж всё же пример), и будет такая математическая детерминированность.
Если ты доказал такое, то осталось еще доказать что обмен данными с голубем не является вводом/выводом. Хотя, можно не доказывать, можно просто задекларировать такое. Заяви что голубь — такая же внутренность программы, как регистр процессора или ячейка памяти.
S>>Память процесса считается внутренним хозяйством программы по википедии, потому обмен данными с ней не считается вводом/выводом. Конечно, если речь не идет об MMF или обменом данными с памятью другого процесса.
VE>Ну если разобрать компьютер и разложить всё на ковре, то что является внутренностью? Вставленная в мать память — внутренняя, а подсоединённая через USB — уже нет? А в чём разница? Допустим, внутри программы мы не знаем, какой памятью пользуемся, там мы просто вычисляем список.
Я же написал про память процесса. Память процесса может быть размазана по туче реальных девайсов, в том числе USB. Любые кэши, влоть до кэша винта, все что отвечает за работу программы — будет иметь отношение к памяти процесса. Но не в полном объеме. Т.е. сейчас программу обслуживают адреса такие, через секунду — другие. Впрочем, для программы это обычно прозрачно, и она не указывает системе, какие адреса ей нужны. Потому, довольно легко понять, что является посылкой данных вовне, а что — нет. У меня, во всяком случае, вопросов нет.
S>>Видишь, я практически ничего не выдумываю и призываю оппонентов к тому же. Тем не менее, у каждого второго на rsdn свое понимание и определение чистоты. А один даже считает чистыми функции, которые явно изменяют свои мутабельные аргументы.
VE>Поэтому я и написал, что определение чистоты бессмысленно. Есть referential transparency, его и надо использовать.
То что оно бессмысленно — ты пиши не мне, а тем, кто им активно пользуется. В том числе авторам кучи книжек. Я не собираюсь спорить с тобой по поводу пользы этого определения. Я сам много лет не подозревал о его существовании, и ничего, пока жив.
Что касается referential transparency. Ну что же, давай обсудим referential transparency действий хаскеля, но не их комбинирования. Только для начала предлагаю ознакомиться с определениями, вики и т.п.
Например, тут определения нет, но понимание этого термина автора жестко завязано на side effect-ы, которые для твоего понимания представляют проблему.
Как я понимаю, основным определением является возможность замены выражения значением с сохранением поведения (yielding a program that has the same effects and output on the same input). Если есть другие предложения — давай рассмотрим их. Что касается этого определения referential transparency — не вижу повода считать, что действия в хаскеле прозрачны. Например, действие, полученное в результате writeFile мы можем заменить кортежем со значением мира после этого действия. Но я протестую против того, что бы считать файл на диске (который должен образоваться) частью этого значения. Т.е. при такой замене эффекты не сохраняются, значит действие, полученное из writeFile, не ссылочно прозрачно. Однако, комбинирование действий обладает такой прозрачностью.
Здравствуйте, VladD2, Вы писали:
VD>А для фана можешь попробовать нашу "игрушку". Она и изучается намного легче (людьми пришедшими из императивного мира), и в сто раз более практична.
VD>Для практического использования тебе может не подойти дотнет. Но для изучения оно точно будет полезен. К тому же мы намереваемся сделать язык мультиплатфорным в будущем.
Вообще из всех декларативных языков у меня настоящую симпатию вызвал только Пролог. Все функциональные языки (считаем подвидом декларативных) как-то особых прелестей не показали, а недостатков кучи. Жаль что он всё же не подходит для наших реальных дел — тут точно только "фан".
Для дела же мне сейчас больше всего нравятся императивные языки с возможностями функциональности, метапрограммирования и т.п. Типа C++11, а лучше D и т.п.
Немерле посмотрю. ) Хотя пока не понял его ориентированность. Т.е. понятно что везде можно извернуться, но допустим на Питоне мы пишем скрипты (внутренние и серверные), но не большие проекты. А на C++ скрипты не пишем, хотя это возможно в теории.. Вот для чего Немерле лучше?
Здравствуйте, alex_public, Вы писали:
_>Для дела же мне сейчас больше всего нравятся императивные языки с возможностями функциональности, метапрограммирования и т.п. Типа C++11, а лучше D и т.п.
Дык в этом случае Nemerle идеален. Развитое ФП, императивность и мощный метапрограмминг. Уж явно лучше C++, D и т.п.
_>Вот для чего Немерле лучше?
Здравствуйте, samius, Вы писали:
S>Все-таки, хотелось бы понять, почему вы считаете что в C# ПП есть, а в C++ нет? Предлагаю взять тривиальную функцию id и посмотреть на нее безотносительно времени компиляции/джита в контексте утвреждения
В языке с ПП id имеет тип forall a. a -> a множество типов, к которым мы можем применить такое выражение (потенциально) бесконечно. В случае C++ это не так. Множество конечно. Для того, чтоб наблюдать на практике отличия между этими случаями нужно иметь возможность организовать тайплевел-вычисления, которые тут обсуждаются.
S>Но в c++ параметрического полиморфизма нет — там не бывает кода для любого типа
S>Что в C++, что в C#, найдутся типы, для которых id работать (и даже компилироваться) не будет.
Ну, из-за особенностей реализации полиморфизма и в хаскеле есть типы, с которыми он не работает. Так что правильнее сказать все-таки "для любого типа определенного кайнда, для которого полиморфизм вообще работает".
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Все-таки, хотелось бы понять, почему вы считаете что в C# ПП есть, а в C++ нет? Предлагаю взять тривиальную функцию id и посмотреть на нее безотносительно времени компиляции/джита в контексте утвреждения
K>В языке с ПП id имеет тип forall a. a -> a множество типов, к которым мы можем применить такое выражение (потенциально) бесконечно. В случае C++ это не так. Множество конечно.
Конечно потому что ... ?
Я не понимаю. Я могу (потенциально) написать id в foo.h и использовать его в бесконечном количестве программ по одному разу. Где конечность? Почему это конечнее, чем в haskell?
K>Для того, чтоб наблюдать на практике отличия между этими случаями нужно иметь возможность организовать тайплевел-вычисления, которые тут обсуждаются.
Практика — это хорошо, но определение ПП не требует тайплевел-вычислений в рантайме.
K>
S>>Но в c++ параметрического полиморфизма нет — там не бывает кода для любого типа
S>>Что в C++, что в C#, найдутся типы, для которых id работать (и даже компилироваться) не будет.
K>Ну, из-за особенностей реализации полиморфизма и в хаскеле есть типы, с которыми он не работает. Так что правильнее сказать все-таки "для любого типа определенного кайнда, для которого полиморфизм вообще работает".
Так же и в C# есть типы, с которыми полиморфизм не работает. Кайндов нет, а типы, с которыми ПП не работает, есть . Тем не менее, C# считается ПП языком.
Здравствуйте, samius, Вы писали:
S>Я могу (потенциально) написать id в foo.h и использовать его в бесконечном количестве программ по одному разу.
Нет. Не в (потенциально) бесконечном кол-ве программ. Так разницу не обнаружить. А в одной программе для (потенциально) бесконечного кол-ва типов. Как в примере выше.
K>>Для того, чтоб наблюдать на практике отличия между этими случаями нужно иметь возможность организовать тайплевел-вычисления, которые тут обсуждаются. S>Практика — это хорошо, но определение ПП не требует тайплевел-вычислений в рантайме.
Ну да, не требует. Да и практика не требует тайплевел-вычислений в рантайме. Какой тайплевел в рантайме? Может вы тоже считаете, что обсуждаемый тут код на haskell/java/c# в рантайме тайпчекается?
S>Так же и в C# есть типы, с которыми полиморфизм не работает. Кайндов нет, а типы, с которыми ПП не работает, есть .
Кайнды в C#, разумеется, есть. Например value-type и ref-type.
S>Тем не менее, C# считается ПП языком.
Потому, что число конкретных типов, которые работают с полиморфным определением (потенциально) бесконечно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
S>Что за чудеса? Мир — внутренний объект программы, файл — внешний.
Ох... файл в программе такой же в точности "внутренний объект", что и мир. Почему этот модельный "мир" такие странные вопросы вызывает? Ну пусть будет не мир, допустим, а TimeStamp и уникальность нужна для сохранения "безпарадоксальной" истории, без всяких временных петель. Так лучше?
S>этак можно сказать что fprintf принимает неявно внешний мир и возвращает длину строки и новый мир с файлом.
Можно, только без возможности сохранить уникальность мира — толку от этого нет и использовать это никак нельзя.
S>Речь о том, что акция может не быть чистой не потому что уникальность мира, а потому что она делает ввод/вывод.
Но если она делает ввод/вывод не нарушая прозрачность по ссылкам — она может быть чистой. И не просто может — она такая и есть.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, VoidEx, Вы писали:
VE>Дык в этом случае Nemerle идеален. Развитое ФП, императивность и мощный метапрограмминг. Уж явно лучше C++, D и т.п.
Остались ещё требования: быстродействие (в разумных пределах), доступ к системным функциям, возможность подключения (или наличие большого числа биндингов как у Питона) к C/C++ библиотекам.
_>>Вот для чего Немерле лучше?
VE>Для всего он лучше, чем C++ и D с учётом .Net
.Net в моих глазах — это недостаток. Для дела естественно, a для "фана" пофиг. Но вроде бы же говорят что планируется Немерле для нейтива.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Я могу (потенциально) написать id в foo.h и использовать его в бесконечном количестве программ по одному разу.
K>Нет. Не в (потенциально) бесконечном кол-ве программ. Так разницу не обнаружить. А в одной программе для (потенциально) бесконечного кол-ва типов. Как в примере выше.
Определение ПП не требует (потенциально) бесконечного кол-ва типов. Оно требует лишь отсутствия кода специализации, (либо кода, который уже специализирован) для любого типа (наверное, с оговорками).
K>>>Для того, чтоб наблюдать на практике отличия между этими случаями нужно иметь возможность организовать тайплевел-вычисления, которые тут обсуждаются. S>>Практика — это хорошо, но определение ПП не требует тайплевел-вычислений в рантайме.
K>Ну да, не требует. Да и практика не требует тайплевел-вычислений в рантайме. Какой тайплевел в рантайме? Может вы тоже считаете, что обсуждаемый тут код на haskell/java/c# в рантайме тайпчекается?
Нет. Тут я о возможности получать экземпляры типов, которые не были инстанциированы при компиляции.
S>>Так же и в C# есть типы, с которыми полиморфизм не работает. Кайндов нет, а типы, с которыми ПП не работает, есть .
K>Кайнды в C#, разумеется, есть. Например value-type и ref-type.
Не понимаю, какое отношение value-type и ref-type имеют к кайндам.
S>>Тем не менее, C# считается ПП языком.
K>Потому, что число конкретных типов, которые работают с полиморфным определением (потенциально) бесконечно.
Чем оно бесконечнее, чем в C++? И где в определении ПП упоминается бесконечность? Еще раз, там об отсутствии специализации для конкретных типов.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Что за чудеса? Мир — внутренний объект программы, файл — внешний.
K>Ох... файл в программе такой же в точности "внутренний объект", что и мир.
файла в программе нет. как объекта, во всяком случае. а "мир"-а нет снаружи.
K> Почему этот модельный "мир" такие странные вопросы вызывает? Ну пусть будет не мир, допустим, а TimeStamp и уникальность нужна для сохранения "безпарадоксальной" истории, без всяких временных петель. Так лучше?
Потому что есть четкое представление о том что этот "модельный мир" это не модель и не мир. TimeStamp — лучше, но тот хотя бы несет информацию о времени, а функция мира — лишь служить эстафетной палочкой при вызове действий.
Но это не важно. Важно в контексте обсуждения то, что чем бы ни был мир внутри программы, файл снаружи программы (или улетевшие байты по сети) частью программы не являются, а значит программа осуществляет взаимодействие с внешним по отношению к программе миром. Раз так, то это input/output и плакала чистота.
Но я не понимаю, зачем нужно строить какие-то иллюзии вокруг модели мира внутри программы, если отсутствие чистоты действий не компрометирует хаскель как чистый язык?
S>>этак можно сказать что fprintf принимает неявно внешний мир и возвращает длину строки и новый мир с файлом.
K>Можно, только без возможности сохранить уникальность мира — толку от этого нет и использовать это никак нельзя.
уникакльность мира сама по себе не является целью. мир нужен только для организации последовательности выполнения действий. В C++ с этим никаких вопросов нет, т.к. последовательность обеспечивается ";".
S>>Речь о том, что акция может не быть чистой не потому что уникальность мира, а потому что она делает ввод/вывод.
K>Но если она делает ввод/вывод не нарушая прозрачность по ссылкам — она может быть чистой. И не просто может — она такая и есть.
Прозрачность по ссылкам — это возможность заменить выражение значением. Я уже отвечал VoidEx-у, что не готов рассматривать файл на диске или байты, ушедшие в сеть, в качестве части значения, которым мы заменили выражение.
Здравствуйте, samius, Вы писали:
S>Прозрачность по ссылкам — это возможность заменить выражение значением. Я уже отвечал VoidEx-у, что не готов рассматривать файл на диске или байты, ушедшие в сеть, в качестве части значения, которым мы заменили выражение.
Вообще говоря, мы можем запустить вычисление функции f от 10 в потоке, и в другом потоке мониторить выделенную память, тем самым определяя, вычисляется ли что-то реально, или подставляется готовое значение. При этом f чистая, а реализация вольна мемоизировать по своему усмотрению.
Если f использует всякие newSTRef, то она всё равно останется чистой, и вряд ли можно сказать, что компилятор не имеет права в (f 10, f 10) вычислить её лишь один раз. При этом мы таки можем при желании организовать возможность определить это, но на чистоту f это никак не влияет.
Поэтому я склонен считать, что даже если f посылает данные в сеть, но остается прозрачной по ссылкам (гарантированно, насколько это возможно), то её можно считать чистой и заменять на вычисленное значение.
Да, мы можем определить, шлёт ли она данные. Да, в ответ может прийти не то (как и в память могут подложить свинью).
Но посылка данных не её основная задача. Она именно что вычисляет факториал, а уж то, что она при этом делает, — неважно.
В общем я понял, что ты с этим несогласен, и не стремлюсь тебя переубедить, я лишь на всякий случай уточнил свою позицию.
Здравствуйте, samius, Вы писали:
S>Определение ПП не требует (потенциально) бесконечного кол-ва типов.
Не требует. (Потенциальная) бесконечность следует из определения параметрического полиморфизма. Или вы можете ограничить сверху число "любых типов"?
S>Нет. Тут я о возможности получать экземпляры типов, которые не были инстанциированы при компиляции.
А где обсуждалась такая возможность? Когда они еще могут "инстанциироваться" (когда говорят о параметрических типах, правильнее все-таки говорить "применяются") если не при компиляции?
S>Не понимаю, какое отношение value-type и ref-type имеют к кайндам.
Прямое. Это типы типов.
S>Чем оно бесконечнее, чем в C++?
Тем, что обсуждаемый код с (потенциально) бесконечным числом типов на C# работает, а на C++ нет.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
K>>Ох... файл в программе такой же в точности "внутренний объект", что и мир. S>файла в программе нет. как объекта, во всяком случае.
Как нет? А во что вы пишете/ что читаете?
S>а "мир"-а нет снаружи.
Вот это да! Как же его, нет, когда есть. По крайней мере мне он в ощущениях задан.
S>Потому что есть четкое представление о том что этот "модельный мир" это не модель и не мир.
Почему не модель? От мира нам требуется только модель времени. Время (точнее, только упорядоченность событий) и моделируется.
S> TimeStamp — лучше, но тот хотя бы несет информацию о времени, а функция мира — лишь служить эстафетной палочкой при вызове действий.
Чем лучше? Названием?
S>Но я не понимаю, зачем нужно строить какие-то иллюзии вокруг модели мира внутри программы, если отсутствие чистоты действий не компрометирует хаскель как чистый язык?
Затем, чтобы можно было отличать процедуры, нарушающие ссылочную прозрачность от функций, не нарушающих. Это практически важное свойство. Если руководствоваться вашим определением чистоты — получится, что такую классификацию мы осуществить не сможем. Ведь у вас получается так, что широкий класс функций, которые чистыми не являются — ссылочную прозрачность тем не менее не нарушают. Это неудобно.
S>>>этак можно сказать что fprintf принимает неявно внешний мир и возвращает длину строки и новый мир с файлом.
S>уникакльность мира сама по себе не является целью. мир нужен только для организации последовательности выполнения действий. В C++ с этим никаких вопросов нет, т.к. последовательность обеспечивается ";".
Не обеспечивается. Она неявная, никакими ; в коде не определяется.
S>Прозрачность по ссылкам — это возможность заменить выражение значением. Я уже отвечал VoidEx-у, что не готов рассматривать файл на диске или байты, ушедшие в сеть, в качестве части значения, которым мы заменили выражение.
Ну, мы вроде бы обсуждаем ссылочную прозрачность, а не чью-то неготовность что-то рассматривать. Попробуйте ее нарушить записью в файл или хождением байтов по сети без всяких unsafe* — тогда и будет материал для разговора.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>файла в программе нет. как объекта, во всяком случае.
K>Как нет? А во что вы пишете/ что читаете?
В вызовы API, полагаю
S>>а "мир"-а нет снаружи.
K>Вот это да! Как же его, нет, когда есть. По крайней мере мне он в ощущениях задан.
Тот что задан в ощущениях, никакой связи с внутрипрограммным World не имеет.
S>>Потому что есть четкое представление о том что этот "модельный мир" это не модель и не мир.
K>Почему не модель? От мира нам требуется только модель времени. Время (точнее, только упорядоченность событий) и моделируется.
не моделируется, а достигается посредством передачи значения.
S>> TimeStamp — лучше, но тот хотя бы несет информацию о времени, а функция мира — лишь служить эстафетной палочкой при вызове действий.
K>Чем лучше? Названием?
да
S>>Но я не понимаю, зачем нужно строить какие-то иллюзии вокруг модели мира внутри программы, если отсутствие чистоты действий не компрометирует хаскель как чистый язык?
K>Затем, чтобы можно было отличать процедуры, нарушающие ссылочную прозрачность от функций, не нарушающих. Это практически важное свойство.
Я отличаю, никаких проблем не вижу, кроме регулярных споров на эту тему. K>Если руководствоваться вашим определением чистоты — получится, что такую классификацию мы осуществить не сможем.
Это не мое определение. K>Ведь у вас получается так, что широкий класс функций, которые чистыми не являются — ссылочную прозрачность тем не менее не нарушают. Это неудобно.
Не понимаю о чем речь. Да, ссылочная прозрачность слабее чистоты. Что в этом неудобного?
S>>>>этак можно сказать что fprintf принимает неявно внешний мир и возвращает длину строки и новый мир с файлом.
S>>уникакльность мира сама по себе не является целью. мир нужен только для организации последовательности выполнения действий. В C++ с этим никаких вопросов нет, т.к. последовательность обеспечивается ";".
K>Не обеспечивается. Она неявная, никакими ; в коде не определяется.
Верно, неявная.
S>>Прозрачность по ссылкам — это возможность заменить выражение значением. Я уже отвечал VoidEx-у, что не готов рассматривать файл на диске или байты, ушедшие в сеть, в качестве части значения, которым мы заменили выражение.
K>Ну, мы вроде бы обсуждаем ссылочную прозрачность, а не чью-то неготовность что-то рассматривать. Попробуйте ее нарушить записью в файл или хождением байтов по сети без всяких unsafe* — тогда и будет материал для разговора.
Начинаю сердиться. Я только и твержу о том, что комбинирование нечистых действий чисто, следовательно ссылочно прозрачно. А выполнение действий — нечисто и непрозрачно. Но выполнение можно сделать лишь через unsafe*.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Прозрачность по ссылкам — это возможность заменить выражение значением. Я уже отвечал VoidEx-у, что не готов рассматривать файл на диске или байты, ушедшие в сеть, в качестве части значения, которым мы заменили выражение.
VE>Вообще говоря, мы можем запустить вычисление функции f от 10 в потоке, и в другом потоке мониторить выделенную память, тем самым определяя, вычисляется ли что-то реально, или подставляется готовое значение. При этом f чистая, а реализация вольна мемоизировать по своему усмотрению. VE>Если f использует всякие newSTRef, то она всё равно останется чистой, и вряд ли можно сказать, что компилятор не имеет права в (f 10, f 10) вычислить её лишь один раз. При этом мы таки можем при желании организовать возможность определить это, но на чистоту f это никак не влияет.
не понимаю, к чему это?
VE>Поэтому я склонен считать, что даже если f посылает данные в сеть, но остается прозрачной по ссылкам (гарантированно, насколько это возможно), то её можно считать чистой и заменять на вычисленное значение.
Если посылает данные в сеть, то ее нельзя заменить вычисленным значением. Данные ведь в сеть не пошлются!!! При замене вызова значением мы должны обеспечить тот же эффект по определению прозрачности.
VE>Да, мы можем определить, шлёт ли она данные. Да, в ответ может прийти не то (как и в память могут подложить свинью). VE>Но посылка данных не её основная задача. Она именно что вычисляет факториал, а уж то, что она при этом делает, — неважно.
Важно, смотри определение.
VE>В общем я понял, что ты с этим несогласен, и не стремлюсь тебя переубедить, я лишь на всякий случай уточнил свою позицию.
Определения с тобой не согласны. Я лишь пытаюсь это обозначить.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Определение ПП не требует (потенциально) бесконечного кол-ва типов.
K>Не требует. (Потенциальная) бесконечность следует из определения параметрического полиморфизма. Или вы можете ограничить сверху число "любых типов"?
Не следует. Из определения следует лишь то, что код не должен быть специализирован для конкретных типов.
S>>Нет. Тут я о возможности получать экземпляры типов, которые не были инстанциированы при компиляции.
K>А где обсуждалась такая возможность? Когда они еще могут "инстанциироваться" (когда говорят о параметрических типах, правильнее все-таки говорить "применяются") если не при компиляции?
Ну вот в C# мы можем применять типы в рантайме. Хотя это означает лишь "докомпиляцию" джитом.
S>>Не понимаю, какое отношение value-type и ref-type имеют к кайндам.
K>Прямое. Это типы типов.
Я бы это назвал видами/категориями/классами типов. Но не типами типов.
S>>Чем оно бесконечнее, чем в C++?
K>Тем, что обсуждаемый код с (потенциально) бесконечным числом типов на C# работает, а на C++ нет.
на C++ потенциальную бесконечность типов мы можем обеспечить во время компиляции, поместив что надо в заголовок и залив на гитхаб.
Здравствуйте, Klapaucius, Вы писали:
K>В языке с ПП id имеет тип forall a. a -> a множество типов, к которым мы можем применить такое выражение (потенциально) бесконечно. В случае C++ это не так. Множество конечно. Для того, чтоб наблюдать на практике отличия между этими случаями нужно иметь возможность организовать тайплевел-вычисления, которые тут обсуждаются.
В случае C++ по моему ты все-таки завязываешся на реализацию. Вроде стандарт не запрещает реализовать шаблоны так чтобы они
компилировались для любых типов в один и тот же код. Возможно в каком нибудь интерпретаторе C++ (например http://root.cern.ch/drupal/content/cint)
это уже и реализовано.
K>module Main where
K>import System.Environment
K>data Nil = Nil
K>data Cons a = Cons Integer a
K>class ScalarProduct a where scalarProduct :: a -> a -> Integer
K>instance ScalarProduct Nil where
K> scalarProduct Nil Nil = 0
K>instance ScalarProduct a => ScalarProduct (Cons a) where
K> scalarProduct (Cons n1 a1) (Cons n2 a2) = n1 * n2 + scalarProduct a1 a2
K>test :: Integer -> Integer
K>test n = test' n 0 Nil Nil where
K> test' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
K> test' 0 _ as bs = scalarProduct as bs
K> test' n i as bs = test' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
K>-- test' n i as bs = test' (n-1) (i+1) as (Cons (i^2) bs)
K>main = print . test . read . head =<< getArgs
Если чуть измениь код функции test на эквивалентный, то компилятор ругаицця:
test :: Integer -> Integer
test n = scalarProduct as bs
where
make_lst _ 0 _ xs = xs
make_lst f n i xs = make_lst f (n-1) (i+1) (Cons (f i) xs)
as = make_lst (\x -> 2 * x + 1) n 0 Nil
bs = make_lst (^2) n 0 Nil
Грит:
Occurs check: cannot construct the infinite type: a2 = Cons a2
In the second argument of `Cons', namely `xs'
In the fourth argument of `make_lst', namely `(Cons (f i) xs)'
In the expression: make_lst f (n - 1) (i + 1) (Cons (f i) xs)
Я что-то не так изменил, или haskell-е нет ПП?
Вроде как и в твоём коде всё известно на этапе компиляции.
Компилятор: ghc 7.0.3 из Kubuntu 11.10
Изначаально я хотел посмотреть что будет при такой сигнатуре:
test :: Integer -> Integer -> Integer
test n m = scalarProduct as bs
where
make_lst _ 0 _ xs = xs
make_lst f n i xs = make_lst f (n-1) (i+1) (Cons (f i) xs)
as = make_lst (\x -> 2 * x + 1) n 0 Nil
bs = make_lst (^2) m 0 Nil
функция скомпилится или нет? Ведь доказать эквивалентность типов можно или нельзя в зависимости от того, как мы её вызываем.
Если скомпилится в любом случае, то очивидно, проверка должна быть вынесена в rantime.
Можно похожим образом изменить код для C# и Java, и посмотреть используется ли там поддержка в rantime или всё полная статика.
И если таки rantime, то есть ли там вообще проверки эквивалентности и какие.
Здравствуйте, samius, Вы писали:
VE>>Вообще говоря, мы можем запустить вычисление функции f от 10 в потоке, и в другом потоке мониторить выделенную память, тем самым определяя, вычисляется ли что-то реально, или подставляется готовое значение. При этом f чистая, а реализация вольна мемоизировать по своему усмотрению. VE>>Если f использует всякие newSTRef, то она всё равно останется чистой, и вряд ли можно сказать, что компилятор не имеет права в (f 10, f 10) вычислить её лишь один раз. При этом мы таки можем при желании организовать возможность определить это, но на чистоту f это никак не влияет. S>не понимаю, к чему это?
К тому, что даже не вызывающее сомнений чистое вычисление не более и не менее чисто, чем вычисление через посыл данных по сети.
VE>>Поэтому я склонен считать, что даже если f посылает данные в сеть, но остается прозрачной по ссылкам (гарантированно, насколько это возможно), то её можно считать чистой и заменять на вычисленное значение. S>Если посылает данные в сеть, то ее нельзя заменить вычисленным значением. Данные ведь в сеть не пошлются!!!
И оперативная память не выделится, если заменить чистую f от 10 на запомненное где-то значение. Ну и что?
S>При замене вызова значением мы должны обеспечить тот же эффект по определению прозрачности.
Какой такой эффект у факториала?
Ты понимаешь, что твоя отсылка "в википедии написано, что такое IO" означает, что в википедии должно быть не "например, вывод на экран", а чёткий такой список, что именно IO, а что нет. И более того, способ определить для гипотетического случая, IO это будет или нет. А там такого нет, там всё на интуицию завязано.
f вычисляет факториал, если есть гарантия, что для 6! вернёт 120, вычисляй ты хоть 300 раз подряд, то она referential transparent, а как именно она реализована — детали реализации. Что мешает использовать файл как оперативную память?
VE>>В общем я понял, что ты с этим несогласен, и не стремлюсь тебя переубедить, я лишь на всякий случай уточнил свою позицию. S>Определения с тобой не согласны. Я лишь пытаюсь это обозначить.
Нет, ты со мной не согласен. Я задал кучу вопросов, на которые такое определение ответить не в состоянии.
Эти определения натыкаются на термин outside world, который не определён.
Работа с памятью определяется рантаймом, и поэтому мы считаем, что это не эффект. Ну ок. Делаем рантайм, который общается с кластером, загружает туда код, получает оттуда результат. Это что? IO? Side effect? С т.з. языка нет, это детали реализации. С т.з. зрения википедии ответа просто банально нет.
An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program (in other words, yielding a program that has the same effects and output on the same input).
Более того, у них там рекурсия какая-то просто.
If all functions involved in the expression are pure functions, then the expression is referentially transparent. Also, some impure functions can be included in the expression if their values are discarded and their side effects are insignificant.
Тут поле для демагогии открывается. В контексте вычисления факториала посылка данных в сеть столь же бессодержательна (insignificant), как и выделение памяти.
Здравствуйте, Tonal-, Вы писали:
T>Если чуть измениь код функции test на эквивалентный, то компилятор ругаицця:
Вы хотите прозвести на меня впечатление способностью нерабочий код написать? Могли бы просто закомментировать сигнатуру типа у test' и получить ошибку "cannot construct the infinite type", а не вкладывать столько труда. И, главное, какое это имеет отношение к полиморфизму и как отменяет работоспособность приведенного выше кода?
Мы вроде бы обсудили уже, что для обнаружения (потенциальной) бесконечности типов тут используются тайплевел-вычисления, а это штука довольно хрупкая и костыльная в хаскеле.
T>Если скомпилится в любом случае, то очивидно, проверка должна быть вынесена в rantime. T>Можно похожим образом изменить код для C# и Java, и посмотреть используется ли там поддержка в rantime или всё полная статика. T>И если таки rantime, то есть ли там вообще проверки эквивалентности и какие.
Удивительно просто, как можно так отчаянно цеплятся за рантайм. Как вы вообще представляете себе механизм, откладывающий эту проверку типов в GHC в рантайм? Это святой дух? Может магия вуду? Мне правда интересно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
K>>Не требует. (Потенциальная) бесконечность следует из определения параметрического полиморфизма. Или вы можете ограничить сверху число "любых типов"? S>Не следует. Из определения следует лишь то, что код не должен быть специализирован для конкретных типов.
Код никому не должен "не быть специализирован", он может быть специализирован для оптимизации, и на практике так и происходит. Важно, чтоб он работал для любого типа (того кайнда, для которого полиморфизм вообще работает). А из "любого типа" и следует (потенциальная) бесконечность. Ну, или — если не согласны — двайте верхнюю оценку на число "любых типов".
Мало того, это не просто теоретизирование — показаны примеры, которые используют именно это свойство полиморфизма и которые предсказуемо работают в языках с ПП и не работают в языке без ПП. И вы как-то умудряетесь это игнорировать утверждая, что ничего ниоткуда не следует.
S>Ну вот в C# мы можем применять типы в рантайме. Хотя это означает лишь "докомпиляцию" джитом.
Все типы в обсуждаемых примерах применяются в компайл-тайм.
S>Я бы это назвал видами/категориями/классами типов.
kind иногда переводят как "вид". Классами типов вообще совершенно другие вещи называют.
S>Но не типами типов.
Но почему?
S>на C++ потенциальную бесконечность типов мы можем обеспечить во время компиляции, поместив что надо в заголовок и залив на гитхаб.
Эта шутка тут уже была: >Я могу (потенциально) написать id в foo.h и использовать его в бесконечном количестве программ по одному разу.
Или шутка, повторенная дважды, становится в два раза смешнее?
Ответ мне копипастить или сами прочитаете?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
K>>Как нет? А во что вы пишете/ что читаете? S>В вызовы API, полагаю
И в каком АПИ есть функции прочтиТоНеЗнаюЧто и сохраниТудаНеЗнаюКуда?
S>Тот что задан в ощущениях, никакой связи с внутрипрограммным World не имеет.
Это ваша позиция по любой модели или конкретно по этой? Если по этой — что именно вас не устраивает.
S>не моделируется
Почему?
S>Не понимаю о чем речь. Да, ссылочная прозрачность слабее чистоты. Что в этом неудобного?
Неудобно отсутствие простой и понятной связи между ссылочной прозрачностью и чистотой.
S>Это не мое определение.
А чье? Замечание про "семантическую наблюдаемость" из общепринятого вы с негодованием отвергаете.
S>Начинаю сердиться. Я только и твержу о том, что комбинирование нечистых действий чисто, следовательно ссылочно прозрачно.
А с этим разве спорит кто-то?
S>А выполнение действий — нечисто и непрозрачно. Но выполнение можно сделать лишь через unsafe*.
Утверждение опровергается одним примером:
main = print 42
Выполнение есть — unsafe* нет. Как же так?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, FR, Вы писали:
FR>В случае C++ по моему ты все-таки завязываешся на реализацию. Вроде стандарт не запрещает реализовать шаблоны так чтобы они FR>компилировались для любых типов в один и тот же код.
Ну обычная семантика шаблонов-то должна в такой реализации сохраняться. А от параметрического полиморфизма она отличается. И реализовать шаблоны через ПП нельзя, хотя в отдельных случаях использовать один и тот же код для разных типов, конечно, можно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
T>>Если чуть измениь код функции test на эквивалентный, то компилятор ругаицця: K>Вы хотите прозвести на меня впечатление способностью нерабочий код написать?
Нет. Я хочу понять, что именно ты понимаешь под Полиметрическим Полиморфизмом.
Чем мой вариант настолько отличается от твоего, что твой является ПП и компилится, а мой нет и нет.
Это моё непонимание или глюк в компиляторе?
Как можно преобразовать мой код (1-ый и 2-ой) чтобы они компилились?
K>Мы вроде бы обсудили уже, что для обнаружения (потенциальной) бесконечности типов тут используются тайплевел-вычисления, а это штука довольно хрупкая и костыльная в хаскеле.
Если ПП настолько сложная весчь, что даже к 7-ой версии haskell её неосилил, то кого-же использовать как эталон?
Неужели C#?
T>>Если скомпилится в любом случае, то очивидно, проверка должна быть вынесена в rantime. T>>Можно похожим образом изменить код для C# и Java, и посмотреть используется ли там поддержка в rantime или всё полная статика. T>>И если таки rantime, то есть ли там вообще проверки эквивалентности и какие. K>Удивительно просто, как можно так отчаянно цеплятся за рантайм. Как вы вообще представляете себе механизм, откладывающий эту проверку типов в GHC в рантайм? Это святой дух? Может магия вуду? Мне правда интересно.
Мне показалось, что я придумал отличный тест, который показывает влияние рантайма на реализацию ПП.
С ghc вроде бы всё понятно — он не может скомпилить даже 1-ый мой пример, стало быть полная поддержка ПП даже времени компиляции у него под вопросом. А для рантайма — и вовсе нет или нужен другой тест.
Ну, или ты внятно объяснишь, почему мои пример 1 и 2 не являются ПП, а стало быть и уточнишь его определение.
Так же было бы очень интересно узнать твоё мнение, почему эти примеры нельзя (или таки можно, тогда как) конвертировать в тру-языки поддерживающие ПП, такие как C# и Java.
П. С. Чтобы не было недопонимания, под примером 1 и 2, я понимаю соответственно
Пример 1:
test :: Integer -> Integer
test n = scalarProduct as bs
where
make_lst _ 0 _ xs = xs
make_lst f n i xs = make_lst f (n-1) (i+1) (Cons (f i) xs)
as = make_lst (\x -> 2 * x + 1) n 0 Nil
bs = make_lst (^2) n 0 Nil
Пример 2:
test :: Integer -> Integer -> Integer
test n m = scalarProduct as bs
where
make_lst _ 0 _ xs = xs
make_lst f n i xs = make_lst f (n-1) (i+1) (Cons (f i) xs)
as = make_lst (\x -> 2 * x + 1) n 0 Nil
bs = make_lst (^2) m 0 Nil
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>Вообще говоря, мы можем запустить вычисление функции f от 10 в потоке, и в другом потоке мониторить выделенную память, тем самым определяя, вычисляется ли что-то реально, или подставляется готовое значение. При этом f чистая, а реализация вольна мемоизировать по своему усмотрению. S>>не понимаю, к чему это?
VE>К тому, что даже не вызывающее сомнений чистое вычисление не более и не менее чисто, чем вычисление через посыл данных по сети.
Это ты на невыполнение какого критерия чистоты намекаешь? На семантически обозримый сайд эффект что ли?
S>>Если посылает данные в сеть, то ее нельзя заменить вычисленным значением. Данные ведь в сеть не пошлются!!!
VE>И оперативная память не выделится, если заменить чистую f от 10 на запомненное где-то значение. Ну и что?
Выделение памяти у нас превартилось в семантически обозримый side effect? С каких пор?
S>>При замене вызова значением мы должны обеспечить тот же эффект по определению прозрачности.
VE>Какой такой эффект у факториала? VE>Ты понимаешь, что твоя отсылка "в википедии написано, что такое IO" означает, что в википедии должно быть не "например, вывод на экран", а чёткий такой список, что именно IO, а что нет. И более того, способ определить для гипотетического случая, IO это будет или нет. А там такого нет, там всё на интуицию завязано.
по поводу четкого списка, я полагаю что ты понимаешь, что его составить невозможно с точностью до идей о нагревании атмосферы. А по поводу интуиции — все-таки ориентировано на неглупых людей, которые способны провести границы между системой и остальным миром.
VE>f вычисляет факториал, если есть гарантия, что для 6! вернёт 120, вычисляй ты хоть 300 раз подряд, то она referential transparent, а как именно она реализована — детали реализации.
Ты путаешь referential transparency с детерминированностью. Если есть гарантия что результат будет тот же и не будет зависеть от ввода через I/O или скрытых состояний, то это есть детерминированность, а не ссылочная прозрачность. Детерминированной функции портить мир можно. Ссылочно прозрачному выражению — нельзя.
VE>Что мешает использовать файл как оперативную память?
Ничего. Только детерминированности не будет, т.к. общаешься с файлом через I/O. А нет детерминированности — нет и прозрачности.
S>>Определения с тобой не согласны. Я лишь пытаюсь это обозначить.
VE>Нет, ты со мной не согласен. Я задал кучу вопросов, на которые такое определение ответить не в состоянии.
Мне казалось, что я на все ответил, опираясь на определение.
VE>Эти определения натыкаются на термин outside world, который не определён. VE>Работа с памятью определяется рантаймом, и поэтому мы считаем, что это не эффект. Ну ок. Делаем рантайм, который общается с кластером, загружает туда код, получает оттуда результат. Это что? IO? Side effect? С т.з. языка нет, это детали реализации. С т.з. зрения википедии ответа просто банально нет.
Ответа не может быть для тех, кто не может провести границу системы. Смотри:
а) твоя программа запрашивает значение факториала у кластера путем отправки голубя. Это ввод/вывод.
б) твоя программа выполняется рантаймом на кластере путем посылки дилижансов, почтовых голубей и т.п. к кластеру и обратно прозрачно для программы. Т.е. сама программа никого не посылает. Тогда ввода/вывода формально нет, и если голуби работают семантически необозримо, то все формально чисто. Даже если у кочегара дилижанса не выдержала селезенка и его семье придется выплачивать пособие.
VE>
VE>An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program (in other words, yielding a program that has the same effects and output on the same input).
Спасибо, но я заглядываю в него практически каждый раз, когда отвечаю.
VE>Более того, у них там рекурсия какая-то просто.
VE>
VE>If all functions involved in the expression are pure functions, then the expression is referentially transparent. Also, some impure functions can be included in the expression if their values are discarded and their side effects are insignificant.
VE>Тут поле для демагогии открывается. В контексте вычисления факториала посылка данных в сеть столь же бессодержательна (insignificant), как и выделение памяти.
Посылка данных в сеть — это output через I/O девайс. А выделение памяти не является семантически обозримым сайд эффектом.
Что по поводу insignificant сайд эффекта — так это представь вычисление факториала со внешним мутабельным аккумулятором. Такая функция будет формально impure, но включенная в выражение таким образом, что бы изменения не распространялись за пределы выражения, ее побочный эффект будет незначителен для того, кто ожидает результат выражения.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>>>Не требует. (Потенциальная) бесконечность следует из определения параметрического полиморфизма. Или вы можете ограничить сверху число "любых типов"? S>>Не следует. Из определения следует лишь то, что код не должен быть специализирован для конкретных типов.
K>Код никому не должен "не быть специализирован", он может быть специализирован для оптимизации, и на практике так и происходит.
Если на практике происходит специализация, то это не ПП, а ad-hoc. K>Важно, чтоб он работал для любого типа (того кайнда, для которого полиморфизм вообще работает). А из "любого типа" и следует (потенциальная) бесконечность. Ну, или — если не согласны — двайте верхнюю оценку на число "любых типов".
(Потенциальная) — это значит что мне не потребуется специализировать для работы с еще одним любым типом. А если мне в программе нужно всего 10 типов и не нужно бесконечность, то это не мои проблемы.
K>Мало того, это не просто теоретизирование — показаны примеры, которые используют именно это свойство полиморфизма и которые предсказуемо работают в языках с ПП и не работают в языке без ПП. И вы как-то умудряетесь это игнорировать утверждая, что ничего ниоткуда не следует.
А вы умудряетесь игнорировать то что определение ПП не требует бесконечности.
S>>Ну вот в C# мы можем применять типы в рантайме. Хотя это означает лишь "докомпиляцию" джитом.
K>Все типы в обсуждаемых примерах применяются в компайл-тайм.
Но не в C#.
S>>Я бы это назвал видами/категориями/классами типов.
K>kind иногда переводят как "вид". Классами типов вообще совершенно другие вещи называют.
Я не про type class-ы.
S>>Но не типами типов.
K>Но почему?
Потому что кайндов в дотнете нет. Не хочу еще и по этому поводу спорить.
S>>на C++ потенциальную бесконечность типов мы можем обеспечить во время компиляции, поместив что надо в заголовок и залив на гитхаб.
K>Эта шутка тут уже была: >>Я могу (потенциально) написать id в foo.h и использовать его в бесконечном количестве программ по одному разу. K>Или шутка, повторенная дважды, становится в два раза смешнее? K>Ответ мне копипастить или сами прочитаете?
Нет. Не в (потенциально) бесконечном кол-ве программ. Так разницу не обнаружить. А в одной программе для (потенциально) бесконечного кол-ва типов. Как в примере выше.
Про бесконечное кол-во типов мы уже обсудили что оно не требуется определением. А по поводу бесконечного колв-а программ я не согласен тоже. Полиморфизм — это фича языка, а не конкретной программы. Потому будет бесконечность типов в одной программе, или в разных — значения не имеет. Тем более, что она не требуется и не следует.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>>>Как нет? А во что вы пишете/ что читаете? S>>В вызовы API, полагаю
K>И в каком АПИ есть функции прочтиТоНеЗнаюЧто и сохраниТудаНеЗнаюКуда?
В любом. Имя файла или его хэндл — это в точности НеЗнаюЧто/Куда. Конкретика лежит по другую сторону API.
S>>Тот что задан в ощущениях, никакой связи с внутрипрограммным World не имеет.
K>Это ваша позиция по любой модели или конкретно по этой? Если по этой — что именно вас не устраивает.
Конкретно по этой. Не устраивает то что общего с миром у нее не больше, чем у слова "затычка".
S>>не моделируется
K>Почему?
По факту
S>>Не понимаю о чем речь. Да, ссылочная прозрачность слабее чистоты. Что в этом неудобного?
K>Неудобно отсутствие простой и понятной связи между ссылочной прозрачностью и чистотой.
Связь присутствует, она проста, понятна, и описана в статье на википедии.
S>>Это не мое определение.
K>А чье? Замечание про "семантическую наблюдаемость" из общепринятого вы с негодованием отвергаете.
Я негодую когда вы пытаетесь говорить о семантической прозрачности ввода/вывода.
S>>Начинаю сердиться. Я только и твержу о том, что комбинирование нечистых действий чисто, следовательно ссылочно прозрачно.
K>А с этим разве спорит кто-то?
Вы с этим не спорите, но пытаетесь этим аргументировать, будто я против чистоты комбинирования.
S>>А выполнение действий — нечисто и непрозрачно. Но выполнение можно сделать лишь через unsafe*.
K>Утверждение опровергается одним примером:
Оно им подтверждается
K>
K>main = print 42
K>
K>Выполнение есть — unsafe* нет. Как же так?
Я не вижу в этом коде выполнения. Если что, main не выполняет построенное им действие. Точнее так: void main() — выполняет, но не строит. main::IO() — строит, но не выполняет. Удивлен и смущен, что мне приходится об этом упоминать.
Здравствуйте, alex_public, Вы писали:
_>Кстати, что касается ленивости в списках, то в том же Питоне есть некая забавная попытка ленивости — две отдельных функции range и xrange.
Отстаёте от жизни. В 3-м осталась только range, которая во 2-м была xrange, то есть со внутренним итератором. Для эффекта, аналогичного range() во 2-м, надо писать list(range()). Вообще в поздних 2-х и 3-х многое сдвинуто в сторону итераторов.
Не всегда это, однако, хорошо — оптимизация по производительности часто показывает противоположные решения: не сильно длинные списки эффективнее итераторов с yield'ом.
Здравствуйте, VoidEx, Вы писали: VE>Они не компилятся не потому, что ПП или не ПП, а потому что нет compile-time доказательства того, что длины таки равные. VE>На Agda перепиши.
Можно ли поподробнее?
Почему, в версии
K>test :: Integer -> Integer
test n = test' n 0 Nil Nil where
test' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
test' 0 _ as bs = scalarProduct as bs
test' n i as bs = test' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
можно доказать что длины as и bs равны, а в версии
test :: Integer -> Integer
test n = scalarProduct as bs
where
make_lst _ 0 _ xs = xs
make_lst f n i xs = make_lst f (n-1) (i+1) (Cons (f i) xs)
as = make_lst (\x -> 2 * x + 1) n 0 Nil
bs = make_lst (^2) n 0 Nil
этого доказать нельзя?
Я межу ними различия не вижу, но для компилятора оно есть.
Можешь объяснить его?
В чём оно? Это принципиальное/теоретическое различие, или это особенности компилятора?
Здравствуйте, samius, Вы писали:
S>Выделение памяти у нас превартилось в семантически обозримый side effect? С каких пор?
Ты спрашиваешь, как программе определить, выделяется ли память?
S>>>При замене вызова значением мы должны обеспечить тот же эффект по определению прозрачности.
S>А по поводу интуиции — все-таки ориентировано на неглупых людей, которые способны провести границы между системой и остальным миром.
Математики не потому составляют чёткие определения, что они глупые, а как раз наоборот, потому что понимают, что дьявол в деталях.
Может, в определение напишем "side-effect — то, что неглупый человек считает side-effect'ом"?
S>Ты путаешь referential transparency с детерминированностью. Если есть гарантия что результат будет тот же и не будет зависеть от ввода через I/O или скрытых состояний, то это есть детерминированность, а не ссылочная прозрачность. Детерминированной функции портить мир можно. Ссылочно прозрачному выражению — нельзя.
Не путаю. Она и не портит, мир живёт себе как жил. Ничем не хуже, чем с выделенной памятью.
VE>>Что мешает использовать файл как оперативную память? S>Ничего. Только детерминированности не будет, т.к. общаешься с файлом через I/O. А нет детерминированности — нет и прозрачности.
Почему это не будет? На n! при сотне вызовов возвращает верное значение? Да. Так что ж тогда?
S>>>Определения с тобой не согласны. Я лишь пытаюсь это обозначить.
VE>>Нет, ты со мной не согласен. Я задал кучу вопросов, на которые такое определение ответить не в состоянии. S>Мне казалось, что я на все ответил, опираясь на определение.
Это тебе казалось, а я ответов не получил. Видимо потому, что ты тоже опираешься на "интуицию умных людей", а у меня такое понятие отсутствует, мне нужно чёткое определение, чтоб определение было полезным даже для дурака вроде меня. Эдак можно начать опираться на клятвы матерью и своим здоровьем.
VE>>Эти определения натыкаются на термин outside world, который не определён. S> VE>>Работа с памятью определяется рантаймом, и поэтому мы считаем, что это не эффект. Ну ок. Делаем рантайм, который общается с кластером, загружает туда код, получает оттуда результат. Это что? IO? Side effect? С т.з. языка нет, это детали реализации. С т.з. зрения википедии ответа просто банально нет. S>Ответа не может быть для тех, кто не может провести границу системы. Смотри: S>а) твоя программа запрашивает значение факториала у кластера путем отправки голубя. Это ввод/вывод. S>б) твоя программа выполняется рантаймом на кластере путем посылки дилижансов, почтовых голубей и т.п. к кластеру и обратно прозрачно для программы. Т.е. сама программа никого не посылает. Тогда ввода/вывода формально нет, и если голуби работают семантически необозримо, то все формально чисто. Даже если у кочегара дилижанса не выдержала селезенка и его семье придется выплачивать пособие.
А что если работает рантайм, но программа имеет возможность косвенно следить за её работой? Ну, как с памятью.
Или обратный эффект. Что, если я могу рантайм расширять? Ну вот например ввёл доставки голубем, и с т.з. языка это считается runtime extension и даёт те же гарантии, что и работа с памятью. А дальше компилятор на основе метаданных выбирает реализацию сам — то ли память, то ли голуби.
Я не думаю, что это такой уж надуманный пример, вон взять Nemerle 2, который будет как бы инструмент для создания языков. Там может понадобиться нечто подобное. В базовом языке (который ещё и грязный) у нас будет всё превращаться в голубиную почту, но в исходном чистом ДСЛ это будет обычное вычисление.
S>Посылка данных в сеть — это output через I/O девайс. А выделение памяти не является семантически обозримым сайд эффектом.
В чём разница? Увидеть можно и то, и другое. Изнутри программы.
S>Что по поводу insignificant сайд эффекта — так это представь вычисление факториала со внешним мутабельным аккумулятором. Такая функция будет формально impure, но включенная в выражение таким образом, что бы изменения не распространялись за пределы выражения, ее побочный эффект будет незначителен для того, кто ожидает результат выражения.
Можно ещё представить себе вычисление огроменного списка. Формально он pure, но запусти их 3 сразу и памяти не хватит. И это оказывается очень так значительно для того, кто ожидает результат вычисления. Но это лирика.
А можно опять вернуться к голубиной почте, а лучше к temporary файл. Можно создать temporary file (имя которому генерирует ОСь, и имя которого неизвестно никому, только handle открывшему процессу), поиспользовать для вычислений и закрыть (после чего он может удалиться, а может и нет, зависит от флагов открытия). Такая функция чиста? Вроде ввод-вывод есть, но побочный эффект необнаруживаем.
Здравствуйте, Tonal-, Вы писали:
T>Здравствуйте, VoidEx, Вы писали: VE>>Они не компилятся не потому, что ПП или не ПП, а потому что нет compile-time доказательства того, что длины таки равные. VE>>На Agda перепиши. T>Можно ли поподробнее?
Можно.
T>Почему, в версии T>
K>>test :: Integer -> Integer
T>test n = test' n 0 Nil Nil where
T> test' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
T> test' 0 _ as bs = scalarProduct as bs
T> test' n i as bs = test' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
T>
T>можно доказать что длины as и bs равны,
По определению. Тип функции test' явно говорит, что 3 и 4 аргументы совпадают по типу, а таковые одинаковы только у списков с равной длиной (не встроенных списков, а нами определённых).
Поэтому приходится использовать рекурсию. Nil имеет тип List b Zero или как-то так (я исходник изначальный не нашёл), где b — тип элементов, Zero — длина. Когда мы во второй строке для test' к каждому списку делаем Cons, то для двух List b n (т.е. одинаковой длины списки) для любого n мы получаем два List b (Succ n), которые опять же одной длины (очевидно), и это компилируется.
То, что они одной длины, как-то отдельно доказывать не надо банально потому, что это доказательство тривиально выводится из определения списка (при таком построении).
Однако возможности Haskell в этой области крайне скудные, поэтому для сложных случаев компилятор уже понять ничего не может, но это не относится к ПП, это относится к невозможности дать подсказки компилятору и как-то выразить известный нам факт.
а в версии T>
T>test :: Integer -> Integer
T>test n = scalarProduct as bs
T> where
T> make_lst _ 0 _ xs = xs
T> make_lst f n i xs = make_lst f (n-1) (i+1) (Cons (f i) xs)
T> as = make_lst (\x -> 2 * x + 1) n 0 Nil
T> bs = make_lst (^2) n 0 Nil
T>
T>этого доказать нельзя?
На Агде можно. В Haskell ваш make_lst возвращает список неизвестной длины. Т.е. вам-то очевидно, какой, но компилятору — нет. А уж то, что они ещё и одинаковые у двух списков — тем более. В первом же варианте у вас в типе test' заложен факт равности длин списков, пусть сама длина и неизвестна, да и не нужна.
T>В чём оно? Это принципиальное/теоретическое различие, или это особенности компилятора?
Принципиальное в том, что число и число уровня типа (Zero, Succ Zero...) — это разные вещи. То, что вам ясно, что список получился и там, и там длиной n, компилятор понять не в состоянии.
В первом случае мы берем за базу явно равные Zero Zero (это компилятор понимает), а потом по ним строим через Succ и Succ (и это тоже компилятору понятно).
Если ничего не путаю, то отличие и теоретическое, так как в Haskell нет зависимых типов (типы, зависящие от термов), в данном случае они эмулируются через вычисления над типами (типы, зависимые от типов).
В Агде с зависимыми типами такое вполне возможно. Вот, кстати, пример закольцованного счётчика (внизу ссылки на код) от 0 до 255 с доказательствами закольцованности и того, что прибавление и отнимание взаимно уничтожают друг друга. Доказательство выражено в виде обычной функции (+1 и -1 — это не сложение вычитание, это оператор (цельный, но пользовательский) инкремента и декремента):
+1-1₀ : ∀ {n} → (k : Counter n) → k +1 -1 ≅ k
И компилятор сможет это использовать (ну или мы ему подскажем).
Соответственно, для примера выше мы могли бы написать доказательство, что длины списков make_lst n и make_lst m равны, если n равно m. Получится — скомпилируется.
Здравствуйте, Tonal-, Вы писали:
T>Я хочу понять, что именно ты понимаешь под Полиметрическим Полиморфизмом.
Под параметрическим полиморфизмом я понимаю общепринятое определение. Т.е:
1) Воможность написать однородный (работающий одинаково для любого типа) код
2) Типизировать этот код обобщенным типом, содержащим переменные, которые конкретизируются применением обобщенного типа к конкретному (или другому обобщенному, если ПП первоклассный, т.е. импредикативный)
Чтоб понять что я говорю, достаточно прочесть какой-нибудь учебник про типы. TAPL, например.
T>Чем мой вариант настолько отличается от твоего
Самое существенно отличие в том, что "мой" код содержит теорему, утверждающую о том, что списки имеют одинаковую длину, т.е. такой вот тип:
test' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
И конструктивное доказательство (с оговорками) утверждения. Т.е. терм, населяющий этот тип:
test' 0 _ as bs = scalarProduct as bs
test' n i as bs = test' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
Ваш, с другой стороны, не содержит ни первого, ни второго.
T>что твой является ПП и компилится, а мой нет и нет.
Полиморфизм параметрический (с ограниченной квантификацией) и в "моем" и в вашем примере.
Обсуждаемый пример это экстремальная ситуация в которой проявляется разница между ПП и шаблонами.
T>Как можно преобразовать мой код (1-ый и 2-ой) чтобы они компилились?
В вашем коде вы пытаетесь доказать эквивалентность двух термов тьюринг-полного языка. Правда, сама доказываемая теорема не сформулирована, а то бы вы почувствовали, что что-то не то делаете. В общем случае это невозможно.
T>Если ПП настолько сложная весчь, что даже к 7-ой версии haskell её неосилил, то кого-же использовать как эталон?
ПП (ML-style) совсем не сложная вещь, проще шаблонов. Сложные вещи нужны для демонстрации разницы.
T>Мне показалось, что я придумал отличный тест, который показывает влияние рантайма на реализацию ПП.
ПП — это статическая типизация. Рантайм на нее никак не влияет. Может использоваться для оптимизаций, но на семантику языка не влияет.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
S>Если на практике происходит специализация, то это не ПП, а ad-hoc.
Мы, вроде бы, сговорились на том, что особенности реализации не учитываются. Уже передумали?
S>(Потенциальная) — это значит что мне не потребуется специализировать для работы с еще одним любым типом.
Нет, не значит. "мне не потребуется специализировать для работы с еще одним любым типом" — это означает "конечное число".
S>А если мне в программе нужно всего 10 типов и не нужно бесконечность, то это не мои проблемы.
Правильно. А можно написать программу которой нужно не 10, а потенциально бесконечное число типов, как в обсуждаемых примерах — и привет, не компилируется!
S>А вы умудряетесь игнорировать то что определение ПП не требует бесконечности.
Я вообще не утверждал, что требует. Я утверждал, что бесконечность следует из определения ПП.
S>Но не в C#.
И в C#. Нет там никакого применения в рантайме. Вот применение параметрического типа в рантайме на C#:
typeof(Foo<>).MakeGenericType(new[]{typeof(Bar)})
А вот в комайл-тайм:
Foo<Bar>
K>>Но почему? S>Потому что кайндов в дотнете нет.
Спрашиваю: почему нет? Ответ — "потому что потому". Потрясающе просто!
S>Про бесконечное кол-во типов мы уже обсудили что оно не требуется определением.
Да, я вам несколько раз написал, что это следствие определения, но вы это просто проигнорировали. "Обсудили", конечно.
S>Полиморфизм — это фича языка, а не конкретной программы. Потому будет бесконечность типов в одной программе, или в разных — значения не имеет.
Т.е. предмет обсуждения значения не имеет. Отлично!
S>Тем более, что она не требуется и не следует.
Т.е. вы в состоянии дать верхнюю оценку числа "любых типов"? И верхнюю оценку числа типов в описываемых программах?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>Если на практике происходит специализация, то это не ПП, а ad-hoc.
K>Мы, вроде бы, сговорились на том, что особенности реализации не учитываются. Уже передумали?
Я лишь указал на то, что специализация (или выбор реализации в зависимости от типа) называется ad-hoc.
S>>(Потенциальная) — это значит что мне не потребуется специализировать для работы с еще одним любым типом.
K>Нет, не значит. "мне не потребуется специализировать для работы с еще одним любым типом" — это означает "конечное число".
Не означает, иначе вы его должны суметь оценить.
S>>А если мне в программе нужно всего 10 типов и не нужно бесконечность, то это не мои проблемы.
K>Правильно. А можно написать программу которой нужно не 10, а потенциально бесконечное число типов, как в обсуждаемых примерах — и привет, не компилируется!
Зато можно написать бесконечное множество программ, каждая из которых использует конечное число типов и компилируется.
S>>А вы умудряетесь игнорировать то что определение ПП не требует бесконечности.
K>Я вообще не утверждал, что требует. Я утверждал, что бесконечность следует из определения ПП.
А я утверждал что не следует. Как быть?
S>>Но не в C#.
K>И в C#. Нет там никакого применения в рантайме. Вот применение параметрического типа в рантайме на C#: K>
И ? Это действительно применение параметрического типа в рантайме. Противоречит тому что нет никакого применения в рантайме. K>А вот в комайл-тайм: K>
K>Foo<Bar>
K>
А вот это в какой тайм?
class X<T>
{
readonly X<Tuple<T>> _next;
public X(T v1, int count)
{
if (count > 0)
_next = new X<Tuple<T>>(Tuple.Create(v1), count - 1);
}
public Type GetTailType()
{
return _next == null
? GetType()
: _next.GetTailType();
}
}
class Program
{
static void Main()
{
int count = int.Parse(Console.ReadLine());
var x = new X<int>(1, count);
Console.WriteLine(x.GetTailType());
}
}
K>>>Но почему? S>>Потому что кайндов в дотнете нет.
K>Спрашиваю: почему нет? Ответ — "потому что потому". Потрясающе просто!
Так вы мне так же и отвечаете. Не хотелось бы начинать еще одну тему.
"потому что потому" получается после заглядывания в определение кайнда. Может вы под кайндом понимаете что-то другое, но тогда следовало дать свое определение.
S>>Про бесконечное кол-во типов мы уже обсудили что оно не требуется определением.
K>Да, я вам несколько раз написал, что это следствие определения, но вы это просто проигнорировали. "Обсудили", конечно.
А я вам несколько раз написал что 1) не следствие, что 2) бесконечность можно наблюдать на множестве программ.
S>>Полиморфизм — это фича языка, а не конкретной программы. Потому будет бесконечность типов в одной программе, или в разных — значения не имеет.
K>Т.е. предмет обсуждения значения не имеет. Отлично!
Вы обсуждаете полиморфизм или что? Если полиморфизм, то это фича языка, а не программы. Смотрим в определение. Если вы обсуждаете что-то другое, то я пойду.
S>>Тем более, что она не требуется и не следует. K>Т.е. вы в состоянии дать верхнюю оценку числа "любых типов"? И верхнюю оценку числа типов в описываемых программах?
К чему оценка? Доступное мне определение говорит лишь о возможности идентичной обработки значений вне зависимости от типа. Определение в TAPL не сильно от него отличается.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Выделение памяти у нас превартилось в семантически обозримый side effect? С каких пор?
VE>Ты спрашиваешь, как программе определить, выделяется ли память?
Я спрашиваю не об этом. Если угодно, я спрашиваю о том, какой смысл говорить о чистоте чего-либо в рамках системы, где выделение памяти считается обозримым сайд-эффектом?
Можно, конечно, считать что вся доступная память передается неявным параметром в функцию, и неявным результатом возвращается, но такой подход оправдывает любые деструктивные изменения, а не только выделение. Так что, я считаю, что семантически-обозримое выделение памяти — это выход за рамки беседы.
VE>Математики не потому составляют чёткие определения, что они глупые, а как раз наоборот, потому что понимают, что дьявол в деталях. VE>Может, в определение напишем "side-effect — то, что неглупый человек считает side-effect'ом"?
Судя по всему, сейчас там так и написано. Во всяком случае каждый второй норовит его подправить своим определением.
S>>Ты путаешь referential transparency с детерминированностью. Если есть гарантия что результат будет тот же и не будет зависеть от ввода через I/O или скрытых состояний, то это есть детерминированность, а не ссылочная прозрачность. Детерминированной функции портить мир можно. Ссылочно прозрачному выражению — нельзя.
VE>Не путаю. Она и не портит, мир живёт себе как жил. Ничем не хуже, чем с выделенной памятью.
По поводу выделения памяти ответил выше.
VE>>>Что мешает использовать файл как оперативную память? S>>Ничего. Только детерминированности не будет, т.к. общаешься с файлом через I/O. А нет детерминированности — нет и прозрачности.
VE>Почему это не будет? На n! при сотне вызовов возвращает верное значение? Да. Так что ж тогда?
Определение детерминированности гласит что результат кроме гарантированного повтора на тех же арргументах, не должен зависеть от ввода. Чтение файла — это ввод. Надеюсь, что этим самым мы закрываем тему о детерминированности чтения из файла.
VE>>>Нет, ты со мной не согласен. Я задал кучу вопросов, на которые такое определение ответить не в состоянии. S>>Мне казалось, что я на все ответил, опираясь на определение.
VE>Это тебе казалось, а я ответов не получил. Видимо потому, что ты тоже опираешься на "интуицию умных людей", а у меня такое понятие отсутствует, мне нужно чёткое определение, чтоб определение было полезным даже для дурака вроде меня. Эдак можно начать опираться на клятвы матерью и своим здоровьем.
Извини, у меня нет определения, которое бы дало совершенно четкие указания о том, считать ли нагрев процессора/разгон вентилятора и т.п. побочным эффектом. Кроме того, уверен, что если бы оно было, ты бы что-нибудь придумал.
VE>А что если работает рантайм, но программа имеет возможность косвенно следить за её работой? Ну, как с памятью.
По поводу памяти ответил выше. Только я не понимаю, к чему тебе слежение за памятью или голубями? Начинай следить сразу за регистрами CPU. Так проще. Чистота сразу превращается в миф и теряет практическую актуальность.
VE>Или обратный эффект. Что, если я могу рантайм расширять? Ну вот например ввёл доставки голубем, и с т.з. языка это считается runtime extension и даёт те же гарантии, что и работа с памятью. А дальше компилятор на основе метаданных выбирает реализацию сам — то ли память, то ли голуби.
Даже если сам Евклид будет тебе вычислять факториал на пергаменте по тексту программы, к сайд эффектам самой программы, которая явно не изменяет состояние самой себя и окружающего мира, это отношения иметь не будет.
VE>Я не думаю, что это такой уж надуманный пример, вон взять Nemerle 2, который будет как бы инструмент для создания языков. Там может понадобиться нечто подобное. В базовом языке (который ещё и грязный) у нас будет всё превращаться в голубиную почту, но в исходном чистом ДСЛ это будет обычное вычисление.
Так обычно и бывает с чистыми языками, N2 тут ничего не открыл.
S>>Посылка данных в сеть — это output через I/O девайс. А выделение памяти не является семантически обозримым сайд эффектом.
VE>В чём разница? Увидеть можно и то, и другое. Изнутри программы.
Разница в том, что одно считается вводом/выводом, а другое — нет.
S>>Что по поводу insignificant сайд эффекта — так это представь вычисление факториала со внешним мутабельным аккумулятором. Такая функция будет формально impure, но включенная в выражение таким образом, что бы изменения не распространялись за пределы выражения, ее побочный эффект будет незначителен для того, кто ожидает результат выражения.
VE>Можно ещё представить себе вычисление огроменного списка. Формально он pure, но запусти их 3 сразу и памяти не хватит. И это оказывается очень так значительно для того, кто ожидает результат вычисления. Но это лирика.
Конечно лирика. Если мы начнем сравнивать кол-во доступной памяти до и после вызова, то вся чистота будет лирикой, не нужно и одновременного запуска 3х вычислений.
VE>А можно опять вернуться к голубиной почте, а лучше к temporary файл. Можно создать temporary file (имя которому генерирует ОСь, и имя которого неизвестно никому, только handle открывшему процессу), поиспользовать для вычислений и закрыть (после чего он может удалиться, а может и нет, зависит от флагов открытия). Такая функция чиста? Вроде ввод-вывод есть, но побочный эффект необнаруживаем.
Определение требует считать такую функцию грязной, ибо ввод/вывод. Но ты можешь считать ее достаточно чистой для каких-то своих соображений. Можешь даже убедить в этом haskell через unsafePerformIO. Только не жалуйся, что для выполнения чистой функции потребовались права.
Здравствуйте, samius, Вы писали:
K>>Нет, не значит. "мне не потребуется специализировать для работы с еще одним любым типом" — это означает "конечное число". S>Не означает, иначе вы его должны суметь оценить.
Я тут уже указывал способ определить не верхнюю границу, а точное число типов для тизируемого кода на C++
S>Зато можно написать бесконечное множество программ, каждая из которых использует конечное число типов и компилируется.
Но типизируется-то конкретная программа, а не "множество программ". И потенциальная бесконечность следует для одной программы.
S>А я утверждал что не следует. Как быть?
Из определения парам. полиморфизма следует, что у нас есть, по крайней мере, один мономорфный тип () и один полиморфный forall a. Succ a с этим вы согласны? Тогда из любого заданного типа a мы можем сконструировать еще один применением Succ. Таким образом, число типов сверху не ограничено. Q.E.D
Ровно это следствие в приведенных тут программах на языках с ПП и используется.
S>>>Но не в C#.
S>Это действительно применение параметрического типа в рантайме. Противоречит тому что нет никакого применения в рантайме.
Речь шла о том, что применение в рантайме для написания обсуждаемого кода не требуется.
S>А вот это в какой тайм? S>
S> class X<T>
S> {
S> readonly X<Tuple<T>> _next;
S> public X(T v1, int count)
S> {
S> if (count > 0)
S> _next = new X<Tuple<T>>(Tuple.Create(v1), count - 1);
S> }
S> public Type GetTailType()
S> {
S> return _next == null
S> ? GetType()
S> : _next.GetTailType();
S> }
S> }
S> class Program
S> {
S> static void Main()
S> {
S> int count = int.Parse(Console.ReadLine());
S> var x = new X<int>(1, count);
S> Console.WriteLine(x.GetTailType());
S> }
S> }
S>
Компайл-тайм, разумеется.
S>Так вы мне так же и отвечаете. Не хотелось бы начинать еще одну тему. S>"потому что потому" получается после заглядывания в определение кайнда. Может вы под кайндом понимаете что-то другое, но тогда следовало дать свое определение.
Мое определение, разумеется, не отличается от википедийного. Не понял, как вы из него вывели, что в .net нет кайндов? В примерах из статьи, правда, есть фактические ошибки. Например, кайнд (->) в хаскеле не * -> * -> *, а ?? -> ? -> *. Прямые аналоги хаскельных кайндов # * в .net это struct и class, и полиморфизм в .net работает, в отличие от хаскеля, для кайнда, который в хаскеле называется ??. Домашнее задание — к типам каких кайндов можно применять оператор typeof в C#?
S>А я вам несколько раз написал что 1) не следствие, что 2) бесконечность можно наблюдать на множестве программ.
См. доказательство выше.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, samius, Вы писали:
S>Можно, конечно, считать что вся доступная память передается неявным параметром в функцию, и неявным результатом возвращается, но такой подход оправдывает любые деструктивные изменения, а не только выделение. Так что, я считаю, что семантически-обозримое выделение памяти — это выход за рамки беседы.
Неявным нельзя, явным — пожалуйста.
foo :: Ptr Int -> Ptr Int
очень явно отличается от
foo :: Memory -> Ptr Int -> (Ptr Int, Memory)
Вторая — чистая. Вы не можете вычислить две foo, кроме как последовательно их соединив, и в этом смысле они referential transparent.
Для иллюстрации вы можете имплементировать Ptr как Int, а Memory как [Byte].
S>Определение детерминированности гласит что результат кроме гарантированного повтора на тех же арргументах, не должен зависеть от ввода. Чтение файла — это ввод. Надеюсь, что этим самым мы закрываем тему о детерминированности чтения из файла.
Не закроем. Фукнция создаёт файл с уникальным доступом, пишет, читает, в конце возвращает результат. Результат будет неизменен, хоть 100 раз вызови.
По поводу памяти, видел программу artmoney? Находишь адрес памяти, отвечающий за кол-во жизней в игре, ставишь 100. Ну и кто же более детерминирован тут, файл или память?
S>Извини, у меня нет определения, которое бы дало совершенно четкие указания о том, считать ли нагрев процессора/разгон вентилятора и т.п. побочным эффектом. Кроме того, уверен, что если бы оно было, ты бы что-нибудь придумал.
Именно. Поэтому это определение смысла не имеет.
S>По поводу памяти ответил выше. Только я не понимаю, к чему тебе слежение за памятью или голубями? Начинай следить сразу за регистрами CPU. Так проще. Чистота сразу превращается в миф и теряет практическую актуальность.
Как раз referential transparency от этого никуда не девается, а про чистоту я уже не раз сказал, что она не имеет смысла.
S>Даже если сам Евклид будет тебе вычислять факториал на пергаменте по тексту программы, к сайд эффектам самой программы, которая явно не изменяет состояние самой себя и окружающего мира, это отношения иметь не будет.
Ну если в самой программе написано "позови Софокла, пусть он второй пергамент порешает", то очень даже имеет. Но результат всё равно детерминирован.
VE>>В чём разница? Увидеть можно и то, и другое. Изнутри программы. S>Разница в том, что одно считается вводом/выводом, а другое — нет.
Одно является I/O потому, что оно считается I/O, а другое нет — потому что нет. Весьма полезное определение.
S>Определение требует считать такую функцию грязной, ибо ввод/вывод. Но ты можешь считать ее достаточно чистой для каких-то своих соображений. Можешь даже убедить в этом haskell через unsafePerformIO. Только не жалуйся, что для выполнения чистой функции потребовались права (_|_).
Ты можешь считать достаточно чистой функцию вычисления суммы списка чисел. Можешь даже убедить в этом меня. Только не жалуйся, что для выполнения чистой функции не хватило памяти (_|_).
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>>>Нет, не значит. "мне не потребуется специализировать для работы с еще одним любым типом" — это означает "конечное число". S>>Не означает, иначе вы его должны суметь оценить.
K>Я тут уже указывал способ определить не верхнюю границу, а точное число типов для тизируемого кода на C++
Остается выяснить, что же мешает превзойти заранее заданное точное число типов.
S>>Зато можно написать бесконечное множество программ, каждая из которых использует конечное число типов и компилируется.
K>Но типизируется-то конкретная программа, а не "множество программ".
Но мы обсуждаем ПП, фичу языка, а не конкретную программу. Причем тут конкретная программа — непонятно.
K>И потенциальная бесконечность следует для одной программы.
Нет.
S>>А я утверждал что не следует. Как быть?
K>Из определения парам. полиморфизма следует, что у нас есть, по крайней мере, один мономорфный тип () и один полиморфный forall a. Succ a с этим вы согласны?
Нет. Из определения ПП следует лишь то, что такие типы полиморфным кодом должны обрабатываться единым образом. А то что они должны существовать, да еще и в какой-то конкретной программе, да еще и потенциальной бесконечностью — увы, не следует.
K>Тогда из любого заданного типа a мы можем сконструировать еще один применением Succ. Таким образом, число типов сверху не ограничено. Q.E.D
что мешает сконструитьвать еще один тип в программе на C++? Разумеется, явно в коде, или в компайл-тайме.
K>Ровно это следствие в приведенных тут программах на языках с ПП и используется.
Нету такого следствия.
S>>Это действительно применение параметрического типа в рантайме. Противоречит тому что нет никакого применения в рантайме.
K>Речь шла о том, что применение в рантайме для написания обсуждаемого кода не требуется.
Если обсуждаются примеры из http://migmit.livejournal.com/32688.html, то С# там их применяет в рантайме.
S>>А вот это в какой тайм? S>>
S>> int count = int.Parse(Console.ReadLine());
S>> var x = new X<int>(1, count);
S>>
K>Компайл-тайм, разумеется.
Тогда вопрос. Откуда C# в компайл тайм знает что надо применить как минимум 1000 типов? Для 1000 код работает, мог бы работать и для большего числа, если бы не StackOverflow.
S>>"потому что потому" получается после заглядывания в определение кайнда. Может вы под кайндом понимаете что-то другое, но тогда следовало дать свое определение.
K>Мое определение, разумеется, не отличается от википедийного. Не понял, как вы из него вывели, что в .net нет кайндов? В примерах из статьи, правда, есть фактические ошибки. Например, кайнд (->) в хаскеле не * -> * -> *, а ?? -> ? -> *. Прямые аналоги хаскельных кайндов # * в .net это struct и class, и полиморфизм в .net работает, в отличие от хаскеля, для кайнда, который в хаскеле называется ??. Домашнее задание — к типам каких кайндов можно применять оператор typeof в C#?
В случае с кайндами — виноват, признаю свою ошибку.
Д.З. Если я правильно все понял, то typeof разрешен для
#
*
(?,...)->? -- unbounded type name
В последнем случае — подразумевается то, что typeof(Dictionary<,>) можно, а typeof(Dictionary<int, >) нельзя, т.е. никаких частичных применений.
А так же можно для указателей (но не ссылок 'out/ref'), которые я не знаю как обозначить в нотации кайндов хаскеля;
и, конечно, Void.
Может быть еще для чего-то можно.
S>>А я вам несколько раз написал что 1) не следствие, что 2) бесконечность можно наблюдать на множестве программ.
K>См. доказательство выше.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Можно, конечно, считать что вся доступная память передается неявным параметром в функцию, и неявным результатом возвращается, но такой подход оправдывает любые деструктивные изменения, а не только выделение. Так что, я считаю, что семантически-обозримое выделение памяти — это выход за рамки беседы.
VE>foo :: Memory -> Ptr Int -> (Ptr Int, Memory) VE>Вторая — чистая. Вы не можете вычислить две foo, кроме как последовательно их соединив, и в этом смысле они referential transparent. VE>Для иллюстрации вы можете имплементировать Ptr как Int, а Memory как [Byte].
Вот что в этом аспекте интересно, а почему в хаскеле любую функцию (например, конкретно map) не оформили в виде монады Memory a?
S>>Определение детерминированности гласит что результат кроме гарантированного повтора на тех же арргументах, не должен зависеть от ввода. Чтение файла — это ввод. Надеюсь, что этим самым мы закрываем тему о детерминированности чтения из файла.
VE>Не закроем. Фукнция создаёт файл с уникальным доступом, пишет, читает, в конце возвращает результат. Результат будет неизменен, хоть 100 раз вызови.
А ввод? Детерминированность это не только гарантия повторения результата. Уже устал повторять. VE>По поводу памяти, видел программу artmoney? Находишь адрес памяти, отвечающий за кол-во жизней в игре, ставишь 100. Ну и кто же более детерминирован тут, файл или память?
Мне непонятно что значит "более детерминирован". Результат, завязанный на работу с файлом недетерминирован по определению. С памятью — зависит от остального.
S>>Извини, у меня нет определения, которое бы дало совершенно четкие указания о том, считать ли нагрев
VE>Именно. Поэтому это определение смысла не имеет.
А какой смысл в хаскеле делать вид что его функции чисты? Или он как-то с памятью по-особому работает?
S>>По поводу памяти ответил выше. Только я не понимаю, к чему тебе слежение за памятью или голубями? Начинай следить сразу за регистрами CPU. Так проще. Чистота сразу превращается в миф и теряет практическую актуальность.
VE>Как раз referential transparency от этого никуда не девается, а про чистоту я уже не раз сказал, что она не имеет смысла.
referential transparency определяется через те же эффекты, что и чистота. Непонятно, что вдруг чистота куда-то девается, а referential transparency, определенная на той же детерминированности — нет.
S>>Даже если сам Евклид будет тебе вычислять факториал на пергаменте по тексту программы, к сайд эффектам самой программы, которая явно не изменяет состояние самой себя и окружающего мира, это отношения иметь не будет.
VE>Ну если в самой программе написано "позови Софокла, пусть он второй пергамент порешает", то очень даже имеет. Но результат всё равно детерминирован.
"позови Софокла, пусть он порешает" — не ввод вывод. А вот "спроси Софокла, чему будет равен результат" — ввод вывод.
VE>>>В чём разница? Увидеть можно и то, и другое. Изнутри программы. S>>Разница в том, что одно считается вводом/выводом, а другое — нет.
VE>Одно является I/O потому, что оно считается I/O, а другое нет — потому что нет. Весьма полезное определение.
Оно по крайней мере допускает наличие чистых функций в хаскеле (кроме действий). Твои же определения — наоборот, говорят что лишь действия IO a чисты (ну и может быть еще что-то, что не оперирует санками).
VE>Ты можешь считать достаточно чистой функцию вычисления суммы списка чисел. Можешь даже убедить в этом меня. Только не жалуйся, что для выполнения чистой функции не хватило памяти (_|_).
Дык это нормально. Чистой функции никто не запрещает жрать память. Портить левую — нельзя. А жрать — сколько влезет.
Здравствуйте, samius, Вы писали:
VE>>foo :: Memory -> Ptr Int -> (Ptr Int, Memory) VE>>Вторая — чистая. Вы не можете вычислить две foo, кроме как последовательно их соединив, и в этом смысле они referential transparent. VE>>Для иллюстрации вы можете имплементировать Ptr как Int, а Memory как [Byte].
S>Вот что в этом аспекте интересно, а почему в хаскеле любую функцию (например, конкретно map) не оформили в виде монады Memory a?
Потому же, почему и runST не надо оформлять в виде монады IO — referential transparency.
Потому же, почему и withTemporaryFile не нужно оформлять в виде монады IO — referential transparency.
Приведённая же функция foo с деструктивным присваиванием — в общем случае грязная.
Если от withTemporaryFile вам нужен именно её "побочный эффект" в виде ввода-вывода, язык должен позволять это указать, тогда это будет IO функция с вводом-выводом и выполняться будет каждый раз.
Что именно важно: результат функции (полученный как угодно) или её эффект — определять должен программист.
Ситуация чем-то напоминает мне вопрос о том, "исключительная ли ситуация, если openFile не смог открыть файл?"
Файл-то чёрт с ним, но вот parse и tryParse существуют прямо здесь и прямо сейчас. А ещё всякие foo и fooAsync.
Вакханалия какая-то.
VE>>Не закроем. Фукнция создаёт файл с уникальным доступом, пишет, читает, в конце возвращает результат. Результат будет неизменен, хоть 100 раз вызови. S>А ввод? Детерминированность это не только гарантия повторения результата. Уже устал повторять.
А я устал повторять, что чтение памяти ничем не отличается от чтения файла, кроме того, что одно IO, потому что считается IO, а другое не IO, потому что таковым не считается.
S>Мне непонятно что значит "более детерминирован". Результат, завязанный на работу с файлом недетерминирован по определению. С памятью — зависит от остального.
По определению "недетерминирован, потому что считается таковым". Это не определение, а чушь какая-то.
S>>>Извини, у меня нет определения, которое бы дало совершенно четкие указания о том, считать ли нагрев
VE>>Именно. Поэтому это определение смысла не имеет. S>А какой смысл в хаскеле делать вид что его функции чисты? Или он как-то с памятью по-особому работает?
Они referential trasparent. unsafePerformIO видел? Так вот она unsafe (как и unsafeCoerce) потому, что ответственность лежит на программисте.
S>referential transparency определяется через те же эффекты, что и чистота. Непонятно, что вдруг чистота куда-то девается, а referential transparency, определенная на той же детерминированности — нет.
Не через те же.
An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program (in other words, yielding a program that has the same effects and output on the same input).
Я настаиваю на том, что если вы считаете идентичными программы, одна из которых выделяет памяти до гигабайта, а другая до мегабайта (потому что в ней что-то там замемоизовали благодаря referential trasparent), то можно считать идентичными и те, одна из которых 3 раза создаёт использует с нуля временный файл, а другая лишь один (потому что можно работу с файлом мемоизовали).
Более того, я считаю, что важность эффекта определяется не википедией, а программистом.
Т.е. конкретно в данном примере я считаю, что первые две программы по эффектам отличаются сильнее, чем вторые две.
Кстати говоря, вы что, никогда не видели ситуации, как мемоизуют функции с побочными эффектами (в вашем определении)? Получается то же самое. Ну да, ну да, там же этого программист захотел. А в отношении withTemporaryFile мне запрещает этого хотеть википедия?
S>Оно по крайней мере допускает наличие чистых функций в хаскеле (кроме действий). Твои же определения — наоборот, говорят что лишь действия IO a чисты (ну и может быть еще что-то, что не оперирует санками).
Мои определения ничего такого не говорят.
VE>>Ты можешь считать достаточно чистой функцию вычисления суммы списка чисел. Можешь даже убедить в этом меня. Только не жалуйся, что для выполнения чистой функции не хватило памяти (_|_). S>Дык это нормально. Чистой функции никто не запрещает жрать память. Портить левую — нельзя. А жрать — сколько влезет.
И какую такую левую функцию портит withTemporaryFile, которой не хватило прав?
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>foo :: Memory -> Ptr Int -> (Ptr Int, Memory) VE>>>Вторая — чистая. Вы не можете вычислить две foo, кроме как последовательно их соединив, и в этом смысле они referential transparent. VE>>>Для иллюстрации вы можете имплементировать Ptr как Int, а Memory как [Byte].
S>>Вот что в этом аспекте интересно, а почему в хаскеле любую функцию (например, конкретно map) не оформили в виде монады Memory a?
VE>Потому же, почему и runST не надо оформлять в виде монады IO — referential transparency. VE>Потому же, почему и withTemporaryFile не нужно оформлять в виде монады IO — referential transparency.
А разве referential transparency не чушь? Она не сохраняет нагрев процессора
VE>Приведённая же функция foo с деструктивным присваиванием — в общем случае грязная.
А что по поводу грязной функции map без деструктивного присваивания?
VE>Если от withTemporaryFile вам нужен именно её "побочный эффект" в виде ввода-вывода, язык должен позволять это указать, тогда это будет IO функция с вводом-выводом и выполняться будет каждый раз.
не знаю, что это
VE>Что именно важно: результат функции (полученный как угодно) или её эффект — определять должен программист.
велкам в С++
VE>Ситуация чем-то напоминает мне вопрос о том, "исключительная ли ситуация, если openFile не смог открыть файл?" VE>Файл-то чёрт с ним, но вот parse и tryParse существуют прямо здесь и прямо сейчас. А ещё всякие foo и fooAsync. VE>Вакханалия какая-то.
что за parse и tryParse?
VE>>>Не закроем. Фукнция создаёт файл с уникальным доступом, пишет, читает, в конце возвращает результат. Результат будет неизменен, хоть 100 раз вызови. S>>А ввод? Детерминированность это не только гарантия повторения результата. Уже устал повторять.
VE>А я устал повторять, что чтение памяти ничем не отличается от чтения файла, кроме того, что одно IO, потому что считается IO, а другое не IO, потому что таковым не считается.
от того что ты устал считаться по-другому не станет.
S>>Мне непонятно что значит "более детерминирован". Результат, завязанный на работу с файлом недетерминирован по определению. С памятью — зависит от остального.
VE>По определению "недетерминирован, потому что считается таковым". Это не определение, а чушь какая-то.
От того что ты называешь определения чушью, других определений не появляется. Предлагаю все-таки ссылаться на какие-то источники, а не на "я так считаю".
S>>А какой смысл в хаскеле делать вид что его функции чисты? Или он как-то с памятью по-особому работает?
VE>Они referential trasparent. unsafePerformIO видел? Так вот она unsafe (как и unsafeCoerce) потому, что ответственность лежит на программисте.
То есть все, кто пишут что хаскель чист — пишут чушь?
S>>referential transparency определяется через те же эффекты, что и чистота. Непонятно, что вдруг чистота куда-то девается, а referential transparency, определенная на той же детерминированности — нет.
VE>Не через те же.
VE>An expression is said to be referentially transparent if it can be replaced with its value without changing the behavior of a program (in other words, yielding a program that has the same effects and output on the same input).
VE>Я настаиваю на том, что если вы считаете идентичными программы, одна из которых выделяет памяти до гигабайта, а другая до мегабайта (потому что в ней что-то там замемоизовали благодаря referential trasparent), то можно считать идентичными и те, одна из которых 3 раза создаёт использует с нуля временный файл, а другая лишь один (потому что можно работу с файлом мемоизовали).
идентичными? Нигде такого не писал. А как же нагрев процессора? Он делает RT чушью
VE>Более того, я считаю, что важность эффекта определяется не википедией, а программистом.
даёшь всё IO без монады IO!
VE>Т.е. конкретно в данном примере я считаю, что первые две программы по эффектам отличаются сильнее, чем вторые две.
окей, я это уже понял. Так же как и то, что это плохо соотносится с другими источниками об эффектах.
VE>Кстати говоря, вы что, никогда не видели ситуации, как мемоизуют функции с побочными эффектами (в вашем определении)? Получается то же самое. Ну да, ну да, там же этого программист захотел. А в отношении withTemporaryFile мне запрещает этого хотеть википедия?
хотеть не запрещает
S>>Оно по крайней мере допускает наличие чистых функций в хаскеле (кроме действий). Твои же определения — наоборот, говорят что лишь действия IO a чисты (ну и может быть еще что-то, что не оперирует санками).
VE>Мои определения ничего такого не говорят.
Мне показалось что они указывают на то что выделение памяти есть семантически обозримый побочный эффект.
S>>Дык это нормально. Чистой функции никто не запрещает жрать память. Портить левую — нельзя. А жрать — сколько влезет.
VE>И какую такую левую функцию портит withTemporaryFile, которой не хватило прав?
я ничего не утверждал о порче функций. Я написал что выделять память чистой функции не запрещено.
Здравствуйте, samius, Вы писали:
VE>>Потому же, почему и runST не надо оформлять в виде монады IO — referential transparency. VE>>Потому же, почему и withTemporaryFile не нужно оформлять в виде монады IO — referential transparency. S>А разве referential transparency не чушь? Она не сохраняет нагрев процессора
И не должна. RT позволяет заменить вычисление значением без смены поведения программы. Поведение программы определяет программист.
Т.е. если должна была греть процессор и перестала — то вызов функции, греющую процессор, заменить на значение нельзя. Если пофиг, греет или нет, то можно менять, и функция RT. Это относительное понятие, а не абсолютное.
S>А что по поводу грязной функции map без деструктивного присваивания?
Какой такой грязной map?
VE>>Если от withTemporaryFile вам нужен именно её "побочный эффект" в виде ввода-вывода, язык должен позволять это указать, тогда это будет IO функция с вводом-выводом и выполняться будет каждый раз. S>не знаю, что это
withTemporaryFile :: (Handle -> IO r) -> r
Если вы используете её так же, как runST, она вполне себе RT. Если вам необходимо именно создание временного файла, то необходим аналог stToIO.
Что такое runST знаете?
VE>>Что именно важно: результат функции (полученный как угодно) или её эффект — определять должен программист. S>велкам в С++
Это такой язык, где гипотетически можно сделать всё (и некоторые даже воротят монстров), но на деле никто ничего такого не использует, потому что без слёз не взглянешь? Спасибо, знаком.
VE>>Ситуация чем-то напоминает мне вопрос о том, "исключительная ли ситуация, если openFile не смог открыть файл?" VE>>Файл-то чёрт с ним, но вот parse и tryParse существуют прямо здесь и прямо сейчас. А ещё всякие foo и fooAsync. VE>>Вакханалия какая-то. S>что за parse и tryParse?
System.Int.parse и System.Int.tryParse (.Net)
VE>>По определению "недетерминирован, потому что считается таковым". Это не определение, а чушь какая-то. S>От того что ты называешь определения чушью, других определений не появляется. Предлагаю все-таки ссылаться на какие-то источники, а не на "я так считаю".
Можно конечно за абсолют принять википедию и годами всех убеждать, что она верна, а думать на эту тему — время терять, а можно таки самому думать над вопросом. Глядишь, и определение в вики исправят.
S>>>А какой смысл в хаскеле делать вид что его функции чисты? Или он как-то с памятью по-особому работает?
VE>>Они referential trasparent. unsafePerformIO видел? Так вот она unsafe (как и unsafeCoerce) потому, что ответственность лежит на программисте. S>То есть все, кто пишут что хаскель чист — пишут чушь?
Я тут раз десять сказал, что понятие "чистый" бесполезное, а ты меня спрашиваешь, чистый ли Хаскель. Ну, с т.з. википедии чистый, но меня это не волнует.
VE>>Я настаиваю на том, что если вы считаете идентичными программы, одна из которых выделяет памяти до гигабайта, а другая до мегабайта (потому что в ней что-то там замемоизовали благодаря referential trasparent), то можно считать идентичными и те, одна из которых 3 раза создаёт использует с нуля временный файл, а другая лишь один (потому что можно работу с файлом мемоизовали). S>идентичными? Нигде такого не писал. А как же нагрев процессора? Он делает RT чушью
Поведение программы — это не её проявление на одном каком-то компьютере, а заложенное программистом. То, что на одной системе она греет процессор, на другой моргает лампочками — в поведение не включается.
Если я пишу программу, которая именно что должна греть процессор, то да, вычисление списка не будет RT. Если я пишу калькулятор, то греет он процессор, открывает ли временные файлы, подключается ли к базе — это всё не важно. Если поведение от замены функций на значение (калькуляторное) не меняется, функции — RT.
VE>>Более того, я считаю, что важность эффекта определяется не википедией, а программистом. S>даёшь всё IO без монады IO!
Не всё, а только то, которое является лишь побочным продуктом и не влияет на результат. С putStrLn это, очевидно, не так. putStrLn — ввод-вывод.
withNewIORef (withNewIORef :: a -> (IORef a -> IO b) -> b — не ввод-вывод).
VE>>Кстати говоря, вы что, никогда не видели ситуации, как мемоизуют функции с побочными эффектами (в вашем определении)? Получается то же самое. Ну да, ну да, там же этого программист захотел. А в отношении withTemporaryFile мне запрещает этого хотеть википедия? S>хотеть не запрещает
Ок, пойдём дальше. Вот пишите вы себе на C++ мемоизатор. И что, поведение программы меняется? Ну т.е. вроде бы что-то меняется, как если одну сортировку на другую поменять, но вот поведение — не меняется. Так как время — не заложено в поведении.
VE>>Мои определения ничего такого не говорят. S>Мне показалось что они указывают на то что выделение памяти есть семантически обозримый побочный эффект.
А также на то, что является ли это поведением или просто implementation defined (т.е. программе в целом насрать на это), так сказать, определяет программист.
S>>>Дык это нормально. Чистой функции никто не запрещает жрать память. Портить левую — нельзя. А жрать — сколько влезет.
VE>>И какую такую левую функцию портит withTemporaryFile, которой не хватило прав? S>я ничего не утверждал о порче функций. Я написал что выделять память чистой функции не запрещено.
Не запрещено Абсолютом.
Вот эту фразу поясните:
Дык это нормально. Чистой функции никто не запрещает жрать память. Портить левую — нельзя. А жрать — сколько влезет.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>Потому же, почему и runST не надо оформлять в виде монады IO — referential transparency. VE>>>Потому же, почему и withTemporaryFile не нужно оформлять в виде монады IO — referential transparency. S>>А разве referential transparency не чушь? Она не сохраняет нагрев процессора
VE>И не должна. RT позволяет заменить вычисление значением без смены поведения программы. Поведение программы определяет программист.
"(in other words, yielding a program that has the same effects and output on the same input)"
VE>Т.е. если должна была греть процессор и перестала — то вызов функции, греющую процессор, заменить на значение нельзя. Если пофиг, греет или нет, то можно менять, и функция RT. Это относительное понятие, а не абсолютное.
Про нагрев процессора можно сказать то же самое и в плане чистоты и побочных эффектов. Нагрев процессора, так же как и изменения в регистрах/кэшах и т.п., не считаются побочным эффектом (конечно, если не влияют на результат вычислений других программ).
S>>А что по поводу грязной функции map без деструктивного присваивания? VE>Какой такой грязной map?
Тот самый map, который возвращает результат, выделяя память под него. Ты ведь предложил считать выделение памяти обозримым побочным эффектом, вот потому map стал грязным в тот же час.
VE>>>Если от withTemporaryFile вам нужен именно её "побочный эффект" в виде ввода-вывода, язык должен позволять это указать, тогда это будет IO функция с вводом-выводом и выполняться будет каждый раз. S>>не знаю, что это
VE>
VE>withTemporaryFile :: (Handle -> IO r) -> r
VE>
Как я понимаю, такая сигнатура не мешает нагадить не только в этот файл, а вообще везде где взбредет.
VE>Если вы используете её так же, как runST, она вполне себе RT. Если вам необходимо именно создание временного файла, то необходим аналог stToIO. VE>Что такое runST знаете?
Еще нет. Но во всяком случае, об этом есть что почитать. А по поводу withTemporaryFile гугл молчит.
S>>что за parse и tryParse?
VE>System.Int.parse и System.Int.tryParse (.Net)
Не признал. Не тот регистр, да и типов System.Int в дотнете нет. Теперь понял, о чем речь. Согласен, не лучшее решение. Но когда не знаешь, как бывает по-другому, претензий не возникает, хотя чувство дискомфорта присутствует.
VE>Можно конечно за абсолют принять википедию и годами всех убеждать, что она верна, а думать на эту тему — время терять, а можно таки самому думать над вопросом. Глядишь, и определение в вики исправят.
Не вижу повода для его исправления. А, главное, не понятно, какое определение лучше и чем.
S>>То есть все, кто пишут что хаскель чист — пишут чушь?
VE>Я тут раз десять сказал, что понятие "чистый" бесполезное, а ты меня спрашиваешь, чистый ли Хаскель. Ну, с т.з. википедии чистый, но меня это не волнует.
Нет, не это спрашиваю. По этому поводу у меня свое мнение и пока никто меня не разубедил. Я спрашиваю о том, считаешь ли ты что все кто пишет что хаскель чист, пишут чушь?
VE>>>Я настаиваю на том, что если вы считаете идентичными программы, одна из которых выделяет памяти до гигабайта, а другая до мегабайта (потому что в ней что-то там замемоизовали благодаря referential trasparent), то можно считать идентичными и те, одна из которых 3 раза создаёт использует с нуля временный файл, а другая лишь один (потому что можно работу с файлом мемоизовали). S>>идентичными? Нигде такого не писал. А как же нагрев процессора? Он делает RT чушью
VE>Поведение программы — это не её проявление на одном каком-то компьютере, а заложенное программистом. То, что на одной системе она греет процессор, на другой моргает лампочками — в поведение не включается.
В побочные эффекты тоже VE>Если я пишу программу, которая именно что должна греть процессор, то да, вычисление списка не будет RT. Если я пишу калькулятор, то греет он процессор, открывает ли временные файлы, подключается ли к базе — это всё не важно. Если поведение от замены функций на значение (калькуляторное) не меняется, функции — RT.
Вижу, что твоя RT тоже устроена по понятиям. Файлы и БД не считаются единым целым с программой. Чтение из них — это ввод. Если то, что ты прочитаешь из файла/БД будет влиять на результат вычисления, то его нельзя классифицировать как RT.
VE>>>Более того, я считаю, что важность эффекта определяется не википедией, а программистом. S>>даёшь всё IO без монады IO!
VE>Не всё, а только то, которое является лишь побочным продуктом и не влияет на результат. С putStrLn это, очевидно, не так. putStrLn — ввод-вывод.
Ура, хоть в чем-то согласие. Только поправлю, putStrLn — это не ввод-вывод, ввод-вывод — это будет IO(), который она вернет. VE>withNewIORef (withNewIORef :: a -> (IORef a -> IO b) -> b — не ввод-вывод).
Нет? А что мне помешает подсунуть в withNewIORef экшн, возвращаемый putStrLn (который ввод-вывод)? Просто я еще не дорос до этого. Потому и спрашиваю, мешает ли что-то заюзать putStrLn в withNewIORef? Соответственно, если не мешает, то withNewIORef будет таким же вводом-выводом, как и экшн от putStrLn.
VE>Ок, пойдём дальше. Вот пишите вы себе на C++ мемоизатор. И что, поведение программы меняется? Ну т.е. вроде бы что-то меняется, как если одну сортировку на другую поменять, но вот поведение — не меняется. Так как время — не заложено в поведении.
Это зависит от прочих вещей. Если в мемоизированном коде был ввод-вывод, то поведение поменяется.
VE>>>Мои определения ничего такого не говорят. S>>Мне показалось что они указывают на то что выделение памяти есть семантически обозримый побочный эффект.
VE>А также на то, что является ли это поведением или просто implementation defined (т.е. программе в целом насрать на это), так сказать, определяет программист.
Если определяет программист, то это значит что не определяет никто другой. Соответственно, любые изменения порядка/кэширования и т.п. определяются программистом и никем другим.
VE>>>И какую такую левую функцию портит withTemporaryFile, которой не хватило прав? S>>я ничего не утверждал о порче функций. Я написал что выделять память чистой функции не запрещено.
VE>Не запрещено Абсолютом.
VE>Вот эту фразу поясните: VE>
VE>Дык это нормально. Чистой функции никто не запрещает жрать память. Портить левую — нельзя. А жрать — сколько влезет.
VE>Кто чего портит у withTemporaryFile?
putStrLn портит. Мне ведь ничего не мешает заюзать его с withTemporaryFile? Если что-то мешает, то там посмотрим.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>По поводу Haskell вы мне лучше скажите по вашим определениям, почему interact принимает чистую функцию, а newIORef возвращает IO (IORef a)?
Я не понимаю сути вопроса. Почему бы interact-у не принимать чистую функцию? И причем тут определения? И почему бы newIORef-у не возвращать IO?
Чему тут конкретно должны мешать определения, которые не мои?
Здравствуйте, samius, Вы писали:
VE>>И не должна. RT позволяет заменить вычисление значением без смены поведения программы. Поведение программы определяет программист. S>"(in other words, yielding a program that has the same effects and output on the same input)"
Именно. Поведение архиватора, например, какое? Файлы архивировать или память выделять? Вот первое — поведение, второе так, деталь реализации.
VE>>Какой такой грязной map? S>Тот самый map, который возвращает результат, выделяя память под него. Ты ведь предложил считать выделение памяти обозримым побочным эффектом, вот потому map стал грязным в тот же час.
Я сказал, что оно ничем не отличается от создания временного файла, и потому чисты они или нет (а точнее RT), зависит от ситуации, а не от самого факта. И map — RT.
VE>>
VE>>withTemporaryFile :: (Handle -> IO r) -> r
VE>>
S>Как я понимаю, такая сигнатура не мешает нагадить не только в этот файл, а вообще везде где взбредет.
Конкретно такая да. Если это принципиально, её можно переписать по аналогии с монадой ST и получить гарантии работы с одним файлом.
Суть сказанного от этого не меняется.
VE>>Если вы используете её так же, как runST, она вполне себе RT. Если вам необходимо именно создание временного файла, то необходим аналог stToIO. VE>>Что такое runST знаете? S>Еще нет. Но во всяком случае, об этом есть что почитать. А по поводу withTemporaryFile гугл молчит.
Тогда почитайте, потому что я на это больше всего упор делаю.
Вкратце: у нас есть монада ST, внутри которой (и только внутри) мы можем выделять память, работать с мутабельными переменными.
Однако так как от одних только махинаций с памятью мы гарантированно не сможем получить разные результаты, всю совокупность вычислений мы можем запихнуть в runST и получить чистый результат.
Т.е. почти как unsafePerformIO, но уже не unsafe, потому что у нас подмножество операций, не позволяющих выдать различные результаты.
S>Нет, не это спрашиваю. По этому поводу у меня свое мнение и пока никто меня не разубедил. Я спрашиваю о том, считаешь ли ты что все кто пишет что хаскель чист, пишут чушь?
Нет.
VE>>Если я пишу программу, которая именно что должна греть процессор, то да, вычисление списка не будет RT. Если я пишу калькулятор, то греет он процессор, открывает ли временные файлы, подключается ли к базе — это всё не важно. Если поведение от замены функций на значение (калькуляторное) не меняется, функции — RT. S>Вижу, что твоя RT тоже устроена по понятиям. Файлы и БД не считаются единым целым с программой. Чтение из них — это ввод. Если то, что ты прочитаешь из файла/БД будет влиять на результат вычисления, то его нельзя классифицировать как RT.
readIORef тоже тогда можно считать вводом. С т.з. языка ничем не отличается.
Касаемо чтения файла — не влиять, а менять результат от вызова к вызову, тогда не RT.
VE>>withNewIORef (withNewIORef :: a -> (IORef a -> IO b) -> b — не ввод-вывод). S>Нет? А что мне помешает подсунуть в withNewIORef экшн, возвращаемый putStrLn (который ввод-вывод)? Просто я еще не дорос до этого. Потому и спрашиваю, мешает ли что-то заюзать putStrLn в withNewIORef? Соответственно, если не мешает, то withNewIORef будет таким же вводом-выводом, как и экшн от putStrLn.
Посмотри runST, я в общем и целом про него, но на примере IO, в котором, разумеется, гарантию не дашь.
VE>>Ок, пойдём дальше. Вот пишите вы себе на C++ мемоизатор. И что, поведение программы меняется? Ну т.е. вроде бы что-то меняется, как если одну сортировку на другую поменять, но вот поведение — не меняется. Так как время — не заложено в поведении. S>Это зависит от прочих вещей. Если в мемоизированном коде был ввод-вывод, то поведение поменяется.
Открыли файл для чтения в начале программы. Потом пользуемся функцией readFileContents, возвращающую из раза в раз одно и то же по понятным причинам.
Поменяется ли поведение, если в этой функции прочесть один раз во внутренний буфер?
VE>>А также на то, что является ли это поведением или просто implementation defined (т.е. программе в целом насрать на это), так сказать, определяет программист. S>Если определяет программист, то это значит что не определяет никто другой. Соответственно, любые изменения порядка/кэширования и т.п. определяются программистом и никем другим.
Пусть. Зато уже не мешает такую функцию передать в map, например, требующую RT функцию.
VE>>Кто чего портит у withTemporaryFile? S>putStrLn портит. Мне ведь ничего не мешает заюзать его с withTemporaryFile? Если что-то мешает, то там посмотрим.
Да, забыл упомянуть про это, хотя держал в голове. Имел в виду подмножество IO (статически-гарантируемое), работающее только с этим файлом.
Само собой, с моей терминологией сделать RT getLine (точнее World -> (String, World)) не получится, но разговор о таком RT вообще бессмысленен. Можно, конечно, считать, что в эту функцию условно передаётся вся Вселенная (в общем-то, именно это и подразумевается), но зачем?
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>"(in other words, yielding a program that has the same effects and output on the same input)"
VE>Именно. Поведение архиватора, например, какое? Файлы архивировать или память выделять? Вот первое — поведение, второе так, деталь реализации.
Согласен, но с натяжкой. Вот такие несущественные детали реализации, как логирование, или недетерминированность времени создания файла-архива, это тоже эффекты. И это такие эффекты, которые могут быть приняты во внимание при оценке чистоты архиватора. Если же рассматривать архиватор как функцию, принимающую параметры и байты файла и выдающую байты архива, то можно говорить о чистоте такой функции. Можем проводить параллели, а вообще сама программа архиватора и функция, которая архивирует в этом отношении будут разными вещами в разных контекстах.
S>>Тот самый map, который возвращает результат, выделяя память под него. Ты ведь предложил считать выделение памяти обозримым побочным эффектом, вот потому map стал грязным в тот же час.
VE>Я сказал, что оно ничем не отличается от создания временного файла, и потому чисты они или нет (а точнее RT), зависит от ситуации, а не от самого факта. И map — RT.
Со оговоркой по поводу того что файл "гарантирует" общение лишь с ним и что чтение будет лишь того что записано, без всяких "наводок", то — да, соглашусь, что на это можно смотреть одинаково (на такой временный файл и работу с памятью).
S>>Как я понимаю, такая сигнатура не мешает нагадить не только в этот файл, а вообще везде где взбредет.
VE>Конкретно такая да. Если это принципиально, её можно переписать по аналогии с монадой ST и получить гарантии работы с одним файлом.
Принципиально, но строить ничего не надо, достаточно принять такую гарантию. VE>Суть сказанного от этого не меняется.
Я понял, о чем ты говорил, просто до конца пытался показать что такой трюк позволяет втиснуть в него взаимодействие с другим миром, где эффекты для нас значимы.
S>>Еще нет. Но во всяком случае, об этом есть что почитать. А по поводу withTemporaryFile гугл молчит.
VE>Тогда почитайте, потому что я на это больше всего упор делаю.
Обязательно в ближайшее время. Инфы с документации мне явно не хватило, что бы перехватить смысл, попытаюсь вкурить Lazy Functional State Threads, упомянутый в статье.
VE>Вкратце: у нас есть монада ST, внутри которой (и только внутри) мы можем выделять память, работать с мутабельными переменными. VE>Однако так как от одних только махинаций с памятью мы гарантированно не сможем получить разные результаты, всю совокупность вычислений мы можем запихнуть в runST и получить чистый результат.
Это как если мы в C++ напишем чистую функцию, которая будет дергать нечистые с локальными (относительно внешней функции) побочными эффектами, но сама внешняя при этом может быть формально чиста. Что-то похожее?
VE>Т.е. почти как unsafePerformIO, но уже не unsafe, потому что у нас подмножество операций, не позволяющих выдать различные результаты.
Интересно, попробую найти ответы в Lazy Functional State Threads.
S>>Нет, не это спрашиваю. По этому поводу у меня свое мнение и пока никто меня не разубедил. Я спрашиваю о том, считаешь ли ты что все кто пишет что хаскель чист, пишут чушь? VE>Нет.
Рад. Тогда может быть это намек на то, что ты и эти авторы по-разному воспринимают чистоту?
S>>Вижу, что твоя RT тоже устроена по понятиям. Файлы и БД не считаются единым целым с программой. Чтение из них — это ввод. Если то, что ты прочитаешь из файла/БД будет влиять на результат вычисления, то его нельзя классифицировать как RT.
VE>readIORef тоже тогда можно считать вводом. С т.з. языка ничем не отличается. VE>Касаемо чтения файла — не влиять, а менять результат от вызова к вызову, тогда не RT.
Что-то в этом есть. Т.е. если ты как разработчик даешь зуб что из БД будет прочитано одно и то же при любом стечении обстоятельств, при котором вообще разумно ожидать результатов выполнения программы, тогда, может быть, имеет смысл говорить об RT. Но таки с оговоркой, что такое RT не может быть выкуплено компилятором с умыслом произвести оптимизацию.
VE>Посмотри runST, я в общем и целом про него, но на примере IO, в котором, разумеется, гарантию не дашь.
Ок.
S>>Это зависит от прочих вещей. Если в мемоизированном коде был ввод-вывод, то поведение поменяется.
VE>Открыли файл для чтения в начале программы. Потом пользуемся функцией readFileContents, возвращающую из раза в раз одно и то же по понятным причинам. VE>Поменяется ли поведение, если в этой функции прочесть один раз во внутренний буфер?
Нет.
S>>Если определяет программист, то это значит что не определяет никто другой. Соответственно, любые изменения порядка/кэширования и т.п. определяются программистом и никем другим.
VE>Пусть. Зато уже не мешает такую функцию передать в map, например, требующую RT функцию.
Не мешает.
S>>putStrLn портит. Мне ведь ничего не мешает заюзать его с withTemporaryFile? Если что-то мешает, то там посмотрим.
VE>Да, забыл упомянуть про это, хотя держал в голове. Имел в виду подмножество IO (статически-гарантируемое), работающее только с этим файлом.
Да, я это предпологал.
VE>Само собой, с моей терминологией сделать RT getLine (точнее World -> (String, World)) не получится, но разговор о таком RT вообще бессмысленен. Можно, конечно, считать, что в эту функцию условно передаётся вся Вселенная (в общем-то, именно это и подразумевается), но зачем?
Вот и я об этом же. Зачем? Особенно, зачем такое считать, если нам не нужна формальная чистота IO String? Мы можем смело называть IO String грязной, не RT, и ничего нам от этого не будет. Хаскель не станет хуже.
Что же касается локальных миров, то у меня мнение следующее: Я готов считать формально чистой функцию, если гарантируется, что все ее взаимодействие с неким локальным миром а) детерминировано, б) не имеет влияние на глобальный мир (тут с кучей оговорок). Т.е. если ты докажешь, что твоя функция (и только она) пишет в файл результаты детерминированных вычислений, читает их детерминированным образом (что само по себе звучит кощунственно по отношению к чтению файла), исключая изменения данных файла извне, если внешний мир (по отношению к этому файлу) не узнает о существовании этого файла, либо даже просто будет делать вид что его нет и не было. Соответственно, вместо файла могут выступать голуби, Софокл с пергаментом (пергамент придется съесть) и т.п.
Но это будет такая, "пользовательская чистота", или чистота в неком контексте, отличающегося от того, что по умолчанию. И все-таки, такая чистота нужна лишь для того, что бы утверждать что совокупность грязных действий имеет локальный и/или незначительный эффект на результат вычислений.
Здравствуйте, MigMit, Вы писали:
MM>А, в этом смысле. Понял. Но тут, скорее, играет "двустороннесть" правил пролога, это не относится именно к ПМ.
Относится. Такое же точно программирование в ограничениях, только полноценное. ИМХО, большую часть работы для статически-типизированного языка можно было бы делать в compile-time, и только если данных недостаточно — вызывать поиск в рантайм.
Здравствуйте, Klapaucius, Вы писали:
K>Нет. MigMit раньше исходил из ошибочного предположения, что в C++ есть параметрический полиморфизм. Но параметрический полиморфизм означает, что forall a. Box a описывает бесконечное кол-во типов.
Почему? ad-hoc — это же не соседний класс, а подкласс чистого полиморфизма, частный случай.
Можно объявить "бесконечное" мн-в типов в С++:
K>В C++ же кол-во типов всегда конечно, поэтому полиморфизм там только ad-hoc.
Обычно для показанной техники делают adhoc только для BigN=0, но и это необязательно. Точнее сформулируем так: можно использовать только те типы, которые выводимы во время компиляции, т.е. независимы от данных. И тогда мн-во реально задействованных в программе типов ес-но будет конечным, но это могут быть любые типы из почти бесконечного объвленного мн-ва:
Здравствуйте, Klapaucius, Вы писали:
T>>Я не поленился и перепёр haskell в шаблоны один в один: K>Ох. Лучше бы вы не поленились читать то, что писал МигМит в том треде и что сейчас пишу я. K>Вот смотрите:
K>Видите, этот код принимает число как аргумент командной строки. Он компилируется. НЕ интерпретируется.
Неправда, он интерпретируется в этом месте:
ScalarProduct a => ScalarProduct (Cons a) where
В отличие от Haskel, подобное преобразование типов (распаковка) для кода Tonal- выполняется в compile-time.
K>И типы проверяет статически.
Тоже неправда. Для АлгТД статически проверяется только м-но допустимых запакованных типов, то бишь статически проверяется лишь некое ограничение на возможный тип, но не сам запакованный тип. Конкретный тип распаковывается исключительно в рантайм, а сама техника распаковки тождественна технике dynamic_cast (с точностью до деталей, то бишь с точностью до способа кодирования/представления токена типа для целей рантайма).
K>Закомментированная строчка вызовет ошибку. Потому, что проверяются не абсолютные размеры списков, а то, что списки одинакового размера. А это известно на этапе компиляции, завит только от написанного кода, а не входных данных.
И опять неправда и непонимание происходящего. Это зависит от реализованной системы типов. Там, где ML-яык работает с боксированными значениями в рантайм, там мы имеем классическую эмуляцию динамики/интерпретации, хоть и со статическими проверками допустимости мн-ва АлгТД в момент компиляции. А там, где ML-язык будет пытаться инстанциировать типы целиком, чтобы избежать лишней динамики в рантайм, без техники зависимых типов не обойтись.
K>И зависимые типы для этого не нужны
Они не нужны только для боксированной реализации рекурсивных типов и прочей динамической типизации.
Здравствуйте, samius, Вы писали:
K>>Я тут уже указывал способ определить не верхнюю границу, а точное число типов для тизируемого кода на C++ S>Остается выяснить, что же мешает превзойти заранее заданное точное число типов.
Мешает вид полиморфизма в C++
S>Но мы обсуждаем ПП, фичу языка, а не конкретную программу. Причем тут конкретная программа — непонятно.
При том, что фича языка позволяет писать определенный класс конкретных программ. А ее отсутствие — не позволяет.
Рассуждать про n программ, которые в сумме что-то реализуют — это софистика. Ну, как говорить, что (int,+,*) это не кольцо вычетов, а кольцо целых чисел, складывая модули кольца вычетов во всех возможных программах.
K>>И потенциальная бесконечность следует для одной программы. S>Нет.
Да.
K>>Из определения парам. полиморфизма следует, что у нас есть, по крайней мере, один мономорфный тип () и один полиморфный forall a. Succ a с этим вы согласны? S>Нет. Из определения ПП следует лишь то, что такие типы полиморфным кодом должны обрабатываться единым образом.
Вы берете удобную для себя половину определения, которое я тут
привел полностью. Одна только однородность кода не является каким-то характеристическим признаком параметрического полиморфизма. Сабтайпинг, например, тоже позволяет писать однородный код. Собственно, компилятор Java и переписывает однородный код на языке с параметрическим полиморфизмом в однородный код с использованием сабтайпинга на языке, в котором ПП нет вовсе.
S> А то что они должны существовать, да еще и в какой-то конкретной программе, да еще и потенциальной бесконечностью — увы, не следует.
А неудобную для вас вторую часть определения, о том, как однородный код типизируется вовсе отбрасываете. Из второй части определения следует, что они должны существовать.
В C++ они, разумеется, не существуют. Шаблонный код на C++ вообще не может быть типизирован (отсюда и вся мощность шаблонов) и не типизируется. В некоторых реализациях вроде gcc проверяются кайнды (* или Nat), в некоторых, вроде, макрософтовской — даже кайнды не проверяются (ну или не проверялись раньше). Типизируется только инстанциация, результат применения. В языках с ПП типизируется и однородный код и само применение.
S>что мешает сконструитьвать еще один тип в программе на C++? Разумеется, явно в коде, или в компайл-тайме.
Отсутствие в системе типов C++ типа forall a. Succ a.
S>Если обсуждаются примеры из http://migmit.livejournal.com/32688.html, то С# там их применяет в рантайме.
Покажите применение в рантайме из того кода.
S>Тогда вопрос. Откуда C# в компайл тайм знает что надо применить как минимум 1000 типов? Для 1000 код работает, мог бы работать и для большего числа, если бы не StackOverflow.
Еще раз, параметрический код не требует для проверки знания числа типов. Ну, как типизация функции + над int не требует построения и типизации таблицы констант для всего декартова произведения двух множеств значений int.
S>
S>#
S>*
S>(?,...)->? -- unbounded type name
S>
Ну да, это я и имел в виду.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, vdimas, Вы писали:
V>Почему? ad-hoc — это же не соседний класс, а подкласс чистого полиморфизма, частный случай.
Нет. Ad-hoc это соседний класс. Никакого "чистого полиморфизма" не бывает. Бывают такие виды порлиморфизма: параметрический полиморфизм (дженерики), ad-hoc (перегрузка/специализация) и сабтайпинг (полиморфизм через наследование) + возможные сочетания.
V>Можно объявить "бесконечное" мн-в типов в С++: V>
Ну и где тут (потенциальная) бесконечность типов? Верхняя граница тут явно задоается.
K>>В C++ же кол-во типов всегда конечно, поэтому полиморфизм там только ad-hoc.
V>Обычно для показанной техники делают adhoc только для BigN=0, но и это необязательно. Точнее сформулируем так: можно использовать только те типы, которые выводимы во время компиляции, т.е. независимы от данных. И тогда мн-во реально задействованных в программе типов ес-но будет конечным, но это могут быть любые типы из почти бесконечного объвленного мн-ва:
Ну, в обсуждаемом примере все типы выводимы во время компиляции (именно это под словом "типы" обычно и понимают). От данных они тоже независисмы. Но шаблоны таким способом использовать нельзя.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, vdimas, Вы писали:
V>Неправда, он интерпретируется в этом месте: V>
V>ScalarProduct a => ScalarProduct (Cons a) where
V>
Ага, классы типов — это такой недопролог, который интерпретируется, правда во время компиляции.
V>В отличие от Haskel, подобное преобразование типов (распаковка) для кода Tonal- выполняется в compile-time.
Это в хаскеле — во время компиляции. А в C++ такой код не написать.
V>Тоже неправда. Для АлгТД статически проверяется только м-но допустимых запакованных типов, то бишь статически проверяется лишь некое ограничение на возможный тип, но не сам запакованный тип.
Не понимаю, что за запакованный тип. Для АлгТД статически проверяется множество населяющих его значений — как и у любого другого типа. Типы, ни "запакованные", ни какие АлгТД не населяют, они населяют кайнд. Кайнды, конечно, тоже бывают алгебраическими.
V>Конкретный тип распаковывается исключительно в рантайм, а сама техника распаковки тождественна технике dynamic_cast (с точностью до деталей, то бишь с точностью до способа кодирования/представления токена типа для целей рантайма).
Каст преобразует тип значения. Матчинг АлгТД никаким преобразованием типов не занимается.
V>И опять неправда и непонимание происходящего.
Точно, непонимание, как оно есть.
V>Там, где ML-яык работает с боксированными значениями в рантайм
Какая разница, боксированы значения или нет, если речь идет о типах? Будут значения боксированными или нет — это вообще детали реализации, на статичность/динамичность
системы типов не влияющие.
V>там мы имеем классическую эмуляцию динамики/интерпретации, хоть и со статическими проверками допустимости
Так динамика или стат. проверки? Тут одно из двух.
V>мн-ва АлгТД в момент компиляции.
Что за "множество АлгТД"?
V>инстанциировать типы целиком
А это что значит?
V>Они не нужны только для боксированной реализации рекурсивных типов и прочей динамической типизации.
Типы не бывают "боксированными" — такими бывают значения. И к динамике это не имеет отношения. Динамика — это когда нет стат. типов, есть рантайм проверки. Типы в рантайме в хаскеле вообще не проверяются. В рантайме их нет — они уже стерты.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Ага, классы типов — это такой недопролог, который интерпретируется, правда во время компиляции.
При чем тут классы типов и паттерн-матчинг алгебраических типов? Классы типов — это лишь проверяемые в compile-time ограничения, то бишь контракты. В данном случае класс типов дает знать, что для типа существует scalarProduct, который можно безопасно вызывать. Я обратил внимание на то, что сама реализация контракта была описана для разных значений дескриминатора АлгТД. Т.е. это такая фишка Хаскеля — при объявлении ф-ий доступен урезанный паттерн матчинг по дискриминаторам АлгТД, где конкретная ф-ия выбирается в рантайм по результатам матчинга.
V>>В отличие от Haskel, подобное преобразование типов (распаковка) для кода Tonal- выполняется в compile-time.
K>Это в хаскеле — во время компиляции. А в C++ такой код не написать.
Никогда в Хаскеле распаковка АлгТД не происходит во время компиляции.
V>>Тоже неправда. Для АлгТД статически проверяется только м-но допустимых запакованных типов, то бишь статически проверяется лишь некое ограничение на возможный тип, но не сам запакованный тип.
K>Не понимаю, что за запакованный тип.
Это тип одного из возможных значений в группе, которая, в свою очередь, реализована техникой АлгТД.
K>Для АлгТД статически проверяется множество населяющих его значений — как и у любого другого типа.
Неверно, статически проверяется мн-во ТИПОВ населяющих его значений, но не сами значения, бо Хаскель, в отличии от С++ НЕ умеет статически реагировать на сами значения (С++ умеет на целочисленные, в т.ч. на указатели). Хаскель умеет только на их типы.
Для случая Хаскеля этот упакованный тип представляет из себя тупл (возможно пустой), где его имя (алиас типа) можно принять за имя соответствующего конструктора АлгТД.
K>Типы, ни "запакованные", ни какие АлгТД не населяют, они населяют кайнд. Кайнды, конечно, тоже бывают алгебраическими.
Вид, сорт, тип — это синонимы в теории групп.
V>>Конкретный тип распаковывается исключительно в рантайм, а сама техника распаковки тождественна технике dynamic_cast (с точностью до деталей, то бишь с точностью до способа кодирования/представления токена типа для целей рантайма).
K>Каст преобразует тип значения. Матчинг АлгТД никаким преобразованием типов не занимается.
Опять неверно, dynamic_cast НЕ изменяет само значение, переданное по ссылке (это обязательно, использовать dynamic_cast можно только через ссылку или указатель), оно тестирует тип поданного значения и преобразует тип ссылки, а не само значение, предоставляя доступ затем к мемберам типа по ссылке. Матчинг АлгТД в Хаскеле аналогично проверяет в рантайм дискриминатор объединения, то бишь запакованный тип, примерно точно так же, как dynamic_cast проверяет токен типа (на который ссылается экземпляр типа через указатель на vtable), а удачная ветка матчинга затем предоставляет доступ к значению упакованного в АлгТД типа.
V>>Там, где ML-яык работает с боксированными значениями в рантайм
K>Какая разница, боксированы значения или нет, если речь идет о типах?
Разница в том, что боксированный рекурсивный тип представляет из себя в памяти список (в простейшем случае, в непростейшем — дерево) из однородных узлов, а в небоксированном случае значение типа должно располагаться в памяти сплошным куском. Поэтому для первого случая достаточно одной реализации на каждый узел, а во втором потребуется столько различного кода, сколько типов было использовано во время компиляции. Поэтому в первом случае — в случае интерпретации структуры типа в рантайм, этот список/дерево может быть сколь угодно большой длины/глубины, то бишь мощность рекурсивного типа может быть сколь угодно большой.
Справедливости ради, в простейшем случае можно обойтись и без боксинга для рекурсивных типов, т.е. для линейного списка в памяти, где рекурсивный тип идет как хвостовой, его можно расположить линейно и сгенерить лишь один однородный код на все типы разной мощности. Но для дерева или для случая нехвостового рекурсивного типа — уже никак. Почему никак? Предлагаю попробовать нарисовать карту памяти как будут располагаться поля.
То бишь боксированность нам гарантирует одинаковый размер полей, т.е. одинаковую карту размещения для любого инстанса такого типа. Возвращаясь к примеру: в итоге, код ф-ии scalarProduct будет одинаков для всех узлов рекурсивного типа Cons int a.
K>Будут значения боксированными или нет — это вообще детали реализации, на статичность/динамичность системы типов не влияющие.
Угу, именно на это твое непонимание и обратил внимание. Статическая система типов хапрактерна не тлько статической проверкой контрактов. Упоминание эффективности статики как само собой разумеющееся (и даже ты тут отметился не раз в этой ветке, напоминая про статику) — это следствие того, что статически порожденный код ЗНАЕТ, как расположено в памяти значение оперируемого типа, и обращается к полям значения непосредственно, то бишь через фиксированные смещения в памяти, а не через дескрипторы/акцессоры.
V>>там мы имеем классическую эмуляцию динамики/интерпретации, хоть и со статическими проверками допустимости
K>Так динамика или стат. проверки? Тут одно из двух.
Мн-во допустимых типов значений АлгТД проверяется статически, конкретный тип из множества — динамически. Какие проблемы-то?
V>>мн-ва АлгТД в момент компиляции.
K>Что за "множество АлгТД"?
ОМГ
V>>инстанциировать типы целиком
K>А это что значит?
Наверно криво выразился. Имел ввиду, что в случае боксированной реалиации рекурсивных типов порожденному компилятором коду для его работоспособности достаточно знать об устройстве лишь части значения, а не всего значения целиком. Доступ к остальной части значения происходит рекурсивно через паттерн-матчинг, т.е. через рантайм-проверку, ну т.е. через динамику.
V>>Они не нужны только для боксированной реализации рекурсивных типов и прочей динамической типизации.
K>Типы не бывают "боксированными" — такими бывают значения. И к динамике это не имеет отношения. Динамика — это когда нет стат. типов, есть рантайм проверки. Типы в рантайме в хаскеле вообще не проверяются. В рантайме их нет — они уже стерты.
Неверно. Дискриминатор АлгТД присутствует в рантайм будучи сохраненным по адресу значения, и этот дискриминатор проверяется исключительно в рантайм. Доступ к запакованному в АлгТД значению предоставляется затем исключительно через ПМ (фишка Хаскеля — у него других способов и нет), т.е. делает невозможным неверную интерпретацию памяти, в которой находится значение АлгТД. В этом и заключается типобезопасность, которая таки для случая Хаскеля динамическая, когда идет оперирование АлгТД. Статически может проверяться лишь полнота, т.е. наличие всех веток для всех дискриминаторов АлгТД, одна из которых должна быть вызвана в рантайм (угу, включая умолчательное '_', но уже без доступа к памяти значения!).
Здравствуйте, Klapaucius, Вы писали:
K>Ну и где тут (потенциальная) бесконечность типов? Верхняя граница тут явно задоается.
Ну, если верхняя граница заведомо больше доступной памяти, то какая разница? Совсем то уж бесконечный тип в Хаскеле тоже невозможен.
K>>>В C++ же кол-во типов всегда конечно, поэтому полиморфизм там только ad-hoc.
V>>Обычно для показанной техники делают adhoc только для BigN=0, но и это необязательно. Точнее сформулируем так: можно использовать только те типы, которые выводимы во время компиляции, т.е. независимы от данных. И тогда мн-во реально задействованных в программе типов ес-но будет конечным, но это могут быть любые типы из почти бесконечного объвленного мн-ва:
K>Ну, в обсуждаемом примере все типы выводимы во время компиляции (именно это под словом "типы" обычно и понимают). От данных они тоже независисмы. Но шаблоны таким способом использовать нельзя.
Дело не в шаблонах, а в переносе части типизации из статики в динамику для случая Хаскеля. Рядом ответил подробнее, предлагаю обсуждать там.
Здравствуйте, Klapaucius, Вы писали:
K>Нет. Ad-hoc это соседний класс. Никакого "чистого полиморфизма" не бывает. Бывают такие виды порлиморфизма: параметрический полиморфизм (дженерики), ad-hoc (перегрузка/специализация) и сабтайпинг (полиморфизм через наследование) + возможные сочетания.
Разве? Параметрический — это ортогонально чистому, бо может быть еще разновидность полиморфизма через утиную типизацию. Чистый полиморфизм — это который "одинаковый для всех случаев". ИМХО, в любом случае параметрический или любой другой вид полиморфизма превращается в ad-hoc в конечной точке, т.е. во время исполнения, коль используемые типы-параметры имеют различную структуру в памяти. Например, для генериков дотнета мы в ограничениях определяем контракт (напр. интерфейс), а затем вызываем методы интерфейса в коде. И, хотя код одинаков для всех типов с виду, но в момент вызова доступных из ограничений методов интерфейсов происходит runtime-ad-hoc через таблицу методов интерфейсов (перегрузка методов для конкретных this).
Отличие шаблонов С++ в том, что там так же доступен compile-time ad-hoc, который недоступен в генериках. Это из-за того, что для боксированных типов JIT генерит идентичный код, поэтому увеличивать мощность рекурсивных типов в рантайм в дотнете можно относительно дешево (это в плане приведенного примера). Если же имеем специализацию генерика небоксированным типом... то можно из двух строчек описания рекурсивного типа породить десятки мегабайт кода в compile-time. Или же еще больше нагрузить JIT в рантайм, т.к. будет каждый раз генерироваться новый код.
Кстати, из-за последнего момента когда-то много лет назад я уже высказывал идею, что вполне можно было бы в генерики добавить compile-time ad-hoc, коль они для релиза 2-го дотнета разработали технику уникальной генерации методов для небоксированных типов. Т.е. раз такая техника есть, почему бы ее не использовать? Напомню, в бете второго дотнета для небоксированных типов для случая генериков происходило боксирование, а потом они добавили этот мощный инструмент, который превращает генерики в runtime ad-hoc для случая небоксированных типов.
Здравствуйте, vdimas, Вы писали:
V>При чем тут классы типов и паттерн-матчинг алгебраических типов?
Понятия не имею. Это вы вдруг про паттерн-матчинг АлгТД заговорили.
V>Классы типов — это лишь проверяемые в compile-time ограничения, то бишь контракты.
Не только.
V>Я обратил внимание на то, что сама реализация контракта была описана для разных значений дескриминатора АлгТД.
Что такое "разные значения дескриминатора АлгТД"? Разные конструкторы?
V>Я обратил внимание на то, что сама реализация контракта была описана для разных значений дескриминатора АлгТД.
Если "декскриминаторы" — конструкторы, то нет. Кмплементации класса нельзя писать для разных конструкторов. Только для типов. Так в этом примере и сделано.
V>Никогда в Хаскеле распаковка АлгТД не происходит во время компиляции.
Если под "распаковкой" подразумевается паттерн-матчинг, то да, не происходит. Но в данном случае это и не нужно. Вычисления то тайплевелные — матчатся типы Nil и Cons. Они, естественно, матчатся в компайл-тайме, потому что в рантайме их просто нет.
V>Это тип одного из возможных значений в группе, которая, в свою очередь, реализована техникой АлгТД.
Но у всех значений один и тот же тип — именно что АлгТД.
K>>Для АлгТД статически проверяется множество населяющих его значений — как и у любого другого типа.
V>Неверно, статически проверяется мн-во ТИПОВ населяющих его значений, но не сами значения
Откуда всялось множество типов? Проверяется принадлежность значения к множеству значений — типу.
V>Хаскель, в отличии от С++ НЕ умеет статически реагировать на сами значения (С++ умеет на целочисленные, в т.ч. на указатели).
Хаскель этого, понятное дело, не умеет. Зависимых типов в хаскеле нет. Точно так же и C++ не умеет. Статически "реагирует" он не нацелочисленные значения, а на целочисленные типы — типы кайнда Nat.
V>Для случая Хаскеля этот упакованный тип представляет из себя тупл (возможно пустой), где его имя (алиас типа) можно принять за имя соответствующего конструктора АлгТД.
Алиас типа нельзя принять за имя конструктора (тег в размеченном объединении). Первое — тип. Второе — значение. Они в разных пространствах имен живут, работать с ними можно на разных стадиях — первые существуют только в компайл-тайме (это некоторое упрощение, при желании есть метаданные/рефлексия, но в данном случае они не используются), вторые только в рантайме. Отображать из типов в термы, правда, можно — для этого классы типов есть, но наоборот — нельзя.
V>Вид, сорт, тип — это синонимы в теории групп.
А в теории типов — нет. Вид (кайнд) — это тип типа, а сорт — тип кайнда.
K>>Каст преобразует тип значения. Матчинг АлгТД никаким преобразованием типов не занимается. V>Опять неверно, dynamic_cast НЕ изменяет само значение
Я и не говорю, что меняет значение — меняет тип.
V>, переданное по ссылке (это обязательно, использовать dynamic_cast можно только через ссылку или указатель), оно тестирует тип поданного значения и преобразует тип ссылки, а не само значение, предоставляя доступ затем к мемберам типа по ссылке.
Аналог dynamic_cast в Хаскеле — Data.Dynamic. Там действительно есть проверка типа в рантайме.
V>Матчинг АлгТД в Хаскеле аналогично проверяет в рантайм дискриминатор объединения,
Ну разумеется.
V> то бишь запакованный тип
Вот только имя конструктора — тег в размеченном объединении — это не тип. Точно так же как 3 не тип и "hello" — не тип. Это значение.
V>, примерно точно так же, как dynamic_cast проверяет токен типа (на который ссылается экземпляр типа через указатель на vtable), а удачная ветка матчинга затем предоставляет доступ к значению упакованного в АлгТД типа.
Еще раз, типы в АлгТД не упаковываются. Вы путаете сабтайпинг и АлгТД. Они не родственники и даже не однофамильцы.
K>>Какая разница, боксированы значения или нет, если речь идет о типах? V>Разница в том, что боксированный рекурсивный тип представляет из себя в памяти список (в простейшем случае, в непростейшем — дерево) из однородных узлов, а в небоксированном случае значение типа должно располагаться в памяти сплошным куском. Поэтому для первого случая достаточно одной реализации на каждый узел, а во втором потребуется столько различного кода, сколько типов было использовано во время компиляции. Поэтому в первом случае — в случае интерпретации структуры типа в рантайм, этот список/дерево может быть сколь угодно большой длины/глубины, то бишь мощность рекурсивного типа может быть сколь угодно большой.
Еще раз повторяю, если речь идет о типах. Типы в памяти ничего из себя не представляют (в рантайме). Их просто нет. А для значений да, все это верно (с оговорками). Вот только никакой интерпретации структуры типов в рантайме тут нет.
V>Справедливости ради, в простейшем случае можно обойтись и без боксинга для рекурсивных типов, т.е. для линейного списка в памяти, где рекурсивный тип идет как хвостовой, его можно расположить линейно и сгенерить лишь один однородный код на все типы разной мощности. Но для дерева или для случая нехвостового рекурсивного типа — уже никак. Почему никак? Предлагаю попробовать нарисовать карту памяти как будут располагаться поля.
V>То бишь боксированность нам гарантирует одинаковый размер полей, т.е. одинаковую карту размещения для любого инстанса такого типа. Возвращаясь к примеру: в итоге, код ф-ии scalarProduct будет одинаков для всех узлов рекурсивного типа Cons int a.
Ну правильно. Я тут так и писал. Код используется повторно ценой боксинга, такую цену за повторное использование сгенерированного кода C++ платить не может. Но мне справедливо напомнили, что я увлекаюсь деталями реализации. Ну а теперь вы увлекаетесь. Разница-то в системе типов. И в гипотетической реализации C++ где скомпилированный код повторно используется (это только для частных случаев можно будет сделать) и все забоксено — этот код или не будет работать все равно, или это будет не C++. Речь то о системе типов и семантике языка.
K>>Будут значения боксированными или нет — это вообще детали реализации, на статичность/динамичность системы типов не влияющие.
V>Угу, именно на это твое непонимание и обратил внимание. Статическая система типов хапрактерна не тлько статической проверкой контрактов. Упоминание эффективности статики как само собой разумеющееся (и даже ты тут отметился не раз в этой ветке, напоминая про статику) — это следствие того, что статически порожденный код ЗНАЕТ, как расположено в памяти значение оперируемого типа, и обращается к полям значения непосредственно, то бишь через фиксированные смещения в памяти, а не через дескрипторы/акцессоры.
Ну так он и в хаскеле знает смещения и расположения в памяти. Теги "типов" как в динамике он в рантайме не проверяет.
V>Мн-во допустимых типов значений АлгТД проверяется статически, конкретный тип из множества — динамически. Какие проблемы-то?
Ну так динамически проверяются значения, а не типы. точно так же, как и в C++. В чем динамическая типизация-то? Вот a == 3 — проверка динамическая, а типизация — сатическая, потому, что и код сравнения и структура для хранения 3 в памяти известна на этапе компиляции. Вы как-то систематически типы и значения путаете.
V>Наверно криво выразился. Имел ввиду, что в случае боксированной реалиации рекурсивных типов порожденному компилятором коду для его работоспособности достаточно знать об устройстве лишь части значения, а не всего значения целиком. Доступ к остальной части значения происходит рекурсивно через паттерн-матчинг, т.е. через рантайм-проверку, ну т.е. через динамику.
Для самой рекурсивной структуры данных — да. Точно так же, как и в C++.
V>Неверно. Дискриминатор АлгТД присутствует в рантайм будучи сохраненным по адресу значения, и этот дискриминатор проверяется исключительно в рантайм. Доступ к запакованному в АлгТД значению предоставляется затем исключительно через ПМ (фишка Хаскеля — у него других способов и нет), т.е. делает невозможным неверную интерпретацию памяти, в которой находится значение АлгТД. В этом и заключается типобезопасность, которая таки для случая Хаскеля динамическая, когда идет оперирование АлгТД.
Ну да, безопасность размеченных объединений по сравнению с неразмеченными достигается рантайм проверкой. Точно так же как и с доступом по индексу в массиве, например. Но какое это отношение имеет к типизации? В C++ аналогично и то и другое либо опасно, либо безовасно за счет рантайм проверки. Но тип у массива любой длины — одинаковый. И у любого конструктора АлгТД — одинаковый. Ну так при чем тут динамическая типизация-то?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, vdimas, Вы писали:
V>Ну, если верхняя граница заведомо больше доступной памяти, то какая разница? Совсем то уж бесконечный тип в Хаскеле тоже невозможен.
Понятно, что актуально бесконечный тип невозможет, я всегда и писал, что он бесконечный потенциально. Но это ведь не значит, что между конечным числом типов и потенциально бесконечным нет разницы. пример как раз и показывает, что есть.
V>Дело не в шаблонах, а в переносе части типизации из статики в динамику для случая Хаскеля.
Ну так в том и дело, что никакая часть типизации по сравнению с C++ в рантайм не перенесена. Перенос типизации в рантайм (даже в форме сабтайпинга а-ля мейнстрим-ООП) в хаскеле без расширений вообще не возможен.
V>Рядом ответил подробнее, предлагаю обсуждать там.
ОК.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, vdimas, Вы писали:
K>>Нет. Ad-hoc это соседний класс. Никакого "чистого полиморфизма" не бывает. Бывают такие виды порлиморфизма: параметрический полиморфизм (дженерики), ad-hoc (перегрузка/специализация) и сабтайпинг (полиморфизм через наследование) + возможные сочетания.
V>Разве? Параметрический — это ортогонально чистому, бо может быть еще разновидность полиморфизма через утиную типизацию.
Утиная типизация — это структурный сабтайпинг. разновидность сабтайпинга.
V> Чистый полиморфизм — это который "одинаковый для всех случаев".
Такой результат — однородный код — достигается разными видами полиморфизма.
V> ИМХО, в любом случае параметрический или любой другой вид полиморфизма превращается в ad-hoc в конечной точке, т.е. во время исполнения, коль используемые типы-параметры имеют различную структуру в памяти. Например, для генериков дотнета мы в ограничениях определяем контракт (напр. интерфейс), а затем вызываем методы интерфейса в коде. И, хотя код одинаков для всех типов с виду, но в момент вызова доступных из ограничений методов интерфейсов происходит runtime-ad-hoc через таблицу методов интерфейсов (перегрузка методов для конкретных this).
Интерфейсы в качестве констрейнтов дженериков — это, разумеется, ad-hoc полиморфизм, просто он тут взаимодействует с параметрическим полиморфизмом дженериков и сабтайпингом интерфейсов. И да, в чистом виде параметрический полиморфизм нигде не существует. Разве что в SML (с некоторыми оговорками вроде типов с проверкой на эквивалентность и перегрузки мат. операций). В подавляющем большинстве языков сложно взаимодействуют 2-3 разновидности полиморфизма. В случае C++ просто среди этих 2-х разновидностей параметрического не оказалось.
Что касается конечной точки то да, в большинстве реализаций параметрического полиморфизма, (и в C# и в Хаскеле и т.д.) предпринимаются действия для того, чтоб избавляться от боксинга, где это возможно, генерировать специализированный код и т.д. Но это детали реализации, тут обсуждается начальная точка.
V>Отличие шаблонов С++ в том, что там так же доступен compile-time ad-hoc, который недоступен в генериках. Это из-за того, что для боксированных типов JIT генерит идентичный код, поэтому увеличивать мощность рекурсивных типов в рантайм в дотнете можно относительно дешево (это в плане приведенного примера). Если же имеем специализацию генерика небоксированным типом... то можно из двух строчек описания рекурсивного типа породить десятки мегабайт кода в compile-time. Или же еще больше нагрузить JIT в рантайм, т.к. будет каждый раз генерироваться новый код.
Compile-time ad-hoc в дженериках доступен в виде констрейнтов. Издержки реализации — понятны и обсуждались.
V>Кстати, из-за последнего момента когда-то много лет назад я уже высказывал идею, что вполне можно было бы в генерики добавить compile-time ad-hoc, коль они для релиза 2-го дотнета разработали технику уникальной генерации методов для небоксированных типов. Т.е. раз такая техника есть, почему бы ее не использовать? Напомню, в бете второго дотнета для небоксированных типов для случая генериков происходило боксирование, а потом они добавили этот мощный инструмент, который превращает генерики в runtime ad-hoc для случая небоксированных типов.
1) Про ситуацию с бетой CLR 2 я тут уже писал.
2) В том и дело, что не рантайм, а компайл-тайм.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, alex_public, Вы писали:
_>Вообще из всех декларативных языков у меня настоящую симпатию вызвал только Пролог.
Тогда попробуй Меркури. Это типизированный аналог пролога. Хотя Прологу он тоже уступает по удобству.
Но пролог — это такая же игрушка как Хаскель. Даже Хаскель намного практичнее. По сути интересного в прологе только унификация и поиск с откатами. А это можно и в библиотеке воплотить. Уж в ДСЛ-е точно.
В коде вывода типов Немерла как раз используется специализированный язык унификации. Для своей задачи он очень удобен.
_>Все функциональные языки (считаем подвидом декларативных) как-то особых прелестей не показали, а недостатков кучи. Жаль что он всё же не подходит для наших реальных дел — тут точно только "фан".
Во-первых, прелесть у ФЯ достаточно, если сравнивать с С++, а не с Прлогом. При этом производительность полученного кода как раз можно сравнивать с С++, а не с Прологом (для которого она стремится к нулю).
_>Для дела же мне сейчас больше всего нравятся императивные языки с возможностями функциональности, метапрограммирования и т.п. Типа C++11, а лучше D и т.п.
_>Немерле посмотрю. ) Хотя пока не понял его ориентированность.
Дык в нем как раз ФП — это так, приятная мелочь. А главное в нем это как раз метапрограммирование (МП). Немерл — это язык с расширяемым синтаксисом и средствами МП позволяющими делать ДСЛ-и и автоматизировать работу.
Так что если тебе интересен МП, то немерл нужно глядеть в первую очередь. Да и в практическом плане он куда интереснее. После реализации конвертера статей для РСДН на нем (раньше был на VBA) скорость его работы увеличилась даже не в сотни раз.
_>Т.е. понятно что везде можно извернуться, но допустим на Питоне мы пишем скрипты (внутренние и серверные), но не большие проекты. А на C++ скрипты не пишем, хотя это возможно в теории.. Вот для чего Немерле лучше?
Скорее для задач где вы применяете С++. Но по уровню язык ближе к Питону. Ну, а особый выигрыш в нем как раз если хочется использовать метапрограммирование или ДСЛ-подход. Вот забавный примерчик возможностей встраивания ДСЛ-я в язык. Это генератор парсеров. Он крут как вареные яйца сам по себе, но в данном контексте интересно то, что он встроен в основной язык и сделано это совершенно бесшовно. При работе под IDE ошибки высвечиваются сразу и по месту, работает навигация, а для правил стартовых описанных в грамматике волшебным образом сразу же становятся доступны методы вызова.
Ну, и все это в языке без изысков, в котором человек с сишным бэкграундом может разобраться не ломая стереотипов.
Это естественно не серебряная пуля и тем более не передовой край науки. Но посмотреть его будет интересно — это точно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, alex_public, Вы писали:
_>Остались ещё требования: быстродействие (в разумных пределах), доступ к системным функциям, возможность подключения (или наличие большого числа биндингов как у Питона) к C/C++ библиотекам.
В этом плане Немерл является практически полной копией C#-а. Быстродействие практически стакое же. Лучшим компиляторам С++ он конечно проиграет, но не порядки, а в лучшем случае разы. Но в общем, будет на уровне.
Доступ к системным функциям отличный. Системные функции описываются специальным образом и используются как родные. Есть даже сайт позволяющий найти такие описания.
К С-библиотекам подключение идет теми же средствами.
С С++ по сложнее. У него нет внятного бинарного протокола. Но и тут есть выход. Есть C++CLI. С его помощью можно очень легко сделать обертку над любой С++-но библиотеку и уже эту обертку из любого дотнетного языка.
Скорость вызова методов из С ниже чем родных, но не так уж что бы это было критично. Скажем на работе с GDI разницу не заметить.
Кроме того в дотенете есть море библиотек на все случаи жизни. Они в сто раз удобнее чем использование сишных аналогов.
_>.Net в моих глазах — это недостаток. Для дела естественно, a для "фана" пофиг. Но вроде бы же говорят что планируется Немерле для нейтива.
Нет недостаток кода нужно вести разработку не под винду. Но и тут есть выход — можно.
В будущем мы планируем сделать бэкэнды для других платформ (в том числе и нативную). Но до этого еще далеко. Года три, точно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Klapaucius, Вы писали:
V>>Кстати, из-за последнего момента когда-то много лет назад я уже высказывал идею, что вполне можно было бы в генерики добавить compile-time ad-hoc, коль они для релиза 2-го дотнета разработали технику уникальной генерации методов для небоксированных типов. Т.е. раз такая техника есть, почему бы ее не использовать? Напомню, в бете второго дотнета для небоксированных типов для случая генериков происходило боксирование, а потом они добавили этот мощный инструмент, который превращает генерики в runtime ad-hoc для случая небоксированных типов.
K>1) Про ситуацию с бетой CLR 2 я тут уже писал.
А что писал?
K>2) В том и дело, что не рантайм, а компайл-тайм.
Имел ввиду сервисы кодогенерации, работающие в рантайм... Будем считать это техникой исполнения параметрического полиморфизма в отсутствии боксирования. Т.е. будем делать вид, что это таки рантайм и подробности реализации. Почему именно так, вроде бы очевидно — я уже упоминал такую банальную банальность как карту памяти данных для случая небоксированных типов.
Здравствуйте, Klapaucius, Вы писали:
K>Вот только имя конструктора — тег в размеченном объединении — это не тип. Точно так же как 3 не тип и "hello" — не тип. Это значение.
Я предыдущее скипнул, бо оно подводит именно к этой фразе.
Я упомянул теорию групп, потому как в Хаскеле присутствует типобезопасное размеченное объединение, для описания св-в которого как раз наиболее подходит теория групп. Поэтому я называю группу сущностей, объединяемых в размеченном объединении, типами (они же — сорта). Имею полное право.
Конкретно в Хаскеле в размеченное объединение можно заворачивать только туплы и ничего кроме туплов. Для вырожденных случаев тупл пустой или состоит из одного элемента. Далее. Имя конструктора размеченного объединения используется так же как синоним значения разметки этого объединения в паттерн-матчинге. Эта дуальность — следствие минималистичности синтаксиса Хаскеля. Один и тот же идентификатор обозначает в разных ситуациях разные сущности: один из конструкторов АлгТД или же символьный алиас метки типа. То бишь выступает идентификатором соответствующего типа упакованного тупла (во втором случае).
V>>, примерно точно так же, как dynamic_cast проверяет токен типа (на который ссылается экземпляр типа через указатель на vtable), а удачная ветка матчинга затем предоставляет доступ к значению упакованного в АлгТД типа.
K>Еще раз, типы в АлгТД не упаковываются. Вы путаете сабтайпинг и АлгТД. Они не родственники и даже не однофамильцы.
Еще раз — курить Алгебраические типы и размеченные объединения. Похоже, та специфика минималистичности Хаскеля, что одновременно с объявлением АлгТД объявляется (вводится) мн-во оборачиваемых им типов-туплов, совершенно сбивает тебя с толку. И я догадываюсь — почему. Наверно от того, что в Хаскеле нет возможности дать символьный алиас типу некоего тупла для других остальных сценариев. Ну это проблемы Хаскеля право, а не сути вещей или системы типов. Дело в том, что размеченное объединение — это объединение уникальных типов. Даже если оборачиваемые типы имеют одинаковую структуру, их необходимо рассматривать как уникальные типы. Прямо как в С++, когда два разных класса имеют одинаковую структуру — они всё-равно являются разными типами. В этом плане boost::variant, например, не дотягивает до полноценных размеченных объединений, т.к. не в состоянии отличить при матчинге одинаковые типы, которые входят в variant.
K>>>Какая разница, боксированы значения или нет, если речь идет о типах? V>>Разница в том, что боксированный рекурсивный тип представляет из себя в памяти список (в простейшем случае, в непростейшем — дерево) из однородных узлов, а в небоксированном случае значение типа должно располагаться в памяти сплошным куском. Поэтому для первого случая достаточно одной реализации на каждый узел, а во втором потребуется столько различного кода, сколько типов было использовано во время компиляции. Поэтому в первом случае — в случае интерпретации структуры типа в рантайм, этот список/дерево может быть сколь угодно большой длины/глубины, то бишь мощность рекурсивного типа может быть сколь угодно большой.
K>Еще раз повторяю, если речь идет о типах. Типы в памяти ничего из себя не представляют (в рантайме). Их просто нет. А для значений да, все это верно (с оговорками). Вот только никакой интерпретации структуры типов в рантайме тут нет.
Такие вещи придется обосновать, бо любая модель типов предполагает некоторую технику реализации. Представь плиз достаточную модель реализации для того, чтобы приведенный параметрический код scalarProduct не порождал бесконечное мн-во различных инстанциирований для произвольной мощности рекурсивного типа.
K>Ну правильно. Я тут так и писал. Код используется повторно ценой боксинга, такую цену за повторное использование сгенерированного кода C++ платить не может. Но мне справедливо напомнили, что я увлекаюсь деталями реализации. Ну а теперь вы увлекаетесь. Разница-то в системе типов.
Вот те раз? А Дед Мороз тоже существует?
Бесплатно что-ле всё? Я не спорил с работоспособностью примера, а лишь указал, какую цену мы за это платим.
ИМХО, система типов — это не цель ни разу, а ср-во. Инструмент. Инструмент должен непротиворечиво работать. Программист обязан знать, как работает инструмент и сколько он за него платит, остальное — лирика. Я всего-навсего утверждаю, что для общего случая для самой возможности оперировать рекурсивными типами произвольной мощности (неизвестной в compile-time) требуется обязательное боксирование значений из-за необходимости сохранения однородной разметки в памяти. Это если мы все еще говорим о статической (предварительной) компиляции, а не динамической в run-time, как для случая дотнета и тамошних value-type параметров генериков.
Всё-таки дотнет более чем убедительно показал, что статическая типизация и статическая компиляция — вовсе не одно и то же. А ты неаккуратно используешь одно вместо другого, почему я и напомнил, что для Хаскеля вовсю используется сплошная динамическая типизация во время операций распаковки значений АлгТД через ПМ. Т.е. чуть ли не в каждой второй строчке среднестатистической программы.
K>И в гипотетической реализации C++ где скомпилированный код повторно используется (это только для частных случаев можно будет сделать) и все забоксено — этот код или не будет работать все равно, или это будет не C++. Речь то о системе типов и семантике языка.
Ну... если бы не куча прочитанных недостойных ярлыков здесь, я бы и не влез. Назвать систему шаблонов С++ макросами на стероидах можно только при очень поверхностном знакомстве. Ведь в приведенном примере работоспособность только от того, что два списка формируются параллельно:
main' n i as bs = main' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
а не от того, что полностью поддерживается параметрический полиморфизм в том смысле, как нам пытаются показать в примере. Сформируй два списка одинаковой длины независимо и попробуй подать на scalarProduct. Увидишь, что это такие же макросы на стероидах (С) как и в С++.
Ну и в С++ вызов ф-ии — это ф-ии времени компиляции, а в Хаскеле — это обращение к группе ф-ий и выбор конкретной через паттерн-матчинг:
main' 0 _ as bs = scalarProduct as bs
main' n i as bs = main' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
В сухом остатке: хотя имеем статическую типизацию (проверку соответствий типов), конкретные типы проверяются в рантайм, конкретные ф-ии тоже выбираются в рантайм и только затем вызываются. Куда ни плюнь — сплошная динамика... В общем, чудес, увы, не бывает.
В общем, если для С++ боксирование проэмулировать, то работать будет, но потеряем compile-time проверку одинаковых по длине списков. Проблема в С++ не в самой системе типов ни разу (если обсуждать приведенный пример), это ошибочное мнение. Проблема как раз в низлежащей технике реализации, т.е. в предоставляемых возможностях помимо системы типов. Получившая система типов — это следствие, а не причина.
Попробую пояснить.
Единственным способом рантайм-полиморизма для С++ является диспетчеризация на таблице виртуальных ф-ий. Так вот, в случае боксирования будет возможна техника, аналогичная АлгТД, только вместо тега размеченного объединения может быть использована vtable. Итого детали реализации меняются — суть нет, т.е. поведение системы типов для рассматриваемого сценария могли быть быть идентичными, останется только реализовать открывшиеся возможности аналогичного ПП... Так вот, эта гипотетическая реализация была бы НЕПРОТИВОРЕЧИВОЙ, тогда и только тогда, когда в С++ не было такой вещи, как прямой доступ к полям типов. Именно в момент этого доступа возникает противоречие с придуманной системой типов для нашего нового гипотетического С++ с ПП. Для сравнения, Хаскель сначала требует через ПМ распаковать содержимое АлгТД, т.е. уменьшить мощность (или "логическую косвенность") для нашего сценария рекурсивного типа, т.е. позволяет оперировать непосредственными полями только на одном уровне. Но если надо продвинуться дальше — опять требуется распаковка содержимого и т.д. пока не Nil. Итого, любая операция над АлгТД в Хаскеле сопровождается обслуживанием полиморфизма. Аналог в технике С++ требовал бы обращение к любым полям через акцессоры — виртуальные ф-ии, которые вполне мог бы генерить компилятор, автоматически превращая поля в пару акцессоров (почему так? вспомни про предложение нарисовать карту памяти для небоксированного сценария)... Но сие малость неэфективно, если каждый чих будет полиморфным, а ведь С++ претендует на нишу самой эффективной на сегодня работы с памятью. Поэтому аппарат типов он использует лишь как ср-во разметки этой памяти для своих нужд. Поэтому-то в С+ run-time полиморфизм скорее опциональный инструмент, чем целевой. Вот и получается, что система типов, помимо своего класса принадлежности в теории типов, так же ограничивается желаемыми характеристиками языка. Хотим неполиморфно обращаться к памяти — закрыли себе часть св-в типов by design. Ругать получившуюся систему типов можно будет затем лишь от непонимания причинно-следственных связей характеристик языка и подходящей под эти характеристики системы типов.
Единственно что мы можем сделать, это встроить в С++ полный аналог АлгТД из Хаскеля, и тогда на такой разновидности полиморфизма можно будет делать аналогичные фокусы. Вроде как-то уже обсуждалось, что это было бы возможно и интероперабельно с другими типами С++, т.е. такие типы можно было бы вкладывать в другие типы, а те, в свою очередь, в АлгТД. Ну и платить ровно такую же цену в итоге.
K>Ну так он и в хаскеле знает смещения и расположения в памяти. Теги "типов" как в динамике он в рантайме не проверяет.
Он знает только после распаковки содержимого и только в области видимости соответствующей ветки ПМ. Чтобы распаковать полученное рекурсивное поле — надо опять делать ему ПМ и так до бесконечности в цикле (см код ф-ии scalarProduct). А ПМ как раз проверяет теги типов, т.е. это банальная рантайм-проверка типов.
V>>Мн-во допустимых типов значений АлгТД проверяется статически, конкретный тип из множества — динамически. Какие проблемы-то?
K>Ну так динамически проверяются значения, а не типы. точно так же, как и в C++. В чем динамическая типизация-то? K>Вот a == 3 — проверка динамическая, а типизация — сатическая, потому, что и код сравнения и структура для хранения 3 в памяти известна на этапе компиляции. Вы как-то систематически типы и значения путаете.
Не путаю. Дескриминатор-то типа проверяется в рантайм. Получаем двухтактную схему — сначала проверка тега типа и выбор ветки алгоритма для этого запакованного типа, затем операция a == 3, где a — переменная шаблона ПМ. Ты ведь привел простой тип, а мы обсуждали алгебраический.
V>>Наверно криво выразился. Имел ввиду, что в случае боксированной реалиации рекурсивных типов порожденному компилятором коду для его работоспособности достаточно знать об устройстве лишь части значения, а не всего значения целиком. Доступ к остальной части значения происходит рекурсивно через паттерн-матчинг, т.е. через рантайм-проверку, ну т.е. через динамику.
K>Для самой рекурсивной структуры данных — да. Точно так же, как и в C++.
Нет, я уже выше написал. В С++ можно обратиться к полям сколь угодно вложенной низлежащей базы напрямую без рекурсии-распаковки. В этом и есть отличие характеристик языков, где все остальные отличия лишь следствие. Поэтому-то для С++ требуется иметь соотв. код и заведомо известную разметку в памяти для всех используемых в программе воплощений шаблонного типа, чтобы обращаться к памяти напрямую, без рекурсивной динамической типизации.
V>>Неверно. Дискриминатор АлгТД присутствует в рантайм будучи сохраненным по адресу значения, и этот дискриминатор проверяется исключительно в рантайм. Доступ к запакованному в АлгТД значению предоставляется затем исключительно через ПМ (фишка Хаскеля — у него других способов и нет), т.е. делает невозможным неверную интерпретацию памяти, в которой находится значение АлгТД. В этом и заключается типобезопасность, которая таки для случая Хаскеля динамическая, когда идет оперирование АлгТД.
K>Ну да, безопасность размеченных объединений по сравнению с неразмеченными достигается рантайм проверкой. Точно так же как и с доступом по индексу в массиве, например. Но какое это отношение имеет к типизации? В C++ аналогично и то и другое либо опасно, либо безовасно за счет рантайм проверки. Но тип у массива любой длины — одинаковый. И у любого конструктора АлгТД — одинаковый. Ну так при чем тут динамическая типизация-то?
Для случая рекурсивных типов — причем. Для С++ была предложена для сравнения техника, когда рекурсивный тип должен был быть воплощен без боксирования. А в этом случае в С++ никакой динамической типизации для доступа к след. уровню рекурсии не требовалось. Вот и выходит, что с одной стороны это эффективней, с другой стороны — ограничение.
У Хаскеля тоже своё ограничение: боксирование — это жутко неэффективная техника на классических архитектурах с плоской (неасоциативной) памятью. Я по характеру своей работы стараюсь избавляться от лишней косвенности, досигая порой прирост вдвое-четверо лишь только за счет вдвое меньшей коссвености. А тут буквально всё косвенно нафик...
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>>>Я тут уже указывал способ определить не верхнюю границу, а точное число типов для тизируемого кода на C++ S>>Остается выяснить, что же мешает превзойти заранее заданное точное число типов.
K>Мешает вид полиморфизма в C++
Не мешает. В компайл-тайме можно применить еще один Succ<T> и превзойти любую оценку, данную заранее.
S>>Но мы обсуждаем ПП, фичу языка, а не конкретную программу. Причем тут конкретная программа — непонятно.
K>При том, что фича языка позволяет писать определенный класс конкретных программ. А ее отсутствие — не позволяет.
Фича ПП позволяет ровно то, что написано в определении. А использовать ПП на бесконечном количестве типов — это уже другая фича.
K>Рассуждать про n программ, которые в сумме что-то реализуют — это софистика. Ну, как говорить, что (int,+,*) это не кольцо вычетов, а кольцо целых чисел, складывая модули кольца вычетов во всех возможных программах.
Угу, потому как нигде не написано что бесконечность типов при использовании ПП должна достигаться в одной программе.
K>>>Из определения парам. полиморфизма следует, что у нас есть, по крайней мере, один мономорфный тип () и один полиморфный forall a. Succ a с этим вы согласны? S>>Нет. Из определения ПП следует лишь то, что такие типы полиморфным кодом должны обрабатываться единым образом.
K>Вы берете удобную для себя половину определения, которое я тут
привел полностью.
По ссылке вы во второй части привели не часть определения, а описание формы ПП (согласно TAPL).
K>Одна только однородность кода не является каким-то характеристическим признаком параметрического полиморфизма. Сабтайпинг, например, тоже позволяет писать однородный код. Собственно, компилятор Java и переписывает однородный код на языке с параметрическим полиморфизмом в однородный код с использованием сабтайпинга на языке, в котором ПП нет вовсе.
Что и куда переписывается и чем исполняется — не суть важно. ПП в Java имеется. А вот
template <class T> struct Succ{};
Вполне удовлетворяет и "вторую часть" вашего определения по ссылке.
S>> А то что они должны существовать, да еще и в какой-то конкретной программе, да еще и потенциальной бесконечностью — увы, не следует.
K>А неудобную для вас вторую часть определения, о том, как однородный код типизируется вовсе отбрасываете. Из второй части определения следует, что они должны существовать.
Это не вторая часть определения, это форма. Бывает еще и предикативный ПП. Но из нее опять не следует бесконечность, а следует лишь возможность подстановки. K>В C++ они, разумеется, не существуют. Шаблонный код на C++ вообще не может быть типизирован (отсюда и вся мощность шаблонов) и не типизируется. В некоторых реализациях вроде gcc проверяются кайнды (* или Nat), в некоторых, вроде, макрософтовской — даже кайнды не проверяются (ну или не проверялись раньше). Типизируется только инстанциация, результат применения. В языках с ПП типизируется и однородный код и само применение.
Мало что понял, но в C++ ПП импредикативный.
S>>что мешает сконструитьвать еще один тип в программе на C++? Разумеется, явно в коде, или в компайл-тайме.
K>Отсутствие в системе типов C++ типа forall a. Succ a.
Не мешает. Обычный такой
template <class T> struct Succ{};
Позволяет написать еще один тип.
Ну и конечно же, в системе типов C# тоже нет типа forall a. Succ a.
S>>Если обсуждаются примеры из http://migmit.livejournal.com/32688.html, то С# там их применяет в рантайме.
K>Покажите применение в рантайме из того кода.
см. _main<A>
S>>Тогда вопрос. Откуда C# в компайл тайм знает что надо применить как минимум 1000 типов? Для 1000 код работает, мог бы работать и для большего числа, если бы не StackOverflow.
K>Еще раз, параметрический код не требует для проверки знания числа типов. Ну, как типизация функции + над int не требует построения и типизации таблицы констант для всего декартова произведения двух множеств значений int.
Это не ответ. Ответ здесь в том, что С# конкретизирует такие типы в рантайме (точнее, в момент JIT компиляции, которую он вызовет для каждой конкретизации _main<A> по мере востребования). C++ так не умеет, потому ему нужно конкретизировать все в момент компиляции.
Здравствуйте, samius, Вы писали:
K>>Мешает вид полиморфизма в C++ S>Не мешает. В компайл-тайме можно применить еще один Succ<T> и превзойти любую оценку, данную заранее.
Вот вы говорите можно, а из обсуждаемого здесь опыта известно, что нельзя. Как же так?
S>Фича ПП позволяет ровно то, что написано в определении. А использовать ПП на бесконечном количестве типов — это уже другая фича.
Приведенное мной док-во того, что вторая фича следует из первой вы, разумеется, игнорируете. Как же так?
S>Угу, потому как нигде не написано что бесконечность типов при использовании ПП должна достигаться в одной программе.
Т.е. и 32-битный int — это тоже самое настоящее целое число?
S>По ссылке вы во второй части привели не часть определения, а описание формы ПП (согласно TAPL).
Что за описание формы? В первой части требование к коду, во второй к типам. Друг без друга они смысла не имеют.
S>Что и куда переписывается и чем исполняется — не суть важно. ПП в Java имеется. А вот S>
S>template <class T> struct Succ{};
S>
S>Вполне удовлетворяет и "вторую часть" вашего определения по ссылке.
Нет, это не описание типа и не код, который можно типизировать в рамках системы типов C++. Это шаблон порождающий код, который только после этого будет типизирован.
K>>В C++ они, разумеется, не существуют. Шаблонный код на C++ вообще не может быть типизирован (отсюда и вся мощность шаблонов) и не типизируется. В некоторых реализациях вроде gcc проверяются кайнды (* или Nat), в некоторых, вроде, макрософтовской — даже кайнды не проверяются (ну или не проверялись раньше). Типизируется только инстанциация, результат применения. В языках с ПП типизируется и однородный код и само применение. S>Мало что понял, но в C++ ПП импредикативный.
Если что-то непонятно, задайте конкретные вопросы. Импредикативность тут вообще не обсуждвется.
S>Позволяет написать еще один тип.
Вот и MigMit думал, что позволяет. Попробовал — не позволило. А вы в упор смотрите на пример где не позволяет и говорите "позволяет!".
S>Ну и конечно же, в системе типов C# тоже нет типа forall a. Succ a.
В системе типов C# 2 и выше — есть.
S>>>Если обсуждаются примеры из http://migmit.livejournal.com/32688.html, то С# там их применяет в рантайме.
K>>Покажите применение в рантайме из того кода. S>см. _main<A>
Смотрим:
public static int _main<A>(int n, int i, A first, A second) where A : ScalarProduct<A> {
if (n == 0) {
return first.scalarProduct(second);
} else {
return _main(n-1, i+1, new Cons<A>(2*i+1,first), new Cons<A>(i*i, second));
}
}
Видим 0 (ноль) применений в рантайме. Зачем же вы обманываете?
S>Это не ответ. Ответ здесь в том, что С# конкретизирует такие типы в рантайме
Это неверный ответ.
S> (точнее, в момент JIT компиляции, которую он вызовет для каждой конкретизации _main<A> по мере востребования).
JIT-компилятора для C# не существует в природе. Допустим, что мы обсуждаем JIT-компиляцию CIL. Вы утверждаете, что в link-time, ngen-ом этот код полностью не скомпилировать?
И как быть с Java у которой в байт-коде нет ПП и, следовательно, нет смысла о JIT говорить? Как быть с хаскелем?
Главный вопрос: как мы получаем ошибку компиляции C# за долго до того, как полиморфный код якобы скомпилируется в рантайме.
S>C++ так не умеет, потому ему нужно конкретизировать все в момент компиляции.
Нужно конкретизировать все до момента тайпчека, вы хотели сказать?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
K>>2) В том и дело, что не рантайм, а компайл-тайм.
V>Имел ввиду сервисы кодогенерации, работающие в рантайм... Будем считать это техникой исполнения параметрического полиморфизма в отсутствии боксирования. Т.е. будем делать вид, что это таки рантайм и подробности реализации. Почему именно так, вроде бы очевидно — я уже упоминал такую банальную банальность как карту памяти данных для случая небоксированных типов.
Почему рантайм-то? Берете ngen и компилируете в link-time. Точно так же и Хаскель, если может сгенерировать специализированный код при межмодульном анализе — специализирует и даже от боксинга избавится, где это возможно.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, vdimas, Вы писали:
K>>Вот только имя конструктора — тег в размеченном объединении — это не тип. Точно так же как 3 не тип и "hello" — не тип. Это значение.
V>Я упомянул теорию групп, потому как в Хаскеле присутствует типобезопасное размеченное объединение, для описания св-в которого как раз наиболее подходит теория групп. Поэтому я называю группу сущностей, объединяемых в размеченном объединении, типами (они же — сорта). Имею полное право.
Называть то вы можете, но это так экстравагантно, что все остальные вас имеют право не понять.
V>Конкретно в Хаскеле в размеченное объединение можно заворачивать только туплы и ничего кроме туплов. Для вырожденных случаев тупл пустой или состоит из одного элемента. Далее. Имя конструктора размеченного объединения используется так же как синоним значения разметки этого объединения в паттерн-матчинге. Эта дуальность — следствие минималистичности синтаксиса Хаскеля. Один и тот же идентификатор обозначает в разных ситуациях разные сущности: один из конструкторов АлгТД или же символьный алиас метки типа. То бишь выступает идентификатором соответствующего типа упакованного тупла (во втором случае).
Это разные идентификаторы. Они в разных пространствах имен и могут поэтому совпадать.
V>>>, примерно точно так же, как dynamic_cast проверяет токен типа (на который ссылается экземпляр типа через указатель на vtable), а удачная ветка матчинга затем предоставляет доступ к значению упакованного в АлгТД типа.
K>>Еще раз, типы в АлгТД не упаковываются. Вы путаете сабтайпинг и АлгТД. Они не родственники и даже не однофамильцы.
V>Еще раз — курить Алгебраические типы
Курим:
Алгебраический тип данных — в теории программирования любой тип, значения которого являются значениями некоторых иных типов, «обёрнутыми» конструкторами алгебраического типа.
Т.е. все как я и сказал. Никакие типы в АлгТД не упаковываются. Упаковываются значения.
Ну, может и можно сказать, что конструктор типа "упаковывает" типа по аналогии с тем, как конструктор "упаковывает" значение. Вот только конструкторы типов в рантайме не матчатся (в рантайме их нет). А "матчатся" в компайл-тайме инстансами классов типов и семействами типов.
V>Похоже, та специфика минималистичности Хаскеля, что одновременно с объявлением АлгТД объявляется (вводится) мн-во оборачиваемых им типов-туплов, совершенно сбивает тебя с толку.
Не знаю, что сбивает с толку вас, но вы последовательно путаете суммы в которых между "слагаемыми" и суммой нет отношения подтипирования и объединения в которых такие отношения есть. Поэтому, если про сабтайпинг через наследование, например, еще можно сказать, что какие-то типы в рантайме проверяются, то про суммы — алгебраические типы — такое нельзя сказать даже с натяжкой.
V>И я догадываюсь — почему. Наверно от того, что в Хаскеле нет возможности дать символьный алиас типу некоего тупла для других остальных сценариев. Ну это проблемы Хаскеля право, а не сути вещей или системы типов. Дело в том, что размеченное объединение — это объединение уникальных типов. Даже если оборачиваемые типы имеют одинаковую структуру, их необходимо рассматривать как уникальные типы. Прямо как в С++, когда два разных класса имеют одинаковую структуру — они всё-равно являются разными типами. В этом плане boost::variant, например, не дотягивает до полноценных размеченных объединений, т.к. не в состоянии отличить при матчинге одинаковые типы, которые входят в variant.
Ну так boost::variant это не сумма, а объединение — чего вы от него хотите. variant< int, bool > это int или bool. С суммами все иначе: Either Int Bool не является ни Int ни Bool.
Поэтому variant не "недотягивает" до АлгТД — это вовсем другая штука со своими достоинствами и недостатками и слабопересекающейся с АлгТД областью применения.
Одно непонятно: зачем так фиксироваться на суммах? В описываемом примере они вовсе не используются. Тайплевел вычисления делаются на Nil и Cons, первый — единица с тегом, второй — произведение с тегом.
K>>Еще раз повторяю, если речь идет о типах. Типы в памяти ничего из себя не представляют (в рантайме). Их просто нет. А для значений да, все это верно (с оговорками). Вот только никакой интерпретации структуры типов в рантайме тут нет. V>Такие вещи придется обосновать, бо любая модель типов предполагает некоторую технику реализации.
Реализация (в компиляторе) требуется. место в памяти в рантайме — нет. В рантайме нет типов.
V>Представь плиз достаточную модель реализации для того, чтобы приведенный параметрический код scalarProduct не порождал бесконечное мн-во различных инстанциирований для произвольной мощности рекурсивного типа.
Да ни в одной существующей реализации ПП нет никакого "бесконечного инстанцирования". Параметрический тип — не шаблон. Код-то один для всей (потенциальной) бесконечности типов. Всякие исключения с оптимизацией — не рассматриваем. Потому я и говорю, что для реализации ПП реализация шаблонов не подходит. И наоборот: шаблоны таким способом не реализовать в общем случае. Ничего удивительного в этом нет, потому что шаблоны и ПП вещи разные. Это и их типизации касается, и семантики компайл-тайм вычислений на них, и реализации.
K>>Ну правильно. Я тут так и писал. Код используется повторно ценой боксинга, такую цену за повторное использование сгенерированного кода C++ платить не может. Но мне справедливо напомнили, что я увлекаюсь деталями реализации. Ну а теперь вы увлекаетесь. Разница-то в системе типов.
V>Бесплатно что-ле всё? Я не спорил с работоспособностью примера, а лишь указал, какую цену мы за это платим.
Ну так я прямо говорю, что не бесплатно. Вы мои ответы вообще читаете? см. выделеное.
V>ИМХО, система типов — это не цель ни разу, а ср-во. Инструмент. Инструмент должен непротиворечиво работать. Программист обязан знать, как работает инструмент и сколько он за него платит, остальное — лирика. Я всего-навсего утверждаю, что для общего случая для самой возможности оперировать рекурсивными типами произвольной мощности (неизвестной в compile-time) требуется обязательное боксирование значений из-за необходимости сохранения однородной разметки в памяти.
Отлично. Т.е. вы объясняете мне то, что я сам использовал как главный аргумент против того, что шаблоны — параметрический полиморфизм, пока samius не одернул меня, указав что я концентрируюсь на реализации.
V>Всё-таки дотнет более чем убедительно показал, что статическая типизация и статическая компиляция — вовсе не одно и то же.
Я разве с этим спорю?
V>А ты неаккуратно используешь одно вместо другого, почему я и напомнил, что для Хаскеля вовсю используется сплошная динамическая типизация во время операций распаковки значений АлгТД через ПМ. Т.е. чуть ли не в каждой второй строчке среднестатистической программы.
Еще раз. Динамической типизации тут нет. a == 3 динамическая типизация? Нет, 3 не тип.
V> Ведь в приведенном примере работоспособность только от того, что два списка формируются параллельно: V>main' n i as bs = main' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
Совершенно верно. Это масимум того, что можно получить от ML-like полиморфизма. Для большего нужны зависимые типы.
V>а не от того, что полностью поддерживается параметрический полиморфизм в том смысле, как нам пытаются показать в примере.
От того. Без поддержки ПП это не заработает.
V>Ну и в С++ вызов ф-ии — это ф-ии времени компиляции, а в Хаскеле — это обращение к группе ф-ий и выбор конкретной через паттерн-матчинг: V>main' 0 _ as bs = scalarProduct as bs V>main' n i as bs = main' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
Это не верно. Никакой группы функций нет. Вот так:
test' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
test' n i as bs | n == 0 = scalarProduct as bs
| otherwise = test' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
тоже сработает. Классы типов тут тоже ничего в рантайме не диспетчеризуют (для ранг-2 полиморфизма будут, но это здесь не используется).
V>В сухом остатке: хотя имеем статическую типизацию (проверку соответствий типов), конкретные типы проверяются в рантайм,
Не верно, все типы проверяются в компайл-тайм. Иначе как ошибка появилась бы при компиляции?
V> конкретные ф-ии тоже выбираются в рантайм
Не верно. Все функции выбираются в компайл-тайм.
V> и только затем вызываются.
Вызываются и впрямь в рантайме.
V>Куда ни плюнь — сплошная динамика... В общем, чудес, увы, не бывает.
Ага вся динамика n == 0. Но 0 не тип, так что эта динамика — не динамическая типизация. scalarProduct тоже ничего в динамике не проверяет. Cons не сумма, а произведение, так что значения просто выбираются по известным смещениям. Выбор между двумя видами scalarProduct происходит на этапе компиляции, классы типов без расширений в рантайме не работают.
V>В общем, если для С++ боксирование проэмулировать, то работать будет, но потеряем compile-time проверку одинаковых по длине списков.
Но весь смысл этого куска кода в compile-time проверке!
V>Проблема в С++ не в самой системе типов ни разу (если обсуждать приведенный пример), это ошибочное мнение. Проблема как раз в низлежащей технике реализации, т.е. в предоставляемых возможностях помимо системы типов. Получившая система типов — это следствие, а не причина.
Ну да. Я так и говорил: C++ не может себе позволить параметрический полиморфизм.
V>Попробую пояснить.
V>Единственным способом рантайм-полиморизма для С++ является диспетчеризация на таблице виртуальных ф-ий.
А в хаскеле без расширений, на котором код и написан рантайм-полиморфизма вообще нет.
V>Для сравнения, Хаскель сначала требует через ПМ распаковать содержимое АлгТД, т.е. уменьшить мощность (или "логическую косвенность") для нашего сценария рекурсивного типа
Сюрприз: в обсуждаемом коде на хаскеле 0 (ноль) рекурсивных типов.
V>, т.е. позволяет оперировать непосредственными полями только на одном уровне. Но если надо продвинуться дальше — опять требуется распаковка содержимого и т.д. пока не Nil. Итого, любая операция над АлгТД в Хаскеле сопровождается обслуживанием полиморфизма.
Вы опять рассуждаете так, как будто у нас сумма, да еще и рекурсивная:
data List = Cons Integer (List a) | Nil
а в коде-то — смотрите-ка:
data Nil = Nil
data Cons a = Cons Integer a
Никаких рекурсивных типов. Никаких сумм.
V>Аналог в технике С++ требовал бы обращение к любым полям через акцессоры — виртуальные ф-ии, которые вполне мог бы генерить компилятор, автоматически превращая поля в пару акцессоров (почему так? вспомни про предложение нарисовать карту памяти для небоксированного сценария)... Но сие малость неэфективно, если каждый чих будет полиморфным, а ведь С++ претендует на нишу самой эффективной на сегодня работы с памятью.
Вывод вы делаете правильный, а посылка — неверная. В случае хаскелевой реализации эти "аксессоры" не виртуальные, без рантайм диспетчеризации для произведений и с рантайм диспетчерезацией для сумм. Т.е проверки не везде — как в наследовании, а только там, где действительно по смыслу программы придется писать if, хоть в C++, хоть где. Но боксинг и лишняя косвенность и так дорого обходится, C++ такого позволить себе не может.
V>Единственно что мы можем сделать, это встроить в С++ полный аналог АлгТД из Хаскеля
Для описываемого фокуса вообще не нужен полный аналог АлгТД. Это тест на параметрический полиморфизм, а не на полность аналогии АлгТД,
V>Он знает только после распаковки содержимого и только в области видимости соответствующей ветки ПМ.
В подавляющем большинстве случаев у ПМ 1 (одна) ветка и все смещения нам уже извстны.
V>Чтобы распаковать полученное рекурсивное поле — надо опять делать ему ПМ и так до бесконечности в цикле (см код ф-ии scalarProduct). А ПМ как раз проверяет теги типов, т.е. это банальная рантайм-проверка типов.
Хаскель — не лисп. Тегов типов в нем нет. В обсуждаемом коде и теги конструкторов не проверяются — проверяется число на равенство 0.
V>Не путаю. Дескриминатор-то типа проверяется в рантайм.
Дескриминаторов типа в Хаскеле просто не существует (если вы не сделаете для типа инстансы Data/Typeable, обеспечив RTTI и не используете Data.Dynamic для всего того, что вы сейчас рассказываете. В обсуждаемом примере это не используется)
V>Получаем двухтактную схему — сначала проверка тега типа
Да не типа, а значения.
V>и выбор ветки алгоритма для этого запакованного типа, затем операция a == 3, где a — переменная шаблона ПМ. Ты ведь привел простой тип, а мы обсуждали алгебраический.
Integer — алгебраический тип (и по стандарту, а 3 — его конструктор) и в реализации. Не забудьте еще алгебраический тип Bool. True и False, кстати, тоже типы по-вашему?
V>Нет, я уже выше написал. В С++ можно обратиться к полям сколь угодно вложенной низлежащей базы напрямую без рекурсии-распаковки. В этом и есть отличие характеристик языков, где все остальные отличия лишь следствие. Поэтому-то для С++ требуется иметь соотв. код и заведомо известную разметку в памяти для всех используемых в программе воплощений шаблонного типа, чтобы обращаться к памяти напрямую, без рекурсивной динамической типизации.
Да. Там где проверять не нужно — там вы не будете проверять ни в хаскеле, ни в C++. Там где без этого не обойтись (например, это указатель на следующий элемент списка или на null) — вы будете проверять в рантайме и там и там.
V>У Хаскеля тоже своё ограничение: боксирование — это жутко неэффективная техника на классических архитектурах с плоской (неасоциативной) памятью. Я по характеру своей работы стараюсь избавляться от лишней косвенности, досигая порой прирост вдвое-четверо лишь только за счет вдвое меньшей коссвености. А тут буквально всё косвенно нафик...
О чем я и говорю — параметрический полиморфизм C++ не по карману.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>>>Мешает вид полиморфизма в C++ S>>Не мешает. В компайл-тайме можно применить еще один Succ<T> и превзойти любую оценку, данную заранее.
K>Вот вы говорите можно, а из обсуждаемого здесь опыта известно, что нельзя. Как же так?
Здесь обсуждается другой опыт. А опыт написать Succ<Succ<Succ<...>>> сколько ума хватит раз не обсуждается.
S>>Фича ПП позволяет ровно то, что написано в определении. А использовать ПП на бесконечном количестве типов — это уже другая фича.
K>Приведенное мной док-во того, что вторая фича следует из первой вы, разумеется, игнорируете. Как же так?
Вторая фича тут
— это импредикативность, верно? Вы доказывали что-то другое, причем я ответил по этому доказательству.
S>>Угу, потому как нигде не написано что бесконечность типов при использовании ПП должна достигаться в одной программе.
K>Т.е. и 32-битный int — это тоже самое настоящее целое число?
Нет. Я же согласился что это софистика.
S>>По ссылке вы во второй части привели не часть определения, а описание формы ПП (согласно TAPL).
K>Что за описание формы? В первой части требование к коду, во второй к типам. Друг без друга они смысла не имеют.
Предикативный и импредикативный — это формы, а не определение ПП.
S>>Вполне удовлетворяет и "вторую часть" вашего определения по ссылке.
K>Нет, это не описание типа и не код, который можно типизировать в рамках системы типов C++. Это шаблон порождающий код, который только после этого будет типизирован.
Succ<Succ<int> > v;
Так пойдет?
K>Если что-то непонятно, задайте конкретные вопросы. Импредикативность тут вообще не обсуждвется.
Вы дали ссылку и по ссылке вторым пунктом импредикативность. Что же вы обсуждаете, если не импредикативность?
S>>Позволяет написать еще один тип.
K>Вот и MigMit думал, что позволяет. Попробовал — не позволило. А вы в упор смотрите на пример где не позволяет и говорите "позволяет!".
У него не получилось кое-что другое, о чем я не говорю. А вот написать наперед заданное число раз Cons<Cons<... он не попробовал. А это помогло бы
S>>Ну и конечно же, в системе типов C# тоже нет типа forall a. Succ a.
K>В системе типов C# 2 и выше — есть.
Аналога explicit forall в C# нет. Если бы был, можно было бы описать абстракцию функтора/монады и не пришлось бы жарить уток как в Query expression pattern.
S>>см. _main<A> K>Смотрим: K>
K> public static int _main<A>(int n, int i, A first, A second) where A : ScalarProduct<A> {
K> if (n == 0) {
K> return first.scalarProduct(second);
K> } else {
K> return _main(n-1, i+1, new Cons<A>(2*i+1,first), new Cons<A>(i*i, second));
K> }
K> }
K>
K>Видим 0 (ноль) применений в рантайме. Зачем же вы обманываете?
Мало ли что мы тут видим. Типы применяются во время выполнения, для каждого рекурсивного вызова _main выполняется докомпиляция JIT-ом.
S>>Это не ответ. Ответ здесь в том, что С# конкретизирует такие типы в рантайме
K>Это неверный ответ.
Аргументы?
S>> (точнее, в момент JIT компиляции, которую он вызовет для каждой конкретизации _main<A> по мере востребования).
K>JIT-компилятора для C# не существует в природе. Допустим, что мы обсуждаем JIT-компиляцию CIL. Вы утверждаете, что в link-time, ngen-ом этот код полностью не скомпилировать?
Не так. в один проход мы его скомпилируем, но по мере необходимости рекурсивных вызовов он будет докомпиляться.
А где-нибудь в сингулярити, где JIT-а нет, такой код работать не будет и мы получим ситуацию в точности как с C++.
K>И как быть с Java у которой в байт-коде нет ПП и, следовательно, нет смысла о JIT говорить? Как быть с хаскелем?
А никак не надо быть, мы ведь обсуждаем ПП ЯВУ а не байт-кода.
K>Главный вопрос: как мы получаем ошибку компиляции C# за долго до того, как полиморфный код якобы скомпилируется в рантайме.
О какой конкретно ошибке речь? Вообще-говоря, С# проверяет ограничения при компиляции в IL и получает гарантии что они не будут нарушены в рантайме. Если такая проверка обламывается, мы получаем ошибку времени компиляции в IL. Если нет — то в JIT все будет как по рельсам.
S>>C++ так не умеет, потому ему нужно конкретизировать все в момент компиляции.
K>Нужно конкретизировать все до момента тайпчека, вы хотели сказать?
наверное
Здравствуйте, samius, Вы писали:
K>>Вот вы говорите можно, а из обсуждаемого здесь опыта известно, что нельзя. Как же так? S>Здесь обсуждается другой опыт. А опыт написать Succ<Succ<Succ<...>>> сколько ума хватит раз не обсуждается.
Да не написать Succ<Succ<Succ<...>>> а получить еще один из готового. Как тут:
instance ScalarProduct a => ScalarProduct (Cons a)
Нет, не верно.
S>Вы доказывали что-то другое, причем я ответил по этому доказательству.
Вы по доказательству ответили, что оно не считается и все тут.
K>>Т.е. и 32-битный int — это тоже самое настоящее целое число? S>Нет. Я же согласился что это софистика.
И чем, по-вашему, это обоснование через "все возможные" программы отличается от вашего?
S>Предикативный и импредикативный — это формы, а не определение ПП.
Точно. И что? Импредикативного никто и не требует.
S>Вы дали ссылку и по ссылке вторым пунктом импредикативность. Что же вы обсуждаете, если не импредикативность?
Ок. Выкинте упоминание об импредикативности — оно там "для справки".
1) Возможность написать однородный (работающий одинаково для любого типа) код
2) Типизировать этот код обобщенным типом, содержащим переменные, которые конкретизируются применением обобщенного типа к конкретному.
Типизировать шаблонный код в C++ нельзя вообще никак.
S>У него не получилось кое-что другое, о чем я не говорю. А вот написать наперед заданное число раз Cons<Cons<... он не попробовал. А это помогло бы
Нда. Кто decl читал — тот в цирке не смеется.
K>>В системе типов C# 2 и выше — есть. S>Аналога explicit forall в C# нет. Если бы был, можно было бы описать абстракцию функтора/монады и не пришлось бы жарить уток как в Query expression pattern.
1)explicit forall не обязателен для такого простого типа. Он тут для понятности. explicit forall, кстати, в Хаскеле без расширений нет.
2)В C# он наоборот есть — программист просто обязан писать его в сигнатуре:
T foo<T>(Bar<T> a)
что то же самое, что
foo :: forall t. Bar t -> t
В хаскеле же писать его не обязательно, там где он очевиден — это просто сахар.
S>Мало ли что мы тут видим.
Видим все что надо
S>Типы применяются во время выполнения
Нет, не применяются. Как выглядит применение во время выполнения в C# мы уже обсудили.
S>, для каждого рекурсивного вызова _main выполняется докомпиляция JIT-ом.
Зачем его докомпилировать? Это же ссылочные типы, для них никакие специализации не нужны!
S>Аргументы?
Аргументы ниже.
K>>JIT-компилятора для C# не существует в природе. Допустим, что мы обсуждаем JIT-компиляцию CIL. Вы утверждаете, что в link-time, ngen-ом этот код полностью не скомпилировать? S>Не так. в один проход мы его скомпилируем, но по мере необходимости рекурсивных вызовов он будет докомпиляться. S>А где-нибудь в сингулярити, где JIT-а нет, такой код работать не будет
Докажите, что он не будет там работать.
K>>И как быть с Java у которой в байт-коде нет ПП и, следовательно, нет смысла о JIT говорить? Как быть с хаскелем? S>А никак не надо быть, мы ведь обсуждаем ПП ЯВУ а не байт-кода.
Так и я о том же. По вашему параметрические типы должны применятся в рантайме. Поэтому вы и обсуждаете компиляцию байткода в котором ПП есть. И только рантаймовую — возможность линктаймовой компиляции вы полностью игнорируете. В случае с C# лично для меня ситуация с рантаймом понятна, но вокруг нее столько всяких недопониманий и суеверий, что я просто сразу предлагаю: обсуждаем случаи, в которых никаких двусмысленностей нет: в Java и Haskell параметрические типы не могут применятся в рантайме в принципе потому, что в рантайме они не существуют.
K>>Главный вопрос: как мы получаем ошибку компиляции C# за долго до того, как полиморфный код якобы скомпилируется в рантайме. S>О какой конкретно ошибке речь?
Об ошибке, когда код генерирует неравные списки. Она возникает в компайл-тайм.
S>Вообще-говоря, С# проверяет ограничения при компиляции в IL и получает гарантии что они не будут нарушены в рантайме. Если такая проверка обламывается, мы получаем ошибку времени компиляции в IL. Если нет — то в JIT все будет как по рельсам.
Т.е. вы хотите сказать, что система типов второго C# unsound, статически проверить безопасность компилятор не может. Спорить с этим сложно, потому что благодаря таким мегафичам как ковариантность массивов и даункасты — так оно, собственно, и есть. С дженериками C# таких проблем, правда, нет, но мне проще обсудить полиморфизм на примере другого языка, чем вас в этом убедить.
K>>Нужно конкретизировать все до момента тайпчека, вы хотели сказать? S>наверное
Ну, тоесть параметрических типов в C++ нет, а параметрический полиморфизм, по вашему, есть? Оригинально.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Это разные идентификаторы. Они в разных пространствах имен и могут поэтому совпадать.
В каких разных? Продемонстрируй плиз разные пространства имен для конструктора АлгТД и для одноименного идентификатора метки типа в конструкции ПМ некоего значения, созданного с помощью этого конструктора.
K>Алгебраический тип данных — в теории программирования любой тип, значения которого являются значениями некоторых иных типов, «обёрнутыми» конструкторами алгебраического типа.
K>Т.е. все как я и сказал. Никакие типы в АлгТД не упаковываются. Упаковываются значения.
Эээ... колега, а что вообще могло бы означать "упаковка другого типа" для АлгТД? Можно мне поинтересоваться, что другое ты мог подумать? Как ты себе представляешь упаковку самих типов, а не их значений в системе, где описательная информация о типах недоступна?
Вот фраза, вызвавшая сложность в понимании:
Матчинг АлгТД в Хаскеле аналогично проверяет в рантайм дискриминатор объединения, то бишь запакованный тип.
А если так перефразировать:
Матчинг АлгТД в Хаскеле аналогично проверяет в рантайм дискриминатор объединения, то бишь тип запакованного значения.
(По моему ИМХО, одно упоминание АлгТД подразумевает все связанные с этим детали, которые не обязательно повторять в каждом предложении.)
K>Ну, может и можно сказать, что конструктор типа "упаковывает" типа по аналогии с тем, как конструктор "упаковывает" значение. Вот только конструкторы типов в рантайме не матчатся (в рантайме их нет).
А как конструкторы могут матчиться? Ведь конструктор — это ф-ия. Может матчиться лишь некое ID размеченного объединения, то бишь матч всегда идет по ЗНАЧЕНИЮ, в данном случае это значение метки типа. Т.е. можно сказать, что идет матч по типу завернутого значения.
V>>Похоже, та специфика минималистичности Хаскеля, что одновременно с объявлением АлгТД объявляется (вводится) мн-во оборачиваемых им типов-туплов, совершенно сбивает тебя с толку. K>Не знаю, что сбивает с толку вас, но вы последовательно путаете суммы в которых между "слагаемыми" и суммой нет отношения подтипирования и объединения в которых такие отношения есть. K>Поэтому, если про сабтайпинг через наследование, например, еще можно сказать, что какие-то типы в рантайме проверяются, то про суммы — алгебраические типы — такое нельзя сказать даже с натяжкой.
Угу, ты повторно налегаешь на подтипирование, хотя я его не упоминал. Я догадываюсь, что ты намекаешь на реализацию наподобие АлгТД в Немерле, но я-то здесь при чем?
Размеченное объединение обязательно хранит метку обернутого в АлгТД типа. Почему именно типа? Потому что согласно определения, размеченное объединение хранит в себе не только сам элемент, но обязательно некий уникальный идентификатор мн-ва, к которому принадлежит хранимый элемент. Мн-во — это тип, элемент мн-ва — это значение типа.
data List a = Nil | Cons a (List a)
Для значения дескриминатора Nil_tag хранится tuple(/*empty*/), для значения Cons_tag хранится tuple(a, List<a>). Оба тупла и есть хранимые значения. Но сии значения-туплы имеют тип, как ни крути. Теперь перечитай ту фразу еще раз:
Похоже, та специфика минималистичности Хаскеля, что одновременно с объявлением АлгТД объявляется (вводится) мн-во оборачиваемых им типов-туплов, совершенно сбивает тебя с толку.
Я теперь более чем уверен, что это именно так.
И да, от техники подтипирования в том же Немерле такая схема по-сути не отличается. Хоть ты и додумал за меня малость, насчет подтипирования, но происходящие в обоих случаях проверки типов при паттерн-матчинге — это суть проверки значения некоей метки. Вся разница лишь в том, что в Хаскеле метка представлена "унутре" интегральным значением, а в Немерле — адресом (сылкой) на дескриптор типа. По идее, тоже интегральным значением (адреса).
K>Ну так boost::variant это не сумма, а объединение — чего вы от него хотите. variant< int, bool > это int или bool.
Не так, это еще хранимый признак типа, как и положено. Поэтому это или container<0, int> или container<1, bool>. Т.е. в плане хранения — дотягивают. Они недотягивают в момент диспетчеризации по конкретным хранимым типам, бо для одного и того же типа будет вызвана одна и та же ветка диспетчеризации, хотя их контейнер имел разные типы, например для случая variant<int, bool, int> будет вызвана одна и та же ветка диспетчеризации в клиентском коде для container<0, int> и для container<2, int> с аргументом просто int, т.е. эти случаи будут неразличимы. Жаль. Было бы неплохо генерить ошибку компиляции для случая одинаковых типов.
K>С суммами все иначе: Either Int Bool не является ни Int ни Bool.
Как я только что показал — вовсе не иначе. Проблема в сценариях и гарантиях. Хотя, проблему нетипизированного union они решили.
K>Поэтому variant не "недотягивает" до АлгТД — это вовсем другая штука со своими достоинствами и недостатками и слабопересекающейся с АлгТД областью применения.
Ну как раз область применения пересекается нормально. Особенно для случая уникальных типов в варианте. И особенно в случае boost::make_recursive_variant — как раз под такие же сценарии.
K>Одно непонятно: зачем так фиксироваться на суммах? В описываемом примере они вовсе не используются. Тайплевел вычисления делаются на Nil и Cons, первый — единица с тегом, второй — произведение с тегом.
Да пофиг. Реализация инстансов классов на технике-аналоге vtable ничем по-сути от происхоящего с помощью АлгТД не отличается. Вернее, наоборот, реализация матча по АлгТД во многих сценариев может быть выполнена на таблицах ф-ий.
Тем более, что обсуждаемый пример можно переписать на АлгТД и обойтись без классов типов. Не в этом суть. Суть была в стоимости итерирования по боксированным типам.
K>Реализация (в компиляторе) требуется. место в памяти в рантайме — нет. В рантайме нет типов.
Для тега алгТД место в рантайме таки требуется.
K>Еще раз. Динамической типизации тут нет. a == 3 динамическая типизация? Нет, 3 не тип.
Повторю, для алгТД есть:
data Box = IntBox int | StringBox string
isThree Box b =
case b of
IntBox a -> a == 3
StringBox a -> a == '3'
Или ты считаешь, что процедура сравнения тега размеченного объединения и распаковка затем хранимого значения чем-то отличается от динамической типизации? Это абсолютно такое же кол-во телодвижений за ту же стоимость.
K>Совершенно верно. Это масимум того, что можно получить от ML-like полиморфизма. Для большего нужны зависимые типы.
Именно. А приводимый пример фактически надуманный, т.к. что там проверять-то?, коль списки формируются параллельно. А для любого другого сценария, действительно требующего проверки, эта магия уже не работает.
V>>а не от того, что полностью поддерживается параметрический полиморфизм в том смысле, как нам пытаются показать в примере.
K>От того. Без поддержки ПП это не заработает.
Да неполноценный это ПП ни разу. В любой точке примера используется один и тот же тип для обоих списков, а потом заявляется, что мы якобы проверяем что типы одинаковы. Хотя такими их объявили сами же. Хочется спросить автора — он хорошо себя чувствует?
Нет ничего проще взять тот же самый n, сформировать сначала один список, потом другой, потом попробовать проделать тот же самый фокус. И хотя тип списков будет опять одинаковый, но фокус уже не сработает. Где ты там увидел работающее ПП? В примере показан несложный трюк, основанный на том, что компилятор Хаскеля способен два одинаково объявленных типа в одной области видимости считать одинаковыми. Стоит выйти из области видимости — и всякое ПП исчезает.
V>>Ну и в С++ вызов ф-ии — это ф-ии времени компиляции, а в Хаскеле — это обращение к группе ф-ий и выбор конкретной через паттерн-матчинг: V>>main' 0 _ as bs = scalarProduct as bs V>>main' n i as bs = main' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
K>Это не верно. Никакой группы функций нет. Вот так: K>
K>test' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
K>test' n i as bs | n == 0 = scalarProduct as bs
K> | otherwise = test' (n-1) (i+1) (Cons (2*i+1) as) (Cons (i^2) bs)
K>
K>тоже сработает.
Угу, как возражение, что при вызове ф-ий используется неявное ПМ привел явную реализацию на ПМ. Поздравляю.
K>Классы типов тут тоже ничего в рантайме не диспетчеризуют (для ранг-2 полиморфизма будут, но это здесь не используется).
Табличная диспетчеризация таки есть.
V>>В сухом остатке: хотя имеем статическую типизацию (проверку соответствий типов), конкретные типы проверяются в рантайм,
K>Не верно, все типы проверяются в компайл-тайм. Иначе как ошибка появилась бы при компиляции?
V>> конкретные ф-ии тоже выбираются в рантайм K>Не верно. Все функции выбираются в компайл-тайм.
K>Выбор между двумя видами scalarProduct происходит на этапе компиляции
Каким образом? Чтобы выбор сделать, надо отказаться от боксированного представления, т.е. заранее знать, где конец списка. Если же в compile-time компилятор не знает где конец, то данные надо связывать с кодом, например посредством таблиц ф-ий.
K>Но весь смысл этого куска кода в compile-time проверке!
Да нет там никакой проверки, не изобретай! Ограничение идет прямо в самой сигнатуре main':
main' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
Т.е. в самой сигнатуре main' черным по белому сказано, что a->a. Как можно было потом радоваться, что у нас скомпиллировалась строчка в теле main':
scalarProduct a1 a2
или
scalarProduct (Cons x a1) (Cons y a2)
если a1 и a2 имеют один и тот же тип по опредедению main'?
Это как написать x = 2 * 2 и радоваться, что получилось 4.
Ты мне покажи вот так:
main' :: Integer -> Integer -> a -> b -> Integer
Потом подай на a и b одинаковые типы извне. Вот это и будет настоящий ПП, а не тот, которым вы хотите незаслуженно обозвать систему типов Хаскеля.
V>>Для сравнения, Хаскель сначала требует через ПМ распаковать содержимое АлгТД, т.е. уменьшить мощность (или "логическую косвенность") для нашего сценария рекурсивного типа
K>Сюрприз: в обсуждаемом коде на хаскеле 0 (ноль) рекурсивных типов. K>а в коде-то — смотрите-ка: K>
K>data Nil = Nil
K>data Cons a = Cons Integer a
K>
K>Никаких рекурсивных типов. Никаких сумм.
Ну коль Cons a параметризируется типом Cons a', то у нас выходит, скажем, де-факто параметрический рекурсивный тип в примере. Дело в том, что в С++ возможны только такого рода рекурсивные типы, если рекурсия объявлена по-значению (через поле или базовый класс), а не по ссылке/указателю. В приведенной неудачной попытке реализации примера на С++ был именно этот вид рекурсии.
И да, как раз случай простого боксирования для обычного рекурсивного типа неинтересен, бо код действительно будет один и тот же на любой глубине рекурсии, т.к. тип один и тот же. Гораздо интереснее вариант такой полиморфной рекурсии, как в примере. Если код main' должен быть одинаковым для каждого шага рекурсии (а он однозначно должен быть одинаковым, коль n может быть сколь угодно большим и неизвестным в compile-time), то должна присутствовать та или иная техника диспетчеризации вызова интерфейсной ф-ии scalarProduct.
V>>Не путаю. Дескриминатор-то типа проверяется в рантайм.
K>Дескриминаторов типа в Хаскеле просто не существует
True, False, Cons, Nil — вот тебе существующие в рантайм дискриминаторы, хранимые вместе с данными. Для случая True, False, Nil данные представляют из себя пустой тупл.
K>Integer — алгебраический тип (и по стандарту, а 3 — его конструктор) и в реализации. Не забудьте еще алгебраический тип Bool. True и False, кстати, тоже типы по-вашему?
Это конструкторы АлгТД в одном контексте и теги АлгТД, то бишь теги хранимого типа — в другом, например в конструкции ПМ. А что, тебя смущает вырожденный случай размеченного объединения навроде Bool?
K>Да. Там где проверять не нужно — там вы не будете проверять ни в хаскеле, ни в C++. Там где без этого не обойтись (например, это указатель на следующий элемент списка или на null) — вы будете проверять в рантайме и там и там.
Это если у нас связанный список боксированных вычислений, то будет так же. А так-то вычисления на подобных шаблонных рекурсивных типов на С++ не то, что не рекурсивны, там даже циклов нет с проверками, есть просто линейный код, повторенный столько раз, сколько глубина рекурсии. Ну и оптимизация в регистрах получается существенная, т.к. помимо ухода затрат на организацию цикла, освобождаются лишние регистры, т.е. кач-во оптимизации выходит получше. Например, для фильтрации нерекурсивным фильтром, эта техника дает увеличение в 1.8-3 раза по сравнению с вариантом двух вложенных циклов... Там же еще играют роль всякие вещи: например, ветвление по выходу из цикла в 100% случаев провоцирует сброс конвейера процессора, а в случае цепочек таких линейных вычислений — никогда. В общем, факт в том, что любой if — тоже дорогая операция, бо может сбросить конвейер. Виртуальные ф-ии в этом плане предпочтительней тега АлгТД, т.к. адрес выбирается безусловно механизмом процессора еще до исполнения инструкции ядром, и плюс компилятор оптимизирует так, что адрес ф-ии достается в начале цикла лишь однажды.
Здравствуйте, Klapaucius, Вы писали:
V>>Имел ввиду сервисы кодогенерации, работающие в рантайм... Будем считать это техникой исполнения параметрического полиморфизма в отсутствии боксирования. Т.е. будем делать вид, что это таки рантайм и подробности реализации.
K>Почему рантайм-то? Берете ngen и компилируете в link-time.
У меня был калькулятор, который парсил выражение-запрос и строил AST из value-type узлов именно по этой технологии в рантайм. И затем прогонял выборку через такой калькулятор. Работало быстрее обычного интерпретирующего AST более чем в 5 раз, не сильно уступая способу с формированием исходника на C#, компиляции и загрузки на лету. А в общем вреемни выполнения операции — вышло намного лучше, за счет дороговизны всех этих вещей по компиляции и загрузке модулей. Ну и плюс одинаковые после оптимизации выражения порождают одинаковый тип AST, т.е. хорошее повторное использование готового заджиттенного кода, в отличие от.
Здравствуйте, vdimas, Вы писали:
> Ругать получившуюся систему типов можно будет затем лишь от непонимания причинно-следственных связей характеристик языка и подходящей под эти характеристики системы типов.
Т.е. если я понимаю причинно-следственные связи характеристик языка, то ругать уже не буду вне зависимости от того, нравятся мне такие характеристики или нет? Интересное мнение.
Скажите пожалуйста, а switch-case для enum в Си++ — это тоже динамическая типизация?
V>Ты мне покажи вот так: V>main' :: Integer -> Integer -> a -> b -> Integer
V>Потом подай на a и b одинаковые типы извне. Вот это и будет настоящий ПП, а не тот, которым вы хотите незаслуженно обозвать систему типов Хаскеля.
Не понял. Что должна делать такая функция? Ругаться при компиляции на разные типы? Или молча жрать все? Тогда в чём замысел?
Здравствуйте, vdimas, Вы писали:
V>В каких разных? Продемонстрируй плиз разные пространства имен для конструктора АлгТД и для одноименного идентификатора метки типа в конструкции ПМ некоего значения, созданного с помощью этого конструктора.
Я говорил про имя типа и имя конструктора.
V>Эээ... колега, а что вообще могло бы означать "упаковка другого типа" для АлгТД?
Вот и мне интересно. Это ведь вы утверждаете, что конструкторы АлгТД — типы.
V>А если так перефразировать: V>Матчинг АлгТД в Хаскеле аналогично проверяет в рантайм дискриминатор объединения, то бишь тип запакованного значения.
Дескриминатор-то он, разумеется, проверяет в рантайме. А тип "запакованного" значения — нет. О чем и весь этот разговор.
K>>Ну, может и можно сказать, что конструктор типа "упаковывает" типа по аналогии с тем, как конструктор "упаковывает" значение. Вот только конструкторы типов в рантайме не матчатся (в рантайме их нет).
V>А как конструкторы могут матчиться?
Ну вот, приехали. А что, что-то может матчиться кроме конструкторов?
V>Ведь конструктор — это ф-ия.
Совсем не обязательно. В ocaml, например, конструктор функцией не является. Но даже если всякий конструктор — функция, то все равно не всякая функция — конструктор.
V>Может матчиться лишь некое ID размеченного объединения, то бишь матч всегда идет по ЗНАЧЕНИЮ
Правильно. Имя конструктора и есть этот ID.
V>в данном случае это значение метки типа. Т.е. можно сказать, что идет матч по типу завернутого значения.
Это значение к метке типа не имеет никакого отношения. Т.е. так сказать нельзя.
-- так можноdata FooBar = Foo Int | Bar Int-- так нельзяdata Foo = Foo Int | Foo Bool
т.е. в рантайме проверяются не типы. ч.т.д.
V>Угу, ты повторно налегаешь на подтипирование, хотя я его не упоминал.
Постоянно упоминаете, когда говорите о проверке метки типа в рантайме.
V>Я догадываюсь, что ты намекаешь на реализацию наподобие АлгТД в Немерле, но я-то здесь при чем?
Не намекаю. Я объясняю, что является типом в хаскеле, а что не является.
V>Размеченное объединение обязательно хранит метку обернутого в АлгТД типа. Почему именно типа? Потому что согласно определения, размеченное объединение хранит в себе не только сам элемент, но обязательно некий уникальный идентификатор мн-ва, к которому принадлежит хранимый элемент. Мн-во — это тип, элемент мн-ва — это значение типа.
Тип — это не всякое множество, а множество, пренадлежность к которому может проверить тайпчекер. Статически.
V>data List a = Nil | Cons a (List a)
V>Для значения дескриминатора Nil_tag хранится tuple(/*empty*/), для значения Cons_tag хранится tuple(a, List<a>). Оба тупла и есть хранимые значения. Но сии значения-туплы имеют тип, как ни крути.
Туплы имеют тип. Но этот тип не находится с алгтд в отношениях подтипирования.
V>И да, от техники подтипирования в том же Немерле такая схема по-сути не отличается. Хоть ты и додумал за меня малость, насчет подтипирования, но происходящие в обоих случаях проверки типов при паттерн-матчинге — это суть проверки значения некоей метки. Вся разница лишь в том, что в Хаскеле метка представлена "унутре" интегральным значением, а в Немерле — адресом (сылкой) на дескриптор типа. По идее, тоже интегральным значением (адреса).
В хаскеле (ghc) эта "метка" как раз и представлена ссылкой на дескриптор блока памяти и (в случае небольшого числа конструкторов) тегом в указателе на блок.
K>>Ну так boost::variant это не сумма, а объединение — чего вы от него хотите. variant< int, bool > это int или bool.
V>Не так, это еще хранимый признак типа, как и положено. Поэтому это или container<0, int> или container<1, bool>. Т.е. в плане хранения — дотягивают. Они недотягивают в момент диспетчеризации по конкретным хранимым типам, бо для одного и того же типа будет вызвана одна и та же ветка диспетчеризации
Вот вы и описываете разницу между сабтайпингом (boost::variant) и алгебраическим типом. Еще одно различие. Через параметр типа variant< int, bool > можно передать и int и bool. Потому, что любые значения этих типов являются также и значениями вариантного типа. Это и называется "отношение подтипирования". Между типами "упакованными" в сумму и суммой такого отношения нет. Через параметр типа Either Int Bool нельзя передать ни значение типа Int, ни значение типа Bool. Только Either Int Bool.
V>Да пофиг. Реализация инстансов классов на технике-аналоге vtable ничем по-сути от происхоящего с помощью АлгТД не отличается.
Вот только инстансы классов на технике-аналоге vtable не реализуются (если у нас полиморфизм ранга-1).
V>Тем более, что обсуждаемый пример можно переписать на АлгТД и обойтись без классов типов.
Нельзя. Потому, что нам нужны тайплевел вычисления, а не рантайм-проверка.
V>Для тега алгТД место в рантайме таки требуется.
Никакие "Теги" АлгТД в рантайме не хранятся. Есть ссылка на InfoTable. У каждого вида лайаута данных в памяти — своя таблица.
V>Или ты считаешь, что процедура сравнения тега размеченного объединения и распаковка затем хранимого значения чем-то отличается от динамической типизации? Это абсолютно такое же кол-во телодвижений за ту же стоимость.
То, является что-то типизацией или не является не определяется стоимостью. Типизация — это проверка типа. Если мы проверяем значения на совпадение с BoxInt, true или 0 — это не проверка типа потому, что все это не типы. Не всякая проверка — проверка типа.
V>Именно. А приводимый пример фактически надуманный, т.к. что там проверять-то?, коль списки формируются параллельно.
Ну так то, что списки формируются параллельно и проверяется.
V>Да неполноценный это ПП ни разу. В любой точке примера используется один и тот же тип для обоих списков, а потом заявляется, что мы якобы проверяем что типы одинаковы. Хотя такими их объявили сами же.
Ну так смысл проверки типов в том, чтоб проверять соответствие функции типу, которым ее проаннотировали. Если функция не будет строить списки параллельно — будет ошибка компиляции.
V>Угу, как возражение, что при вызове ф-ий используется неявное ПМ привел явную реализацию на ПМ. Поздравляю.
Привел реализацию в которой явно видно, что есть проверка счетчика для прерывания рекурсии, а никакого "выбора функции из группы" нет.
V>Табличная диспетчеризация таки есть.
В обсуждаемом случае нет.
K>>Не верно. Все функции выбираются в компайл-тайм. V>
Да, смешно читать, что человек может нафантазировать про тайпклассы, не зная, как они работают.
V>Каким образом? Чтобы выбор сделать, надо отказаться от боксированного представления, т.е. заранее знать, где конец списка.
Не нужно знать, где конец списка. Мы начинаем построение с Nil — это статически известно. выбираем одну функцию и подставляем ее вместо функции тайпкласса. Во всех остальных случаях будет функция для Cons — одна и та же — подставлем ее. Все, вся диспетчерезация закончилась на этапе компиляции.
K>>Но весь смысл этого куска кода в compile-time проверке! V>Да нет там никакой проверки, не изобретай! Ограничение идет прямо в самой сигнатуре main': V>main' :: ScalarProduct a => Integer -> Integer -> a -> a -> Integer
Правильно, это ограничение и проверяется.
V>Т.е. в самой сигнатуре main' черным по белому сказано, что a->a. Как можно было потом радоваться, что у нас скомпиллировалась строчка в теле main': V>scalarProduct a1 a2 V>или V>scalarProduct (Cons x a1) (Cons y a2) V>если a1 и a2 имеют один и тот же тип по опредедению main'?
Ну так в этом проверка типа и заключается. Если в функции не будет "черным по белому сказано", что списки одинаковой длины — будет ошибка компиляции.
V>Ты мне покажи вот так: V>main' :: Integer -> Integer -> a -> b -> Integer
Тут написано, что типы могут быть одинаковыми, а могут и раными — все равно. Что это проверяет-то?
V>Ну коль Cons a параметризируется типом Cons a', то у нас выходит, скажем, де-факто параметрический рекурсивный тип в примере.
Отличная шутка! А любая функция, у которой область определения — подмножество области значений — "де-факто рекурсивная"? А то ведь ее можно так применить f(f(х))
K>>Дескриминаторов типа в Хаскеле просто не существует V>True, False, Cons, Nil — вот тебе существующие в рантайм дискриминаторы, хранимые вместе с данными.
Это и есть данные. Не типы.
V>Это конструкторы АлгТД в одном контексте и теги АлгТД, то бишь теги хранимого типа — в другом, например в конструкции ПМ.
Еще раз. Медленно. Теги конструктора и теги типа — разные вещи. И в конструкции ПМ никакие типы не проверяются. Тип там один — тип матчимого АлгТД. Проверяется статически.
V>А что, тебя смущает вырожденный случай размеченного объединения навроде Bool?
Ничем не смущает. Меня смущает, что вы True и False считаете типами.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
K>>>Вот вы говорите можно, а из обсуждаемого здесь опыта известно, что нельзя. Как же так? S>>Здесь обсуждается другой опыт. А опыт написать Succ<Succ<Succ<...>>> сколько ума хватит раз не обсуждается.
K>Да не написать Succ<Succ<Succ<...>>> а получить еще один из готового. Как тут: K>
K>instance ScalarProduct a => ScalarProduct (Cons a)
K>
А причем тут наличие/отсутствие ПП?
S>>Вторая фича тут
— это импредикативность, верно?
K>Нет, не верно.
Прошу прощения. Но тогда я вообще не понимаю, как пункт 2 относится к полиморфизму.
S>>Вы доказывали что-то другое, причем я ответил по этому доказательству.
K>Вы по доказательству ответили, что оно не считается и все тут.
Я ответил что вы сделали слишком сильные выводы из определения. Это в том случае, если использовать определение с википедии. Ну а то, на которое вы ссылаетесь — я не знаю, откуда оно взялось.
K>>>Т.е. и 32-битный int — это тоже самое настоящее целое число? S>>Нет. Я же согласился что это софистика.
K>И чем, по-вашему, это обоснование через "все возможные" программы отличается от вашего?
Это важно? Имеет какое-то отношение к ПП?
S>>Предикативный и импредикативный — это формы, а не определение ПП.
K>Точно. И что? Импредикативного никто и не требует.
Ок, пусть так.
S>>Вы дали ссылку и по ссылке вторым пунктом импредикативность. Что же вы обсуждаете, если не импредикативность?
K>Ок. Выкинте упоминание об импредикативности — оно там "для справки". K>1) Возможность написать однородный (работающий одинаково для любого типа) код
Не возражаю. K>2) Типизировать этот код обобщенным типом, содержащим переменные, которые конкретизируются применением обобщенного типа к конкретному.
Вот это откуда? И можно разжевать, что значит обобщенный тип, содержащий переменные?
Вот как это в TAPL (если я правильно понял, о чем речь):
Parametric polymorphism,the topic of this chapter, allows a single piece of
code to be typed “generically,” using variables in place of actual types, and
then instantiated with particular types as needed.
И в этом нет ничего такого, что не позволил бы шаблон в C++.
K>Типизировать шаблонный код в C++ нельзя вообще никак.
Вот как то же выглядит в переводе
... позволяет давать участку кода "обобщенный тип", используя переменные вместо настоящих типов, а затем конкретизировать, замещая переменные типами.
Что вы здесь подразумеваете под "типизировать"?
S>>У него не получилось кое-что другое, о чем я не говорю. А вот написать наперед заданное число раз Cons<Cons<... он не попробовал. А это помогло бы
K>Нда. Кто decl читал — тот в цирке не смеется.
Мну цирк и до decl-а не прикалывал.
K>>>В системе типов C# 2 и выше — есть. S>>Аналога explicit forall в C# нет. Если бы был, можно было бы описать абстракцию функтора/монады и не пришлось бы жарить уток как в Query expression pattern.
K>1)explicit forall не обязателен для такого простого типа. Он тут для понятности. explicit forall, кстати, в Хаскеле без расширений нет. K>2)В C# он наоборот есть — программист просто обязан писать его в сигнатуре: K>
K>T foo<T>(Bar<T> a)
K>
K>что то же самое, что K>
K>foo :: forall t. Bar t -> t
K>
K>В хаскеле же писать его не обязательно, там где он очевиден — это просто сахар.
Я об этом (http://en.wikibooks.org/wiki/Haskell/Polymorphism#Higher_rank_types). C# такое не умеет.
S>>Типы применяются во время выполнения
K>Нет, не применяются. Как выглядит применение во время выполнения в C# мы уже обсудили.
Нет, не обсудили. Вы не захотели обсуждать, на какую глубину применяются типы в компайл-тайме и чем занимается рантайм при введении значительной глубины структуры (например от 1000).
S>>, для каждого рекурсивного вызова _main выполняется докомпиляция JIT-ом.
K>Зачем его докомпилировать? Это же ссылочные типы, для них никакие специализации не нужны!
S>>Аргументы?
K>Аргументы ниже.
K>>>JIT-компилятора для C# не существует в природе. Допустим, что мы обсуждаем JIT-компиляцию CIL. Вы утверждаете, что в link-time, ngen-ом этот код полностью не скомпилировать? S>>Не так. в один проход мы его скомпилируем, но по мере необходимости рекурсивных вызовов он будет докомпиляться. S>>А где-нибудь в сингулярити, где JIT-а нет, такой код работать не будет
K>Докажите, что он не будет там работать.
У меня нет под рукой сингулярити. Придется поверить так. Каждый new Cons<A>(...) согласно особенностям рантайма требует свой RuntimeType, а так же свои native реализации конструктора и метода scalarProduct. Хотя я не считаю себя специалистом по дотнет рантайму, но дотнет был моей основной специализацией с 2002-го года, и уверяю, мне неизвестно ни одного факта, позволяющего предполагать что Cons будет обходиться одной native реализацией своих методов и обходиться одним RuntimeType-ом во всех конкретизациях. Да, с List<T> одна реализация native методов прокатывает для всех reference типов T. Но это немного другое, там ни один метод List<T> не зависит от природы типа T (reference типа). А вот реализация scalarProduct — очень даже зависит.
Может в конце концов убедит то что у _main<A>, у scalarProduct от вызова к вызову разные MethodInfo?
public int scalarProduct(Cons<A> second)
{
Func<Cons<A>, int> f = scalarProduct;
return value * second.value + tail.scalarProduct(second.tail); //<-- set breakpoint here
}
Вот такой трюк позволяет убедиться в том, что f при различных <A> совершенно разные объекты. Очевидно, что одна и та же нативная реализация метода не позволит получать разные объекты.
K>>>И как быть с Java у которой в байт-коде нет ПП и, следовательно, нет смысла о JIT говорить? Как быть с хаскелем? S>>А никак не надо быть, мы ведь обсуждаем ПП ЯВУ а не байт-кода.
K>Так и я о том же. По вашему параметрические типы должны применятся в рантайме. Поэтому вы и обсуждаете компиляцию байткода в котором ПП есть. И только рантаймовую — возможность линктаймовой компиляции вы полностью игнорируете. В случае с C# лично для меня ситуация с рантаймом понятна, но вокруг нее столько всяких недопониманий и суеверий, что я просто сразу предлагаю: обсуждаем случаи, в которых никаких двусмысленностей нет: в Java и Haskell параметрические типы не могут применятся в рантайме в принципе потому, что в рантайме они не существуют.
K>>>Главный вопрос: как мы получаем ошибку компиляции C# за долго до того, как полиморфный код якобы скомпилируется в рантайме. S>>О какой конкретно ошибке речь?
K>Об ошибке, когда код генерирует неравные списки. Она возникает в компайл-тайм.
А с чего им быть неравными? Да и это доказано во время компиляции C#, что они будут равными. Так что я не вижу никакой ошибки компиляции, на вопрос ответить затрудняюсь, т.к. не понял его.
S>>Вообще-говоря, С# проверяет ограничения при компиляции в IL и получает гарантии что они не будут нарушены в рантайме. Если такая проверка обламывается, мы получаем ошибку времени компиляции в IL. Если нет — то в JIT все будет как по рельсам.
K>Т.е. вы хотите сказать, что система типов второго C# unsound, статически проверить безопасность компилятор не может.
Я такого не хочу сказать. Проверить безопасность компилятор может, и делает это. А вот конкретизирует типы и JIT-ит код методов в рантайме. Это я хочу сказать. K>Спорить с этим сложно, потому что благодаря таким мегафичам как ковариантность массивов и даункасты — так оно, собственно, и есть. С дженериками C# таких проблем, правда, нет, но мне проще обсудить полиморфизм на примере другого языка, чем вас в этом убедить.
В этом это в чем? В том что JIT не докомпиляет каждый вложенный вызов _main<A>/scalarProduct? Наверное на другом языке это будет сделать еще сложнее.
K>>>Нужно конкретизировать все до момента тайпчека, вы хотели сказать? S>>наверное
K>Ну, тоесть параметрических типов в C++ нет, а параметрический полиморфизм, по вашему, есть? Оригинально.
А кто сказал что должны существовать параметрические типы? Определение касается вообще куска кода и способа сделать его обобщенным. Так что, что шаблон, что макрос, могут удовлетворять определению.
Здравствуйте, samius, Вы писали:
S>А причем тут наличие/отсутствие ПП?
Вот как раз это я тут и объясняю в 128-и сообщениях. Без всякого толка, как видно.
S>Но тогда я вообще не понимаю, как пункт 2 относится к полиморфизму.
Первый пункт говорит про код. Второй — про типизацию этого кода. Если не рассматривать типизацию, как вы вообюще отличите параметрический полиморфизм от полиморфизма через сабтайпинг? Код и там и там однородный, только способы его типизировать отличаются.
S>Я ответил что вы сделали слишком сильные выводы из определения.
И в чем слишкомсильность их? Что существует тип forall a. Succ a? Ну а какой полиморфный код вы типизируете, если даже такой тип не существует?
S>Это в том случае, если использовать определение с википедии. Ну а то, на которое вы ссылаетесь — я не знаю, откуда оно взялось.
Это пересказ обределения из TAPL своими словами.
K>>И чем, по-вашему, это обоснование через "все возможные" программы отличается от вашего? S>Это важно? Имеет какое-то отношение к ПП?
Это имеет непосредственное отношение к обсуждаемой теме. Потому, что вы аргументируете таким способом (потенциальную) бесконечность типов. Если я применяю ваш способ неправильно — укажите мне в чем ошибка.
K>>2) Типизировать этот код обобщенным типом, содержащим переменные, которые конкретизируются применением обобщенного типа к конкретному. S>Вот это откуда? И можно разжевать, что значит обобщенный тип, содержащий переменные?
Вы же прямо из TAPL сюда куски текста копируете, значит доступ к тексту у вас есть. А там все разжевано.
S>
S>Parametric polymorphism,the topic of this chapter, allows a single piece of
S>code to be typed “generically,” using variables in place of actual types, and
S>then instantiated with particular types as needed.
S>И в этом нет ничего такого, что не позволил бы шаблон в C++.
Проще сказать, что из описаного шаблон бы позволил. Видите, тут написано, что обобщенный код сначала типизируется обобщенным типом. Шаблонный код не типизируется. И типизировать его нельзя — тогда часть выразительных возможностей шаблонов будет недоступна. Типизируемый код получается только после инстанциации.
S>Что вы здесь подразумеваете под "типизировать"?
Это и явный forall — не одно и то же. Мы вообще обсуждаем самый упрощенный вариант полиморфизма (ML-like) там возможен только полиморфизм ранга 1.
S>C# такое не умеет.
Haskell 98 на котором написан обсуждаемый код — тоже не умеет.
S>Нет, не обсудили. Вы не захотели обсуждать, на какую глубину применяются типы в компайл-тайме и чем занимается рантайм при введении значительной глубины структуры (например от 1000).
Я просто не вижу какое отношение это обсуждение деталей реализации к основной теме треда. И каким образом оно может быть продолжено. Допустим даже, что в C# все применяется в рантайме и т.д. — вывод-то какой из этого? Что для ПП нужна поддержка рантайма? Нет. Приведены примеры реализаций ПП без всякой рантайм-кодогенерации. Что в C++ есть ПП? Нет, эти два вопроса вообще никак не связаны.
K>>Так и я о том же. По вашему параметрические типы должны применятся в рантайме. Поэтому вы и обсуждаете компиляцию байткода в котором ПП есть. И только рантаймовую — возможность линктаймовой компиляции вы полностью игнорируете. В случае с C# лично для меня ситуация с рантаймом понятна, но вокруг нее столько всяких недопониманий и суеверий, что я просто сразу предлагаю: обсуждаем случаи, в которых никаких двусмысленностей нет: в Java и Haskell параметрические типы не могут применятся в рантайме в принципе потому, что в рантайме они не существуют.
Ну правильно, упоминания о реализациях без всякой поддержки рантайма вы опять проигнорировали.
K>>Об ошибке, когда код генерирует неравные списки. Она возникает в компайл-тайм. S>А с чего им быть неравными?
Ну, перепишите код функции так, чтоб она генерировала неравные списки. С того и будут. И будет ошибка компиляции.
K>>Т.е. вы хотите сказать, что система типов второго C# unsound, статически проверить безопасность компилятор не может. S>Я такого не хочу сказать. Проверить безопасность компилятор может, и делает это. А вот конкретизирует типы и JIT-ит код методов в рантайме. Это я хочу сказать.
Т.е. вы не утверждаете как остальные мои оппоненты тут, что проверка рантаймовая? В чем тогда суть вашего возражения?
S>В этом это в чем? В том что JIT не докомпиляет каждый вложенный вызов _main<A>/scalarProduct?
Нет, в том, что типобезопасность дженериков проверяется статически, а не в рантайме. К счастью, вас в этом убеждать и не требуется.
K>>Ну, тоесть параметрических типов в C++ нет, а параметрический полиморфизм, по вашему, есть? Оригинально. S>А кто сказал что должны существовать параметрические типы?
Я сказал. В определении параметрического полиморфизма так написано. Поэтому он и "параметрический" и отличается от других видов полиморфизма.
S>Определение касается вообще куска кода и способа сделать его обобщенным. Так что, что шаблон, что макрос, могут удовлетворять определению.
Не могут. Если вы определяете "параметрический полиморфизм" просто как однородный код, тогда под него подпадают и другие виды полиморфизма. Но не шаблоны и не макросы, потому что с их помощью писать однородный код нельзя.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>А причем тут наличие/отсутствие ПП?
K>Вот как раз это я тут и объясняю в 128-и сообщениях. Без всякого толка, как видно.
Да, заметно.
S>>Но тогда я вообще не понимаю, как пункт 2 относится к полиморфизму.
K>Первый пункт говорит про код. Второй — про типизацию этого кода. Если не рассматривать типизацию, как вы вообюще отличите параметрический полиморфизм от полиморфизма через сабтайпинг? Код и там и там однородный, только способы его типизировать отличаются.
ПП от СП я отличаю по определению (одного, другого). Ни то ни другое определение (в википедии, по крайней мере) не используют термин "типизация".
К вам, собственно встречный вопрос. Как вы их отличаете? Подод к вопросу тоже есть. Вы берете пример migmit-а, в котором задействованы bounded и subtype полиморфизмы, и утверждаете что ПП там нет в случае С++ и есть в случае C#.
S>>Я ответил что вы сделали слишком сильные выводы из определения.
K>И в чем слишкомсильность их? Что существует тип forall a. Succ a? Ну а какой полиморфный код вы типизируете, если даже такой тип не существует?
Я же не утверждаю что он не существует. Я утвреждаю что существование такого типа не связано с определением. Тем более, не очевидно существование такого типа в конкретной программе. А существование его в какой-то абстрактной программе — такая же софистика, как и существование бесконечного числа типов в бесконечном числе программ.
S>>Это в том случае, если использовать определение с википедии. Ну а то, на которое вы ссылаетесь — я не знаю, откуда оно взялось.
K>Это пересказ обределения из TAPL своими словами.
Обращу внимание, что в TAPL нет упоминания о типе, содержащем переменные.
K>>>И чем, по-вашему, это обоснование через "все возможные" программы отличается от вашего? S>>Это важно? Имеет какое-то отношение к ПП?
K>Это имеет непосредственное отношение к обсуждаемой теме. Потому, что вы аргументируете таким способом (потенциальную) бесконечность типов. Если я применяю ваш способ неправильно — укажите мне в чем ошибка.
Мой основной довод — это то, что бесконечность типов не требуется. Попытку представить бесконечность типов в бесконечности программ предлагаю игнорировать. Точно так же как и существование типа forall a. Succ a.
K>>>2) Типизировать этот код обобщенным типом, содержащим переменные, которые конкретизируются применением обобщенного типа к конкретному. S>>Вот это откуда? И можно разжевать, что значит обобщенный тип, содержащий переменные?
K>Вы же прямо из TAPL сюда куски текста копируете, значит доступ к тексту у вас есть. А там все разжевано.
А там об этом ничего нет. Там говорится об использовании переменных вместо actual types, а вовсе не о типах, содержащих переменные.
S>>
S>>Parametric polymorphism,the topic of this chapter, allows a single piece of
S>>code to be typed “generically,” using variables in place of actual types, and
S>>then instantiated with particular types as needed.
S>>И в этом нет ничего такого, что не позволил бы шаблон в C++.
K>Проще сказать, что из описаного шаблон бы позволил. Видите, тут написано, что обобщенный код сначала типизируется обобщенным типом. Шаблонный код не типизируется. И типизировать его нельзя — тогда часть выразительных возможностей шаблонов будет недоступна. Типизируемый код получается только после инстанциации.
Не согласен. Там написано что кусок кода типизируется "обобщенно", используя переменные вместо типов. И ничего о параметрических типах, кстати.
S>>Что вы здесь подразумеваете под "типизировать"?
K>Под "типизировать терм" я понимаю "классифицировать терм, задать (проверить) его принадлежность к какому-то определенному типу".
Я думаю, что если бы трактовка "typed generically" была бы именно такой, то в переводе это было бы как-то отражено. В переводе же "типизация" вообще куда-то пропала.
S>>C# такое не умеет.
K>Haskell 98 на котором написан обсуждаемый код — тоже не умеет.
Согласен. Тогда мы в с++ и C# имеем forall, который непонятно чем отличается от forall H98
S>>Нет, не обсудили. Вы не захотели обсуждать, на какую глубину применяются типы в компайл-тайме и чем занимается рантайм при введении значительной глубины структуры (например от 1000).
K>Я просто не вижу какое отношение это обсуждение деталей реализации к основной теме треда. И каким образом оно может быть продолжено. Допустим даже, что в C# все применяется в рантайме и т.д. — вывод-то какой из этого?
Я влез в эту тему что бы выяснить вашу позицию по поводу разграничения C++ и C# на тему присутствия в них ПП. Вы глядя на примеры migmit-а утверждаете что в С# ПП есть, а в С++ — нет. Вот мне и захотелось уточнить, почему вы так считаете. Аргументами была бесконечность типов. Теперь же выяснилось (или как минимум, допустилось), что C# своей потенциальной бесконечности типов обязан особенностям рантайма и без них пример migmit-а работать не будет.
K>Что для ПП нужна поддержка рантайма? Нет.
Согласен, в общем случае не нужна и для C# не нужна тоже. Поддержка рантайма нужна для работы примера migmit-а, связь которого с определением ПП туманна. Там ПП присутствует в форме bounded полиморфизма с замесом на subtype полиморфизм.
K>Приведены примеры реализаций ПП без всякой рантайм-кодогенерации. Что в C++ есть ПП? Нет, эти два вопроса вообще никак не связаны.
Да, приведены. В С++ ПП есть, но это действительно не связано с примерами ПП без рантайм-кодогенерации. Тем более, что в C++ этот ПП присутствует без всякой рантайм-кодогенерации.
K>>>Так и я о том же. По вашему параметрические типы должны применятся в рантайме. Поэтому вы и обсуждаете компиляцию байткода в котором ПП есть. И только рантаймовую — возможность линктаймовой компиляции вы полностью игнорируете. В случае с C# лично для меня ситуация с рантаймом понятна, но вокруг нее столько всяких недопониманий и суеверий, что я просто сразу предлагаю: обсуждаем случаи, в которых никаких двусмысленностей нет: в Java и Haskell параметрические типы не могут применятся в рантайме в принципе потому, что в рантайме они не существуют.
K>Ну правильно, упоминания о реализациях без всякой поддержки рантайма вы опять проигнорировали.
А что мне на них отвечать? Они есть, я с этим спорить не собирался. Да, то что ПП должны применяться в рантайме — я такого не утвреждал, если что. Утверждал я лишь то, что в примере migmit-а (и в моем тоже) типы в C# применяются в рантайме. Ни в коей мере это не является необходимым условием ПП.
S>>А с чего им быть неравными?
K>Ну, перепишите код функции так, чтоб она генерировала неравные списки. С того и будут. И будет ошибка компиляции.
А, без проблем. Только к какому выводу это приведет? В C++ тоже ведь будет ошибка компиляции.
K>>>Т.е. вы хотите сказать, что система типов второго C# unsound, статически проверить безопасность компилятор не может. S>>Я такого не хочу сказать. Проверить безопасность компилятор может, и делает это. А вот конкретизирует типы и JIT-ит код методов в рантайме. Это я хочу сказать.
K>Т.е. вы не утверждаете как остальные мои оппоненты тут, что проверка рантаймовая? В чем тогда суть вашего возражения?
Нет, не утверждаю. Но утверждать то, что в рантайме никаких проверок совсем нет — тоже не берусь. Наверняка есть.
Суть возражения была в том, что обсуждаемая фича в C# работает за счет рантайм конкретизации и кодогенерации. Без этих фич C# в отношении обсуждаемых примеров будет столь же бесполезен, как и C++. Разве что в C# мы сможем написать typeof(Cons<>), а в C++ нет.
S>>В этом это в чем? В том что JIT не докомпиляет каждый вложенный вызов _main<A>/scalarProduct?
K>Нет, в том, что типобезопасность дженериков проверяется статически, а не в рантайме. К счастью, вас в этом убеждать и не требуется.
Верно, не требуется. В обсуждаемых примерах она проверяется статически (как минимум, в том числе статически).
K>>>Ну, тоесть параметрических типов в C++ нет, а параметрический полиморфизм, по вашему, есть? Оригинально. S>>А кто сказал что должны существовать параметрические типы?
K>Я сказал. В определении параметрического полиморфизма так написано. Поэтому он и "параметрический" и отличается от других видов полиморфизма.
Нет, в определении такого не написано. Ни в википедии, ни в TAPL. "Параметрический" он потому, что параметризуется код (single piece of code), а не тип.
S>>Определение касается вообще куска кода и способа сделать его обобщенным. Так что, что шаблон, что макрос, могут удовлетворять определению.
K>Не могут. Если вы определяете "параметрический полиморфизм" просто как однородный код, тогда под него подпадают и другие виды полиморфизма.
Нет, другие виды полиморфизма отличаются своими определениями. Ключевые отличия:
ad-hoc — используется специальная информация о типах (специализация)
subtyping — тут есть 2 определения. а) работает для всех подтипов автоматически; б) для ООП полиморфизма — с возможностью специализации кода для некоторых типов за счет override.
bounded — гибрид ПП и subtyping-а, где на типы накладываютя ограничения в виде обещаний о присутствии каких-то атрибутов(конструкторов/методов/и даже полей).
K>Но не шаблоны и не макросы, потому что с их помощью писать однородный код нельзя.
как это?
#define ID x = (x)
template <class T> T id(const T& v) { return v; }
Разве такие определения не позволяют использовать их однородно для различных типов?
Здравствуйте, samius, Вы писали:
S>Не согласен. Там написано что кусок кода типизируется "обобщенно", используя переменные вместо типов. И ничего о параметрических типах, кстати.
Ну и как такое протипизировать:
template <class C, class T>
void foo(int x)
{
C<T>::template Foo<T>::typename Bar y;
y.Baz = x;
}
Можно потом SFINAE ещё подкрутить и попробовать протипизировать. Не инстанцированную конкретную функцию, а обобщённый шаблон.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Не согласен. Там написано что кусок кода типизируется "обобщенно", используя переменные вместо типов. И ничего о параметрических типах, кстати.
VE>Ну и как такое протипизировать:
В TAPL написано "using variables in place of actual types".
VE>
VE>template <class C, class T>
VE>void foo(int x)
VE>{
VE> C<T>::template Foo<T>::typename Bar y;
VE> y.Baz = x;
VE>}
VE>
Т.е. оно уже typed "generically" согласно TAPL. Осталось лишь "and then instantiated with particular types as needed".
VE>Можно потом SFINAE ещё подкрутить и попробовать протипизировать. Не инстанцированную конкретную функцию, а обобщённый шаблон.
Не понятно, что значит попробовать протипизировать. Определение говорит о двух вещах:
1) использовать переменные вместо типов
2) инстанциировать с конкретными типами (подставить их вместо переменных).
Здравствуйте, samius, Вы писали:
VE>>Об этом же. Ставим C = int, T = float. Какой тип получился?
S>Фиговый какой-то. Но это же не повод обвинять C++ в отсутствии ПП! Мы ведь можем написать шаблон, который будет себе вполне соответствовать ПП.
Не фиговый, а нет такого типа, ошибка типизации, типизация не прошла. Типизация происходит только после подстановки. О чём и речь. Вы, конечно, можете написать такой шаблон, который при любом подставляемом типе таки стипизируется, но стипизируется он уже после.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>Об этом же. Ставим C = int, T = float. Какой тип получился?
S>>Фиговый какой-то. Но это же не повод обвинять C++ в отсутствии ПП! Мы ведь можем написать шаблон, который будет себе вполне соответствовать ПП.
VE>Не фиговый, а нет такого типа, ошибка типизации, типизация не прошла. Типизация происходит только после подстановки. О чём и речь. Вы, конечно, можете написать такой шаблон, который при любом подставляемом типе таки стипизируется, но стипизируется он уже после.
Что за "стипизируется"?
Пункт 1 — объявить переменные типов. Можно? Да. Язык C++ позволяет. Я не говорю о вашем примере. Я говорю о возможности.
Пункт 2 — подставить вместо переменных конкретные типы. Можно? Да.
А всякие там "до", "после", "стипизируется", "потенциальная бесконечность" — это уже спекуляции.
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Что за "стипизируется"?
VE>typed
S>>Пункт 1 — объявить переменные типов. Можно? Да. Язык C++ позволяет. Я не говорю о вашем примере. Я говорю о возможности.
VE>Не объявить, а be typed “generically,” using variables in place of actual types
Окей. В русской версии это выглядит как "давать участку кода “обобщенный” тип, используя переменные вместо настоящих типов".
S>>Пункт 2 — подставить вместо переменных конкретные типы. Можно? Да.
VE>Можно, но не всегда, ибо typed будет уже после подстановки, и может не сработать.
Никто же не утверждает что всегда, что любое использование шаблона есть ПП. Речь лишь о том, что ПП в C++ присутствует и _могут_ быть приведены примеры, где шаблон вполне соответствует определению ПП (которое не требует параметрических типов или бесконечностей типов в одной программе).
Здравствуйте, samius, Вы писали:
VE>>Не объявить, а be typed “generically,” using variables in place of actual types S>Окей. В русской версии это выглядит как "давать участку кода “обобщенный” тип, используя переменные вместо настоящих типов".
Но там нет типа. Там шаблон, а тип появляется после подстановки. И это ужасное C<T>::template Foo<T>::typename Bar превращается в MyStruct. А может и не превращается, и тогда получаем ошибку типизации.
VE>>Можно, но не всегда, ибо typed будет уже после подстановки, и может не сработать.
S>Никто же не утверждает что всегда, что любое использование шаблона есть ПП. Речь лишь о том, что ПП в C++ присутствует и _могут_ быть приведены примеры, где шаблон вполне соответствует определению ПП (которое не требует параметрических типов или бесконечностей типов в одной программе).
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
VE>>>Не объявить, а be typed “generically,” using variables in place of actual types S>>Окей. В русской версии это выглядит как "давать участку кода “обобщенный” тип, используя переменные вместо настоящих типов".
VE>Но там нет типа. Там шаблон, а тип появляется после подстановки. И это ужасное C<T>::template Foo<T>::typename Bar превращается в MyStruct. А может и не превращается, и тогда получаем ошибку типизации.
Если может превращается, а может и не превращается, то о каком ПП речь? ПП должен работать одинаково для всех типов. И если там для какого-то типа заложены знания что нечто ужасное превратится в MyStruct, а для другого не превратится, то это уже не ПП.
VE>>>Можно, но не всегда, ибо typed будет уже после подстановки, и может не сработать.
S>>Никто же не утверждает что всегда, что любое использование шаблона есть ПП. Речь лишь о том, что ПП в C++ присутствует и _могут_ быть приведены примеры, где шаблон вполне соответствует определению ПП (которое не требует параметрических типов или бесконечностей типов в одной программе).
VE>В таком смысле и в Си есть ПП VE>
VE>#define ID(x) x
VE>
В общем, кроме того что переменной типа не видно, других претензий в отношении определения ПП не могу предъявить. И сам уже писал что это является примером ПП, только скобочки забыл вокруг первого x.
Здравствуйте, samius, Вы писали:
S>ПП от СП я отличаю по определению (одного, другого). Ни то ни другое определение (в википедии, по крайней мере) не используют термин "типизация".
Но они только способом типизации однородного кода и отличаются.
S>К вам, собственно встречный вопрос. Как вы их отличаете?
Отличаю по способу типизации однородного кода. Другого-то способа нет.
S>Подод к вопросу тоже есть. Вы берете пример migmit-а, в котором задействованы bounded и subtype полиморфизмы, и утверждаете что ПП там нет в случае С++ и есть в случае C#.
То, что квантификация во всех примерах ограниченная я с самого начала говорил. Это ничего не меняет — кол-во "ограниченных" типов все равно бесконечно. Bounded-полиморфизм — это сочетание параметрического полиморфизма и сабтайпинга, т.е. наличие полноценного параметрического полиморфизма подразумевается. Никаких работающих примеров без ПП, например только subtype среди обсуждаемых нет.
S>Я же не утверждаю что он не существует. Я утвреждаю что существование такого типа не связано с определением. Тем более, не очевидно существование такого типа в конкретной программе.
Ну так тип объявлен в конкретной программе.
S>Обращу внимание, что в TAPL нет упоминания о типе, содержащем переменные.
Ну разумеется есть. Он процентов на 80 состоит из упоминаний "типа, содержащем переменные"
S>Мой основной довод — это то, что бесконечность типов не требуется. Попытку представить бесконечность типов в бесконечности программ предлагаю игнорировать.
Отлично.
S>Точно так же как и существование типа forall a. Succ a.
Т.е. в вашем определении ПП типизировать полиморфный код нельзя?
K>>Вы же прямо из TAPL сюда куски текста копируете, значит доступ к тексту у вас есть. А там все разжевано. S>А там об этом ничего нет.
Ну, это неправда.
S>Там говорится об использовании переменных вместо actual types, а вовсе не о типах, содержащих переменные.
Да ну, в главе о "типах, содержащих переменные" не говорится о "типах, содержащих переменные"?
S>Не согласен. Там написано что кусок кода типизируется "обобщенно", используя переменные вместо типов. И ничего о параметрических типах, кстати.
А что такое параметрические типы, по-вашему? Ну, скажу тогда, что шаблонный код "обобщенно, используя переменные вместо(?) типов". "Вместо типов" — вообще анекдот. не вместо типов вообще, а вместо конкретных типов. Переменная типа — это тип. Посмотрите, где семантика расписана переменные типа справа от ( — операции типизации.
S>Я думаю, что если бы трактовка "typed generically" была бы именно такой, то в переводе это было бы как-то отражено. В переводе же "типизация" вообще куда-то пропала.
Какой смысл рассуждать о том, сколько ангелов поместится на выдернутом из контекста абзаце, когда в его непосредственной окресности совершщенноь ясно описывается, с примерами и что такое тип и что такое типизировать и вообще все остальное, что вы сейчас выводите из каких-то странных предположений о переносе смысла при переводе.
S>Тогда мы в с++ и C# имеем forall, который непонятно чем отличается от forall H98
Нету в C++ квантора всеобности, о чем и разговор.
S>Я влез в эту тему что бы выяснить вашу позицию по поводу разграничения C++ и C# на тему присутствия в них ПП. Вы глядя на примеры migmit-а утверждаете что в С# ПП есть, а в С++ — нет. Вот мне и захотелось уточнить, почему вы так считаете.
Потому, что код, написанный, исходя из предположения, что полиморфизм в C# параметрический работает так же, как и в других языках с параметрическим полиморфизмом и в соотвествии с теорией, построеной на свойствах параметрического полиморфизма. С другой стороны, код, написанный, исходя из предположения, что полиморфизм в C++ параметрический не работает так же, как и в других языках с параметрическим полиморфизмом и не соотвествует теории, построеной на свойствах параметрического полиморфизма.
S>Аргументами была бесконечность типов. Теперь же выяснилось (или как минимум, допустилось), что C# своей потенциальной бесконечности типов обязан особенностям рантайма и без них пример migmit-а работать не будет.
Т.е. в каком-то гипотетическом рантайме гипотетического языка (который C# не является потому, что у него другая система типов и семантика) может не быть параметрического полиморфизма. Какой из этого следует вывод?
S>Согласен, в общем случае не нужна и для C# не нужна тоже. Поддержка рантайма нужна для работы примера migmit-а, связь которого с определением ПП туманна. Там ПП присутствует в форме bounded полиморфизма с замесом на subtype полиморфизм.
Ну а на Хаскеле пример работает без поддержки рантайма. Ну так что, нужна поддержка рантайма для этого или нет?
K>>Приведены примеры реализаций ПП без всякой рантайм-кодогенерации. Что в C++ есть ПП? Нет, эти два вопроса вообще никак не связаны. S>Да, приведены. В С++ ПП есть, но это действительно не связано с примерами ПП без рантайм-кодогенерации. Тем более, что в C++ этот ПП присутствует без всякой рантайм-кодогенерации.
Ну, если определять ПП как "что-то, что есть в C++" тогда да.
S>А что мне на них отвечать? Они есть, я с этим спорить не собирался. Да, то что ПП должны применяться в рантайме — я такого не утвреждал, если что. Утверждал я лишь то, что в примере migmit-а (и в моем тоже) типы в C# применяются в рантайме.
Применение типов в рантайме в C# отличается от применения в компайл-тайме синтаксически:
Перепутать их никак невозможно. Кроме того, если бы типы применялись в рантайме — тайпчекер не смог бы проверить их. "Код генерируется в рантайме" — это не то же самое, что и "Типы применяются в рантайме".
K>>Ну, перепишите код функции так, чтоб она генерировала неравные списки. С того и будут. И будет ошибка компиляции. S>А, без проблем. Только к какому выводу это приведет? В C++ тоже ведь будет ошибка компиляции.
На C++ не написать код, который правильно работает.
S>Суть возражения была в том, что обсуждаемая фича в C# работает за счет рантайм конкретизации и кодогенерации. Без этих фич C# в отношении обсуждаемых примеров будет столь же бесполезен, как и C++.
Именно в C# фича работает, потому что он компилируется в байткод с ПП. Что и где там потом генерируется из совсем другого языка к делу отношения не имеет.
K>>Я сказал. В определении параметрического полиморфизма так написано. Поэтому он и "параметрический" и отличается от других видов полиморфизма. S>Нет, в определении такого не написано. Ни в википедии, ни в TAPL.
В TAPL совершенно точно написано. Определение в википедии не читал.
S>"Параметрический" он потому, что параметризуется код (single piece of code), а не тип.
Чем код-то параметризуется?
S>>>Определение касается вообще куска кода и способа сделать его обобщенным. Так что, что шаблон, что макрос, могут удовлетворять определению.
S>Нет, другие виды полиморфизма отличаются своими определениями. Ключевые отличия: S>subtyping — тут есть 2 определения. а) работает для всех подтипов автоматически;
Здравствуйте, samius, Вы писали:
VE>>Но там нет типа. Там шаблон, а тип появляется после подстановки. И это ужасное C<T>::template Foo<T>::typename Bar превращается в MyStruct. А может и не превращается, и тогда получаем ошибку типизации. S>Если может превращается, а может и не превращается, то о каком ПП речь? ПП должен работать одинаково для всех типов. И если там для какого-то типа заложены знания что нечто ужасное превратится в MyStruct, а для другого не превратится, то это уже не ПП.
Здравствуйте, Klapaucius, Вы писали:
K>То, что квантификация во всех примерах ограниченная я с самого начала говорил. Это ничего не меняет — кол-во "ограниченных" типов все равно бесконечно. Bounded-полиморфизм — это сочетание параметрического полиморфизма и сабтайпинга, т.е. наличие полноценного параметрического полиморфизма подразумевается. Никаких работающих примеров без ПП, например только subtype среди обсуждаемых нет.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, samius, Вы писали:
S>>ПП от СП я отличаю по определению (одного, другого). Ни то ни другое определение (в википедии, по крайней мере) не используют термин "типизация".
K>Но они только способом типизации однородного кода и отличаются. K>Отличаю по способу типизации однородного кода. Другого-то способа нет.
Способ подразумевает какой-то процесс. А я в TAPL не нашел упоминаний процесса типизации. Там есть отношение типизации (8.2), определяемое набором правил вывода, присваивающих термам типы.
K>То, что квантификация во всех примерах ограниченная я с самого начала говорил. Это ничего не меняет — кол-во "ограниченных" типов все равно бесконечно. Bounded-полиморфизм — это сочетание параметрического полиморфизма и сабтайпинга, т.е. наличие полноценного параметрического полиморфизма подразумевается. Никаких работающих примеров без ПП, например только subtype среди обсуждаемых нет.
Как интересно, рассматривать наличие ПП на примере, где он лишь подразумевается...
S>>Я же не утверждаю что он не существует. Я утвреждаю что существование такого типа не связано с определением. Тем более, не очевидно существование такого типа в конкретной программе.
K>Ну так тип объявлен в конкретной программе.
В моей конкретной программе его нет. Софистика?
S>>Обращу внимание, что в TAPL нет упоминания о типе, содержащем переменные.
K>Ну разумеется есть. Он процентов на 80 состоит из упоминаний "типа, содержащем переменные"
Увы, это не так. Может быть "термы, содержащие типовые переменные"?
S>>Мой основной довод — это то, что бесконечность типов не требуется. Попытку представить бесконечность типов в бесконечности программ предлагаю игнорировать.
K>Отлично.
S>>Точно так же как и существование типа forall a. Succ a.
K>Т.е. в вашем определении ПП типизировать полиморфный код нельзя?
Я все еще не понимаю, что значит "типизировать". (8.2.1) вводит понятия t is typable (or well typed). Используется это в контексте переменных типа следующим образом (22.2)
1. "Are all substitution instances of t well typed?"
2. "Is some substitution instance of t well typed?"
K>>>Вы же прямо из TAPL сюда куски текста копируете, значит доступ к тексту у вас есть. А там все разжевано. S>>А там об этом ничего нет.
K>Ну, это неправда.
Можно ссылку (хотя бы номер параграфа), где бы упоминались "типы, содержащие переменные"...
S>>Там говорится об использовании переменных вместо actual types, а вовсе не о типах, содержащих переменные.
K>Да ну, в главе о "типах, содержащих переменные" не говорится о "типах, содержащих переменные"?
... и номер главы о "типах, содержащих переменные"
S>>Не согласен. Там написано что кусок кода типизируется "обобщенно", используя переменные вместо типов. И ничего о параметрических типах, кстати.
K>А что такое параметрические типы, по-вашему? Ну, скажу тогда, что шаблонный код "обобщенно, используя переменные вместо(?) типов". "Вместо типов" — вообще анекдот. не вместо типов вообще, а вместо конкретных типов. Переменная типа — это тип. Посмотрите, где семантика расписана переменные типа справа от ( — операции типизации.
Параметрические типы в TAPL упомянуты лишь в главе 29 (Type Operators and Kinding), сильно после ПП. Так что я думаю что это оффтоп.
K>Какой смысл рассуждать о том, сколько ангелов поместится на выдернутом из контекста абзаце, когда в его непосредственной окресности совершщенноь ясно описывается, с примерами и что такое тип и что такое типизировать и вообще все остальное, что вы сейчас выводите из каких-то странных предположений о переносе смысла при переводе.
Можно координату абзаца, в котором описано что такое типизировать?
S>>Тогда мы в с++ и C# имеем forall, который непонятно чем отличается от forall H98
K>Нету в C++ квантора всеобности, о чем и разговор.
А в C# его тоже нет
S>>Я влез в эту тему что бы выяснить вашу позицию по поводу разграничения C++ и C# на тему присутствия в них ПП. Вы глядя на примеры migmit-а утверждаете что в С# ПП есть, а в С++ — нет. Вот мне и захотелось уточнить, почему вы так считаете.
K>Потому, что код, написанный, исходя из предположения, что полиморфизм в C# параметрический работает так же, как и в других языках с параметрическим полиморфизмом и в соотвествии с теорией, построеной на свойствах параметрического полиморфизма. С другой стороны, код, написанный, исходя из предположения, что полиморфизм в C++ параметрический не работает так же, как и в других языках с параметрическим полиморфизмом и не соотвествует теории, построеной на свойствах параметрического полиморфизма.
Можно показать мне место в теории, построенной на свойствах ПП? Где в TAPL (или еще где-то) сказано что такое в ПП должно работать, иначе это не ПП.
K>Т.е. в каком-то гипотетическом рантайме гипотетического языка (который C# не является потому, что у него другая система типов и семантика) может не быть параметрического полиморфизма. Какой из этого следует вывод?
Вывод тут следует такой: что пример на C# от примера на C++ отличается лишь тем, что его рантайм (не всякий, причем) позволяет применять/конкретизировать/инстанциировать типы в рантайме. А такое отличие не считается ключевым в контексте ПП, иначе, наверное, оно было бы где-то упомянуто. А значит, либо в C# ПП отсутствует, либо в C++ присутствует.
S>>Согласен, в общем случае не нужна и для C# не нужна тоже. Поддержка рантайма нужна для работы примера migmit-а, связь которого с определением ПП туманна. Там ПП присутствует в форме bounded полиморфизма с замесом на subtype полиморфизм.
K>Ну а на Хаскеле пример работает без поддержки рантайма. Ну так что, нужна поддержка рантайма для этого или нет?
C#-у нужна.
K>Ну, если определять ПП как "что-то, что есть в C++" тогда да.
А если определять ПП как "что-то, чего нет в C++"?
K>Применение типов в рантайме в C# отличается от применения в компайл-тайме синтаксически: K>
K>Перепутать их никак невозможно.
Ой ли? А вот представьте, что в Foo используется List<T>. Будет ли Bar подставлен в List<> в компайл-тайме, если Bar в Foo<> подставили в рантайме? По-моему ваш способ отличия не дает однозначного ответа
K>Кроме того, если бы типы применялись в рантайме — тайпчекер не смог бы проверить их.
Что там проверять-то? конкретный тип на констрейнты? Нечего делать. Это же не вывод типов!
Проверяет, не вопрос: http://msdn.microsoft.com/en-us/library/system.type.makegenerictype.aspx
обратите внимание на раздел исключений.
K>"Код генерируется в рантайме" — это не то же самое, что и "Типы применяются в рантайме".
Да, вещи разные, но применяются типы в обсуждаемом примере именно в рантайме. И код генерится тоже.
S>>А, без проблем. Только к какому выводу это приведет? В C++ тоже ведь будет ошибка компиляции.
K>На C++ не написать код, который правильно работает.
Не написать код, который правильно работает, используя рантайм сабтайп полиморфизм (virtual же), подразумевающий ПП и неограниченное кол-во типов. Это верно. Только причем здесь ПП?
Покажите мне пример, где в C++ не работает ПП в чистом виде без всяких виртуальностей, сабтайпингов и требований к применению типов во времени выполнения.
S>>Суть возражения была в том, что обсуждаемая фича в C# работает за счет рантайм конкретизации и кодогенерации. Без этих фич C# в отношении обсуждаемых примеров будет столь же бесполезен, как и C++.
K>Именно в C# фича работает, потому что он компилируется в байткод с ПП. Что и где там потом генерируется из совсем другого языка к делу отношения не имеет.
Я вообще пока не увидел, какое отношение эта фича имеет к присутствию/отсутствию ПП.
K>>>Я сказал. В определении параметрического полиморфизма так написано. Поэтому он и "параметрический" и отличается от других видов полиморфизма. S>>Нет, в определении такого не написано. Ни в википедии, ни в TAPL.
K>В TAPL совершенно точно написано. Определение в википедии не читал.
А TAPL?
"parametric type" упоминается в целой книге целых два раза (один раз в главе 29, другой — в индексе). Так что, я смотрю в книгу на опеределение ПП и совершенно точно (ц) вижу что там нет упоминания parametric type.
S>>"Параметрический" он потому, что параметризуется код (single piece of code), а не тип.
K>Чем код-то параметризуется?
переменной
S>>Нет, другие виды полиморфизма отличаются своими определениями. Ключевые отличия: S>>subtyping — тут есть 2 определения. а) работает для всех подтипов автоматически;
K>Ну я и говорю. Типизацией.
K>Вот вам две length без сигнатур типа: K>
K>Где здесь параметрический полиморфизм, а где полиморфизм через сабтайпинг?
1) не смог найти отличий в двух функциях
2) не обнаружил подтипы
3) полагаю что в обоих случаях параметрический
S>>как это? S>>
S>>#define ID x = (x)
S>>template <class T> T id(const T& v) { return v; }
S>>
S>>Разве такие определения не позволяют использовать их однородно для различных типов?
K>Сравнивать разновидности полиморфизма — свойства системы типов — без обсуждения типизации я вообще считаю бессмысленным.
Это не мешает вам рассуждать об присутствии разновидности полиморфизма. И я так и не понял, какой вы вкладываете смысл в слово "типизация". Ниужели type checking? Вот, нашел вашу фразу (вырвал из контекста)
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Если может превращается, а может и не превращается, то о каком ПП речь? ПП должен работать одинаково для всех типов. И если там для какого-то типа заложены знания что нечто ужасное превратится в MyStruct, а для другого не превратится, то это уже не ПП.
VE>
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Уже только x.Foo(); говорит что это не ПП.
VE>Не совсем. То, что это не ПП, говорит хотя бы template <class T> VE>Ибо ну кто мешает сделать template <> ...?
Никто не мешает. Но пока известно что это не сделано в неком множестве исходников конкретной программы, имеем право рассматривать другие причины сомневаться в присутствии ПП в неком куске кода, например, ad-hoc полиморфные выражения, вызовы перегруженных функций...
VE>Но даже несмотря на это хотелось бы посмотреть на какой-нибудь пример с так называемым "ПП" кроме T & id(T & arg) { return arg; }.
их можно привести потенциально бесконечное число:
template<class T, class X>
T & Const(T & arg, X&) { return arg; }
А вот с реально полезными примерами чистого ПП не так весело. Впрочем, если ограничиться в рассмотрении некоторыми хорошими типами (с копированием, присваиваниями и т.п.), то множество полезных примеров резко расширяется, но это уже будет bounded полиморфизм.
ME>скажем, можно вспомнить мемоизацию результата чистой функции -- она мутирует данные, но тем не менее не мешает чистоте; с другой стороны, если компилятор не сможет отловить ошибку программиста в *этом* сценарии (когда, скажем, по ошибке из кэша выдается мемоизированный результат не для данной функции, а для другой) -- то это неполноценность языка
Верифируемое множество кода всегда отстает от множества кода, которое выдает правильный результат.
Соответственно, возникает вопрос: можно ли разрешать программисту писать код, которые не полностью верифицируем компилятором, но программист мамой клянется, что он выдает правильный результат?
на практике удобнее и эффективнее второе, и этим хаскель не удобен. например, чистый внешний вызов без танцев с бубном нельзя обернуть в чистую haskell-функцию.
V>Мое определение чистоты сильно короче. Чистая ф-ия возвращает один и тот же результат на одних и тех же аргументах.
Не очень хорошее определение (оно неустойчивое: с одной стороны — одно считает чистым, с другой стороны — тоже самое считает не чистым).
Это определение лучше(более обобщенная форма), чем следующий вариант:
> Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices
это тоже неустойчивое определение.
По этим определениям получается, что, например, sin x становится грязной функцией, как только в язык добавляется возможность измерять температуру процессора (или считывать счетчик выполненных процессором команд)
Чтобы определение чистоты было устойчивым — необходимо еще фиксировать множество операций для которого определяется чистота функции.
sin x чистая функция относительно множества математических операций, и нечистая относительно операций читающих состояние процессора или "состояние времени" (sin x при одном и том же x — может выполняться разное кол-во времени)
Функция, мемоизирующая свой результат при первом вызове является чистой функцией с точки зрения почти всех операций, и не является чистой функцией с точки зрения следующих операций: проверка результат функции уже вычислен или еще нет, сколько памяти отъела программа
Сложный пример для понимания: функция random — нечистая функция с точки зрения операций чтения счетчика random-а и операций завязанных на конкретное значение результата random-а, но random — чистая функция с точки зрения всех остальных операций. В частности, это проявляется в следующей композиции: is-number(random()), которая является чистой функцией уже с точки зрения почти всех операций.
K>Под параметрическим полиморфизмом я понимаю общепринятое определение. Т.е: K>1) Воможность написать однородный (работающий одинаково для любого типа) код K>2) Типизировать этот код обобщенным типом, содержащим переменные, которые конкретизируются применением обобщенного типа к конкретному (или другому обобщенному, если ПП первоклассный, т.е. импредикативный)
следующие функции по этому определению полиморные или нет? и почему?
bool IsInt32Enumerable<T>(IEnumerable<T> items)
{
if (typeof(T) == typeof(int))
return true;
return false;
}
int Count<T>(this IEnumerable<T> items)
{
var collection = items as ICollection<T>;
if (collection != null)
return collection.Count;
return items.Aggregate(0, (a, _) => a + 1);
}
основной вопрос: можно ли строго определить, что такое "работающий одинаково для любого типа"?
зы
имхо, строгое определение должно быть другим: код является полиморфным, если он не требует дописывания при введении новых типов(или другими словами: описывает поведение законченным образом для всего многообразия типов). И соответственно, не важно код работает одинаково или по разному для разных типов.
Здравствуйте, VoidEx, Вы писали:
VE>Скажите пожалуйста, а switch-case для enum в Си++ — это тоже динамическая типизация?
Тоже. Но сравнение я приводил 1. для статической диспетчеризации в рантайм (для привденной попытки на C++), или для табличной диспетчеризации. И даже говорил, что именно я сравниваю — ветвление ныне дорого из-за длинных конвейеров внутри процессоров.
V>>Ты мне покажи вот так: V>>main' :: Integer -> Integer -> a -> b -> Integer V>>Потом подай на a и b одинаковые типы извне. Вот это и будет настоящий ПП, а не тот, которым вы хотите незаслуженно обозвать систему типов Хаскеля.
VE>Не понял. Что должна делать такая функция? Ругаться при компиляции на разные типы? Или молча жрать все? Тогда в чём замысел?
Например том, чтобы она смогла быть применена в таком контексте, где a и b могут выводиться как одни и те же типы, а могут и нет. Речь идет о возможностях техники навроде static_if в С++. Возможностей Хаскеля для сравнимой реализации такой техники недостаточно, т.к. нет аппарата compile-time вычислений.
Здравствуйте, DarkGray, Вы писали:
DG>на практике удобнее и эффективнее второе, и этим хаскель не удобен. например, чистый внешний вызов без танцев с бубном нельзя обернуть в чистую haskell-функцию.
Какие танцы? unsafePerformIO и есть "мамой клянус".
Здравствуйте, DarkGray, Вы писали:
DG>Сложный пример для понимания: функция random — нечистая функция с точки зрения операций чтения счетчика random-а и операций завязанных на конкретное значение результата random-а, но random — чистая функция с точки зрения всех остальных операций. В частности, это проявляется в следующей композиции: is-number(random()), которая является чистой функцией уже с точки зрения почти всех операций.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, VoidEx, Вы писали:
VE>>Скажите пожалуйста, а switch-case для enum в Си++ — это тоже динамическая типизация?
V>Тоже. Но сравнение я приводил 1. для статической диспетчеризации в рантайм (для привденной попытки на C++), или для табличной диспетчеризации. И даже говорил, что именно я сравниваю — ветвление ныне дорого из-за длинных конвейеров внутри процессоров.
Тогда if-else тоже динамическая типизация.
VE>>Не понял. Что должна делать такая функция? Ругаться при компиляции на разные типы? Или молча жрать все? Тогда в чём замысел?
V>Например том, чтобы она смогла быть применена в таком контексте, где a и b могут выводиться как одни и те же типы, а могут и нет.
Зачем?
Тем более, что это не ПП, а речь-таки о нём шла.
V>Речь идет о возможностях техники навроде static_if в С++. Возможностей Хаскеля для сравнимой реализации такой техники недостаточно, т.к. нет аппарата compile-time вычислений.
Во-первых, как это нету?
Во-вторых, можно пример, когда static_if не является сам по себе костылём?
Здравствуйте, Klapaucius, Вы писали:
V>>В каких разных? Продемонстрируй плиз разные пространства имен для конструктора АлгТД и для одноименного идентификатора метки типа в конструкции ПМ некоего значения, созданного с помощью этого конструктора. K>Я говорил про имя типа и имя конструктора.
Я задал вполне конкретный уточняющий вопрос в ответ на твое отрицание. Т.е. ты или соглашаешься или приводишь своё отрицание целиком.
V>>Эээ... колега, а что вообще могло бы означать "упаковка другого типа" для АлгТД? K>Вот и мне интересно. Это ведь вы утверждаете, что конструкторы АлгТД — типы.
Чего-чего?
V>>А если так перефразировать: V>>Матчинг АлгТД в Хаскеле аналогично проверяет в рантайм дискриминатор объединения, то бишь тип запакованного значения.
K>Дескриминатор-то он, разумеется, проверяет в рантайме. А тип "запакованного" значения — нет. О чем и весь этот разговор.
А, кажется понимаю, где нестыковка. В общем, я всегда считал, что проверка типов рантайме (например для ветвления по типу в некоем контексте) — это всегда проверка некоего уникального значения, служащего идентификатором проверяемого типа. Тогда ты должен пояснить, что же по-твоему есть проверка типов в рантайм.
K>>>Ну, может и можно сказать, что конструктор типа "упаковывает" типа по аналогии с тем, как конструктор "упаковывает" значение. Вот только конструкторы типов в рантайме не матчатся (в рантайме их нет). V>>А как конструкторы могут матчиться? K>Ну вот, приехали. А что, что-то может матчиться кроме конструкторов?
Да я уже понял абзацем выше, что приехали. Функция-конструктор не может мачиться, бо в месте матчинга ее нет. Чудес не бывает, сорри — технически может матчиться только некий рантайм-признак типа.
V>>Ведь конструктор — это ф-ия. K>Совсем не обязательно. В ocaml, например, конструктор функцией не является.
А чем является конструктор алгТД в Caml?
K>Но даже если всякий конструктор — функция, то все равно не всякая функция — конструктор.
А это при чем? Интересует только первая часть фразы.
V>>Может матчиться лишь некое ID размеченного объединения, то бишь матч всегда идет по ЗНАЧЕНИЮ K>Правильно. Имя конструктора и есть этот ID.
Т.е. ты таки решил согласиться, что имя функции-конструктора используется в кач-ве метки типа в одном из контекстов (в ПМ)? Бо этот ID — это по-определению рантайм-метка типа, завернутого в АлгТД.
V>>в данном случае это значение метки типа. Т.е. можно сказать, что идет матч по типу завернутого значения. K>Это значение к метке типа не имеет никакого отношения. Т.е. так сказать нельзя.
Я не услышал объяснения наличия какой-то другой метки типа, к которой эта не имеет отношения. Есть только эта и никакой больше.
K>
K>-- так можно
K>data FooBar = Foo Int | Bar Int
K>-- так нельзя
K>data Foo = Foo Int | Foo Bool
K>
K>т.е. в рантайме проверяются не типы. ч.т.д.
При чем тут рантайм, если у тебя ошибка компиляции во втором случае? Не хочешь привести сообщение об ошибке компиляции?
V>>Угу, ты повторно налегаешь на подтипирование, хотя я его не упоминал. K>Постоянно упоминаете, когда говорите о проверке метки типа в рантайме.
Да, но только техника подтипирования здесь не при чем. Подтипирование вроде бы предполагает совместимость типов, а определение АлгТД этого не требует, хоть и не запрещает в некоей реализации.
В общем, подтипрование — это лишь одна из техник реализации АлгТД, возможная в некоей системы типов с подтипами. И то с ограничениями, например в С++ подтипирование работает только по ссылке или по указателю и никогда по значению.
V>>Я догадываюсь, что ты намекаешь на реализацию наподобие АлгТД в Немерле, но я-то здесь при чем? K>Не намекаю. Я объясняю, что является типом в хаскеле, а что не является.
Речь была о метке типа АлгТД, а не о том, что является типом, а что нет. Например, сигнатура ф-ии — является вполне конкретным типом в дотнете, и этот тип проверяется в compile-time при создании делегатов, но для этого типа не существует дескриптора типа, в отличие от подтипов Object. Т.е. не все типы должны обязательно должны иметь метки в рантайм. В С++ тоже, dynamic_cast работает не для всех типов, а только для некоторого класса пользовательских, имеющих виртуальные ф-ии.
K>Тип — это не всякое множество, а множество, пренадлежность к которому может проверить тайпчекер. Статически.
Ну так техника АлгТД нужна как раз чтобы обойти такое ограничение, т.е. чтобы сложить несколько типов в один "мешок", и протащить мимо таких проверок. Помню твою цепетильность — протаскивается значение, разумеется. В этом случае проверяется лишь то, чтобы в этот мешок не попало значение постороннего типа, а только из объявленного в ограничениях варианта. Затем этот мешок надо раскрыть в рантайм и применить такую проверку уже к содержимому де-факто, что и делается в конструкции ПМ. Естествено, что для работоспособности всей схемы необходимо закодировать признак типа вместе с данными. В схеме АлгТД этот признак существует явно, согласно определению АлгТД.
V>>data List a = Nil | Cons a (List a) V>>Для значения дескриминатора Nil_tag хранится tuple(/*empty*/), для значения Cons_tag хранится tuple(a, List<a>). Оба тупла и есть хранимые значения. Но сии значения-туплы имеют тип, как ни крути. K>Туплы имеют тип. Но этот тип не находится с алгтд в отношениях подтипирования.
Я уже не первый раз спрашиваю. Можешь, интереса ради, целиком раскрыть свою мысль насчет подтипирования? Я действительно не понимаю, при чем тут АлгТД и подтипирование (ну кроме как в плане одной из возможных техник реализации).
Я когда-то натыкался на мозгоправство, что мол конструкторы АлгТД — это конструкторы данных, а не конструкторы типов. Большей ереси я еще не слышал. Результатом, возвращаемым этим конструктором, будет именно значение целевого типа АлгТД, а не какого-то еще особенного типа данных.
V>>И да, от техники подтипирования в том же Немерле такая схема по-сути не отличается. Хоть ты и додумал за меня малость, насчет подтипирования, но происходящие в обоих случаях проверки типов при паттерн-матчинге — это суть проверки значения некоей метки. Вся разница лишь в том, что в Хаскеле метка представлена "унутре" интегральным значением, а в Немерле — адресом (сылкой) на дескриптор типа. По идее, тоже интегральным значением (адреса).
K>В хаскеле (ghc) эта "метка" как раз и представлена ссылкой на дескриптор блока памяти и (в случае небольшого числа конструкторов) тегом в указателе на блок.
Необязательно. Некоторые типы данных он "знает в лицо" и метка типа представлена интегральным значением, скажем так, заведомо ограниченного мн-ва, т.е. мн-ва ограниченной разрядности. Речь о char или int. Более того, если АлгТД имеет всего один конструктор, то метки может не быть вообще, т.к. "кот в мешке" заведомо один. Эту технику можно использовать для объявления уникального типа тулпа для прикладных целей, т.к. обычный алиас тупла не вводит уникальный тип, т.е. бесполезен для использования тайпчекера для прикладных целей.
K>>>Ну так boost::variant это не сумма, а объединение — чего вы от него хотите. variant< int, bool > это int или bool. V>>Не так, это еще хранимый признак типа, как и положено. Поэтому это или container<0, int> или container<1, bool>. Т.е. в плане хранения — дотягивают. Они недотягивают в момент диспетчеризации по конкретным хранимым типам, бо для одного и того же типа будет вызвана одна и та же ветка диспетчеризации K>Вот вы и описываете разницу между сабтайпингом (boost::variant) и алгебраическим типом. Еще одно различие. Через параметр типа variant< int, bool > можно передать и int и bool. Потому, что любые значения этих типов являются также и значениями вариантного типа. Это и называется "отношение подтипирования". Между типами "упакованными" в сумму и суммой такого отношения нет. Через параметр типа Either Int Bool нельзя передать ни значение типа Int, ни значение типа Bool. Только Either Int Bool.
Ээээ... похоже, ты не понимаешь, что есть подтипирование. Ты не можешь передать int туда, где ожидается variant<int, bool>. Ты можешь подать только variant<int, bool>. Это в Немерле возможен такой трюк, правда ни Boolean ни Int32, ни какой другой имеющийся тип использовать в такой сумме не выйдет. Нужно будет объявлять пользовательские типы прямо в варианте, которые подтипы некоего MyVariant. Причем, обязательно ссылочные.
V>>Тем более, что обсуждаемый пример можно переписать на АлгТД и обойтись без классов типов. K>Нельзя. Потому, что нам нужны тайплевел вычисления, а не рантайм-проверка.
Можно, добавив фиктивные тайлевел вычисления, не участвующие в рантайм-вычислениях. Я имел ввиду организацию рекурсивного типа. Сейчас эта рекурсия выполнена на полиморфном аргументе, а можно было сделать явно на алгТД.
V>>Для тега алгТД место в рантайме таки требуется. K>Никакие "Теги" АлгТД в рантайме не хранятся. Есть ссылка на InfoTable. У каждого вида лайаута данных в памяти — своя таблица.
Чем тебе ссылка не тег? Тег — это метка, по русски. Я не хотел делать конкретных предположений относительно устройства метки и уже объяснил почему: не всегда эта метка существует в рантайм (например, если в варианте всего один конструктор), и даже если она есть, она может иметь разную физическую природу для встроенных или пользовательских типов. Пусть будет просто метка (тег) типа, согласно определению АлгТД. А как ее реализует конкретный компилятор — да пофиг вообще.
V>>Или ты считаешь, что процедура сравнения тега размеченного объединения и распаковка затем хранимого значения чем-то отличается от динамической типизации? Это абсолютно такое же кол-во телодвижений за ту же стоимость. K>То, является что-то типизацией или не является не определяется стоимостью. Типизация — это проверка типа. Если мы проверяем значения на совпадение с BoxInt, true или 0 — это не проверка типа потому, что все это не типы. Не всякая проверка — проверка типа.
Разве вот это не проверка типа:
if(typeof(T) == typeof(int)) {...}
Это ровно тоже самое, что происходит в ПМ, с поправкой на ветер, т.е. на механику Хаскеля. И даже больше — я привел проверку, которая могла быть вполне статической.
V>>Именно. А приводимый пример фактически надуманный, т.к. что там проверять-то?, коль списки формируются параллельно. K>Ну так то, что списки формируются параллельно и проверяется.
K>Ну так смысл проверки типов в том, чтоб проверять соответствие функции типу, которым ее проаннотировали. Если функция не будет строить списки параллельно — будет ошибка компиляции.
Не спорю, но при чем тут полноценный ПП?
V>>Угу, как возражение, что при вызове ф-ий используется неявное ПМ привел явную реализацию на ПМ. Поздравляю. K>Привел реализацию в которой явно видно, что есть проверка счетчика для прерывания рекурсии, а никакого "выбора функции из группы" нет.
Выбор есть. Просто ты не понимаешь термина "выбор функции из группы". Одноименные ф-ии в одной области видимости составляют группу ф-ий. В С++ выбор происходит статически для обычных ф-ий или динамически для виртуальных. В Хаскеле почти любая ф-ия может быть выбрана статически или динамически, через неявный ПМ. Как этот ПМ происходит — ты примерно и показал. С чем спорил тогда?
K>Да, смешно читать, что человек может нафантазировать про тайпклассы, не зная, как они работают. V>>Каким образом? Чтобы выбор сделать, надо отказаться от боксированного представления, т.е. заранее знать, где конец списка. K>Не нужно знать, где конец списка. Мы начинаем построение с Nil — это статически известно. выбираем одну функцию и подставляем ее вместо функции тайпкласса. Во всех остальных случаях будет функция для Cons — одна и та же — подставлем ее. Все, вся диспетчерезация закончилась на этапе компиляции.
И куда мы "подставляем" ф-ию в данные? Ф-ия в данные подставляется через адрес, что и называется диспетчеризация. Или же через аналог неявно вставленного if — и это тоже есть диспетчеризация. Техника исполнения не принципиальна.
Я тебя спросил — покажи как это работает в рантайм? Ведь вызываемая затем ф-ия scalarProduct — рекурсивная, а глубина рекурсии заведомо неизвестна. Т.е. я понимаю твои предположения, если полностью раскрутить и переписать рекурсию, просчитав всё с конца списка. Давай тогда добавим побочный эффект в код scalarProduct, чтобы избежать этого. Думаю, добавление IO не должно сломать тайпчекер.
Более того, сами списки могли быть попеременно составлены из разных "сегментов", например из float и т.д., и для каждого сегмента мог быть определена своя рекурсивная реализация scalarProduct. Как будет работать в этом случае? Твои предположения? Или если есть возможность — сгенери хасклеем сишный код (вроде так умеет), посмотрим на происходящее.
K>Ну так в этом проверка типа и заключается. Если в функции не будет "черным по белому сказано", что списки одинаковой длины — будет ошибка компиляции.
Для формируемых параллельно списков, это, прямо скажем, небольшая прикладная ценность. А как только не параллельно — так сразу перестает работать.
V>>Ты мне покажи вот так: V>>main' :: Integer -> Integer -> a -> b -> Integer
K>Тут написано, что типы могут быть одинаковыми, а могут и раными — все равно. Что это проверяет-то?
Не надо проверять main', не он ведь целевой. Пусть проверяется scalarProduct. Т.е. пусть тело main' скомпиллируется только если в compile-time подать на a и b одинаковые типы.
V>>Ну коль Cons a параметризируется типом Cons a', то у нас выходит, скажем, де-факто параметрический рекурсивный тип в примере.
K>Отличная шутка! А любая функция, у которой область определения — подмножество области значений — "де-факто рекурсивная"? А то ведь ее можно так применить f(f(х))
Такое применение — это и есть де-факто рекурсия. В примере акцент не на том, что тип бъявлен как рекурсивный, а что рекурсия вышла де-факто, потому что N заведомо неизвестен. Вроде мы уже пару раундов назад согласились с тем, что раз N заведомо неизвестен, то карта данных в памяти может быть только рекурсивной и никак иначе.
Ну и опять же, на С++ пытались реализовать именно на технике такой де-факто рекурсии, а не на честном рекурсивном типе. Т.е. попытка была нерелевантна с твоей т.з.? Смысл было тогда обсуждать С++ реализацию?
K>>>Дескриминаторов типа в Хаскеле просто не существует V>>True, False, Cons, Nil — вот тебе существующие в рантайм дискриминаторы, хранимые вместе с данными. K>Это и есть данные. Не типы.
Это метки типов АлгТД, как бы это не разрывало твой шаблон.
V>>Это конструкторы АлгТД в одном контексте и теги АлгТД, то бишь теги хранимого типа — в другом, например в конструкции ПМ. K>Еще раз. Медленно. Теги конструктора и теги типа — разные вещи.
В смысле, по разному устроены в конкретном компиляторе? Не важно ни разу.
K>И в конструкции ПМ никакие типы не проверяются. Тип там один — тип матчимого АлгТД. Проверяется статически.
Ну да, тип "мешка" проверяется статически, что сказать-то хотел? А тип содержимого — динамически, увы.
V>>А что, тебя смущает вырожденный случай размеченного объединения навроде Bool? K>Ничем не смущает. Меня смущает, что вы True и False считаете типами.
Это метки типов. Типов пустых туплов. Я вижу, что таки минималистичность синтаксиса Хаскеля, т.е. вызов ф-ий без аргументов и даже без пустых скобок, тебя смущает. Например, что есть True? Это никакое не значение вне контекста ПМ, это вызов одного из конструкторов типа Bool. Осталось помедитировать над тем, что есть вызов конструктора типа.
DG>>Сложный пример для понимания: функция random — нечистая функция с точки зрения операций чтения счетчика random-а и операций завязанных на конкретное значение результата random-а, но random — чистая функция с точки зрения всех остальных операций. В частности, это проявляется в следующей композиции: is-number(random()), которая является чистой функцией уже с точки зрения почти всех операций.
VE>is-number = const true?
например, такая:
bool is-number(object value)
{
return value is int || value is double;
}
DG>>на практике удобнее и эффективнее второе, и этим хаскель не удобен. например, чистый внешний вызов без танцев с бубном нельзя обернуть в чистую haskell-функцию.
VE>Какие танцы? unsafePerformIO и есть "мамой клянус".
танцы в том, что компилятор проверяет или каждый чих на чистоту (без unsafePerformIO), или вообще чистоту не проверяет (с unsafePerformIO).
хотелось бы чтобы можно было зафиксировать — относительно каких операций чистота гарантируется и должна проверяться компилятором.
DG>>>Сложный пример для понимания: функция random — нечистая функция с точки зрения операций чтения счетчика random-а и операций завязанных на конкретное значение результата random-а, но random — чистая функция с точки зрения всех остальных операций. В частности, это проявляется в следующей композиции: is-number(random()), которая является чистой функцией уже с точки зрения почти всех операций.
Если я правильно понял мысль, композиция is-number(random()) остаётся грязной с т.з. операции чтения счётчика random, но становится чистой для функций, использующих значение всей этой композиции.
Здравствуйте, DarkGray, Вы писали:
DG>танцы в том, что компилятор проверяет или каждый чих на чистоту (без unsafePerformIO), или вообще чистоту не проверяет (с unsafePerformIO).
Ну есть ещё ST, например.
DG>хотелось бы чтобы можно было зафиксировать — относительно каких операций чистота гарантируется и должна проверяться компилятором.
Можно пример в псевдокоде? Как предполагается это указывать?
Вот, например, Disciple, диалект Хаскеля с системой эффектов.
VE>композиция is-number(random()) остаётся грязной с т.з. операции чтения счётчика random, но становится чистой для функций, использующих значение всей этой композиции.
да
ps
более точно:
var rnd = new Random();
is-number(rnd.Next()); //грязная относительно счетчика rndis-number(new Random().Next()); //чистая относительно всех операций (кроме каких-то специфических связанных с состоянием исполнителя)
ззы
вообще смысл в том, что как только переходим от черно-белого мышления(чистая/грязная) к полутоновому(чистая относительно определенного множества операций), то можно формализованно утверждать, что при композии грязь не только постоянно растёт, но и может пропадать при определенных замыканиях (или других инкапсуляциях)
VE>>композиция is-number(random()) остаётся грязной с т.з. операции чтения счётчика random, но становится чистой для функций, использующих значение всей этой композиции.
DG>да
DG>ps DG>более точно: DG>
DG>var rnd = new Random();
DG>is-number(rnd.Next()); //грязная относительно счетчика rnd
DG>is-number(new Random().Next()); //чистая относительно всех операций (кроме каких-то специфических связанных с состоянием исполнителя)
DG>
DG>ззы DG>вообще смысл в том, что как только переходим от черно-белого мышления(чистая/грязная) к полутоновому(чистая относительно определенного множества операций), то можно формализованно утверждать, что при композии грязь не только постоянно растёт, но и может пропадать при определенных замыканиях (или других инкапсуляциях)
Ну так в этом смысле у Haskell не только либо чистота, либо нет чистоты.
Вышеозначенный пример можно и в Haskell переписать.
В одном случае будет ST-ссылка на внешний счётчик, во втором — монада State.
Другое дело, что текущими средствами в Haskell не доказать (нет зависимых типов), что при любом значении состояния во втором случае (State) результат будет один и тот же.
С другой стороны, если например определить isNumber так, то можно пользоваться "мамой клянус"
class IsNumber a where
isNumber :: a -> Bool
instance IsNumber Int where
isNumber _ = True
needNoState :: (RandomGen g, Random a) => State g a -> a
needNoState _ = undefined
isNumber (needNoState rnd)
VE>Можно пример в псевдокоде? Как предполагается это указывать?
указывать их, конечно, не хочется. хочется чтобы они вычислялись автоматически
эффекты кладутся на систему типов, как некий довесок, существующих только при компиляции(сейчас это, вроде, kind-ом принято называть), в виде точного указания, какие состояния затрягиваются.
т.к. при композиции эффекты растут как снежный ком, то при композиции эффекты могут складываться с потерей точности.
например, при хардкодной оптимизации небольшого куска кода важно, как та или иная конструкция влияет на кэш процессора, регистры, fpu и т.д., но для большого куска кода достаточно знать что он "большой" (что он как-то сильно меняет состояние процессора)
VE>Можно пример в псевдокоде? Как предполагается это указывать?
вообще, что интересует? нотация, какая-то часть семантики?
Здравствуйте, VoidEx, Вы писали:
VE>Тогда if-else тоже динамическая типизация.
Если при этом проверяется внутреннее устройство данных, то бишь их тип — несомненно.
VE>>>Не понял. Что должна делать такая функция? Ругаться при компиляции на разные типы? Или молча жрать все? Тогда в чём замысел?
V>>Например том, чтобы она смогла быть применена в таком контексте, где a и b могут выводиться как одни и те же типы, а могут и нет.
VE>Зачем? VE>Тем более, что это не ПП, а речь-таки о нём шла.
V>>Речь идет о возможностях техники навроде static_if в С++. Возможностей Хаскеля для сравнимой реализации такой техники недостаточно, т.к. нет аппарата compile-time вычислений. VE>Во-первых, как это нету?
Так что нету. Потому что нет параметризации типов константами и нет compile-time диспетчеризации по этим константам и нет частичного или явного инстанциирования шаблонных типов для конкретных комбинаций аргументов. Есть только такое инстанциирование для ф-ий.
VE>Во-вторых, можно пример, когда static_if не является сам по себе костылём?
Ха. Тогда любая статическая система типов — это костыль сам по себе, т.к. это способ перенести ограничения прикладной логики на компилятор. Я могу спросить в ответ привести пример, когда классы типов Хаскеля не являются костылем? Вот это аналогично. static_if покрывают возможности ограничений, вводимых классами типов, + еще позволяют сверху наложить кучу других ограничений. На сегодня, используя boost::type_traits — выразить как ограничения фактически любые отношения м/у типами или мемберами типов в системе типов С++.
>> Ругать получившуюся систему типов можно будет затем лишь от непонимания причинно-следственных связей характеристик языка и подходящей под эти характеристики системы типов.
VE>Т.е. если я понимаю причинно-следственные связи характеристик языка, то ругать уже не буду вне зависимости от того, нравятся мне такие характеристики или нет? Интересное мнение.
Ес-но, если тебе некие характеристики языка не важны, но они, в свою очередь являются определяющими для системы типов, возможной в этих характеристиках, то надо брать другой язык. Т.е. сам смори что тебе первично — система типов инструментария или характеристики порождаемого инструментарием результата.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, VoidEx, Вы писали:
VE>>Тогда if-else тоже динамическая типизация.
V>Если при этом проверяется внутреннее устройство данных, то бишь их тип — несомненно.
Т.е. всегда. Ибо в чём фундаментальное отличие нижеприведённых примеров?
if (x < 0)
foo1(x) // x is (a:int){a<0}else
foo2(x) // x is (a:int){a>=0}
if (isChild1(p))
foo3(p) // x is Child1*else
foo4(p) // x is Child2*
Вы могли бы сказать, что в первом случае у нас используется само число, в во втором доступ к членам, но
1. Это далеко не всегда так, может, foo3 и foo4 этот указатель в списки добавляют
2. Разницы всё равно нет, так как foo1 и foo2 могут на основе значения бегать по таблицам, использовать числа как смещения и т.п. Грубо говоря вообще можно написать такой код, что foo1 и foo2 будут через таблицы эмулировать доступы в объектам.
VE>>Во-первых, как это нету?
V>Так что нету. Потому что нет параметризации типов константами и нет compile-time диспетчеризации по этим константам и нет частичного или явного инстанциирования шаблонных типов для конкретных комбинаций аргументов. Есть только такое инстанциирование для ф-ий.
Чой-то нету-то? Там вообще макросы есть, если что.
VE>>Во-вторых, можно пример, когда static_if не является сам по себе костылём?
V>Ха. Тогда любая статическая система типов — это костыль сам по себе, т.к. это способ перенести ограничения прикладной логики на компилятор.
Неверное обобщение. Если static_if используется для диспетчеризации, то для этого есть и другие механизмы. При этом с учётом того, что в Haskell никто не использует static_if, есть основания полагать, что все use-case покрываются другими средствами. Причём тут вся статическая типизация — не ясно.
V>Я могу спросить в ответ привести пример, когда классы типов Хаскеля не являются костылем?
А они костыль.
VE>>Можно пример в псевдокоде? Как предполагается это указывать?
DG>вообще, что интересует? нотация, какая-то часть семантики?
Какие типы "грязи" предлагаются? Зависимость от конкретной внешней переменной? Использование состояние? Ввод-вывод?
Вроде как в Disciple все функции могут иметь эффект, причём выводимый автоматически. Например putStrLn там имеет тип что-то вроде String -{!Console}-> ()
При этом в обычном коде это {!Console} писать не надо, хотя можно.
Посмотрите по ссылке (я выше давал), скажите, в чём не сходится с вашим представлением.
VE>>Т.е. всегда. Ибо в чём фундаментальное отличие нижеприведённых примеров?
DG>вроде о том и речь, что ПМ это лишь красивый сахар для последовательности if/else-ов
Если смотреть с этой стороны, то с этим никто не спорит. Спорят с тем, что это что-то вроде динамической типизации. Проблема в том, что термин "типизация" тут как-то не определили.
Если смотреть в целом, то всё-таки нет, ибо паттерны могут быть вложенными, есть view-patterns, в Agda для функции, определённой через ПМ можно проводить доказательства (т.к. например для оператора сравнения будет существовать утверждение типа succ x == succ y = x == y), а через if-else будет значительно сложнее (т.к. приведённое выше утверждение уже отсутствует и им не попользуешься при доказательствах, его само сначала придётся доказать), и т.п.
V>Я тебя спросил — покажи как это работает в рантайм? Ведь вызываемая затем ф-ия scalarProduct — рекурсивная, а глубина рекурсии заведомо неизвестна. Т.е. я понимаю твои предположения, если полностью раскрутить и переписать рекурсию, просчитав всё с конца списка. Давай тогда добавим побочный эффект в код scalarProduct, чтобы избежать этого. Думаю, добавление IO не должно сломать тайпчекер.
в данном случае речь о том, что scalarProduct в runtime-е будет следующим с гарантией, что First никогда не выкинет исключение, и что закоментированный if не нужен:
int scalarProduct(as, bs)
{
//if(as == Nil && bs != Nil || as != Nil && bs == Nil)
// throw new Exception("длина списков не совпадает");if(as == Nil)
return 0;
return as.First() * bs.First() + scalarProduct(as.Tail(), bs.Tail());
}
VE>Если смотреть с этой стороны, то с этим никто не спорит. Спорят с тем, что это что-то вроде динамической типизации. Проблема в том, что термин "типизация" тут как-то не определили.
в моем понимании, динамическая типизация — это когда в рантайме есть if на основе признака типа или виртуальный вызов на основе vtable типа. И то, и другое — роняет производительность.
а почти весь ПМ (при условии, что есть несколько веток) в это и транслируется.
VE>Если смотреть в целом, то всё-таки нет, ибо паттерны могут быть вложенными, есть view-patterns, в Agda для функции, определённой через ПМ можно проводить доказательства (т.к. например для оператора сравнения будет существовать утверждение типа succ x == succ y = x == y), а через if-else будет значительно сложнее (т.к. приведённое выше утверждение уже отсутствует и им не попользуешься при доказательствах, его само сначала придётся доказать), и т.п.
да, это всё есть и очень полезно, но динамическую типизацию уберет только в редких случаях
VE>Посмотрите по ссылке (я выше давал), скажите, в чём не сходится с вашим представлением.
сходу не вижу примера самого интересного, когда при композиции та или иная грязь пропадает
VE> isNumber (needNoState rnd)
тут основной подвох, что необходимо сначала в нужное место вставить нужный needNoState, а потом при рефакторинге в:
isNumber-Больше-Одной-Второй(needNoState rnd)
не забыть его убрать.
те же явные касты (вид в профиль) с теми же последствиями
VE>>Посмотрите по ссылке (я выше давал), скажите, в чём не сходится с вашим представлением.
DG>сходу не вижу примера самого интересного, когда при композиции та или иная грязь пропадает
Не уверен, что там это есть. Тогда соответственно назревает вопрос. Как вы предлагаете проверять то, что грязь убирается?
Можно на таком примере: isEven (rnd * 2)
Ну или на ваш вкус
VE>> isNumber (needNoState rnd)
DG>тут основной подвох, что необходимо сначала в нужное место вставить нужный needNoState, а потом при рефакторинге в: DG>isNumber-Больше-Одной-Второй(needNoState rnd) DG>не забыть его убрать. DG>те же явные касты (вид в профиль) с теми же последствиями
Явные касты — это благо. Именно потому, что при рефакторинге вас мордой ткнут и придётся убрать. А вот если молча проглотит, будет беда.
VE>>Если смотреть с этой стороны, то с этим никто не спорит. Спорят с тем, что это что-то вроде динамической типизации. Проблема в том, что термин "типизация" тут как-то не определили.
DG>в моем понимании, динамическая типизация — это когда в рантайме есть if на основе признака типа или виртуальный вызов на основе vtable типа. И то, и другое — роняет производительность.
Любой if — это вызов блока в if или блока в else на основе таблицы из true/false.
DG>а почти весь ПМ (при условии, что есть несколько веток) в это и транслируется.
Любая программа содержит условные переходы, но динамической типизацией это называть было бы крайне странно.
Поэтому первостепенной задачей было бы как раз решить, что же такое динамическая типизация.
То, что при наличии разных вариантов у АлгТД происходит ветвление — это динамическая типизация в той же мере, что и ветвление по 0 и не 0 в факториале. Назвать это динамической типизацией можно только при большой фантазии, да и то, с натяжкой.
С другой стороны динамическую типизацию можно было представить как кортеж из имени типа и значения.
Фундаментальную разницу я вижу в том, что при динамической типизации тип ровно один: dynamic.
VE>Явные касты — это благо. Именно потому, что при рефакторинге вас мордой ткнут и придётся убрать. А вот если молча проглотит, будет беда.
говоря явный каст, я имел ввиду reinterpreted_cast или dynamic_cast, т.е. такой cast валидность которого не проверяется компилятором
извини, что сказал двусмысленно.
VE> Любая программа содержит условные переходы, но динамической типизацией это называть было бы крайне странно. VE> Поэтому первостепенной задачей было бы как раз решить, что же такое динамическая типизация.
весь смысл статической типизации, что она может убирать if-ы
имея следующий код:
T Sum<T>(this IEnumerable<T> items)
{
var sum = default(T);
foreach (var item in items)
sum = Sum(sum, item);
return sum;
}
int Sum(int a, int b)
{
return a + b;
}
double Sum(double a, double b)
{
return a + b;
}
void Main()
{
Enumerable.Range(1, 10).Sum().ToConsole();
}
при сильной статической типизации можно ожидать, что при исполнении будет следующий код без всяких if-ов:
Int Sum_Int(this IEnumerable<int> items)
{
var sum = 0;
foreach (var item in items)
sum = Sum_Int(sum, item);
return sum;
}
int Sum_Int(int a, int b)
{
return a + b;
}
void Main()
{
Enumerable.Range(1, 10).Sum_Int().ToConsole();
}
без статической типизации при исполнении не будет ничего лучше, чем следующий код:
object Sum(this items)
{
var sum = null;
foreach (var item in items)
sum = Sum(sum, item);
return sum;
}
object Sum(a, b)
{
if (a is int && b is int)
return (int)a + (int)b;
if (a is double && b is double)
return (double)a + (double)b;
throw new Exception(..);
}
void Main()
{
Enumerable.Range(1, 10).Sum().ToConsole();
}
данный код достаточно сильно тормозит по сравнению с предыдущим, потому что на каждой итерации идут проверки.
и я утверждаю, что ПМ в основном именно в такой тормозной код и разворачивается.
Здравствуйте, DarkGray, Вы писали:
DG>весь смысл статической типизации, что она может убирать if-ы
Я бы сказал, что это побочный её эффект. Тем не менее в таком определении типизация не статическая или динамическая, а более или менее статическая.
В любой программе у нас так или иначе будут if'ы, без ветвлений ничего полезного не написать.
Мы могли бы ограничиться кортежами и целыми числами, и как будто бы и нет динамической типизации, но нам бы пришлось её эмулировать так или иначе через те самые if'ы.
АлгТД решает эту проблему. if при обработке АлгТД никуда не убрать, но его никуда не уберёшь в любой программе. Просто тут он в АлгТД, а там он был бы где-то ещё.
Тем не менее, в языке с зависимыми типами и теоремами и для АлгТД можно было бы выбрать ветку без if'а, но в частных случаях.
В общем, от ввода АлгТД "динамичности" не прибавляется, она просто перекочёвывает из if-else'ов в АлгТД, вот и всё.
DG>и я утверждаю, что ПМ в основном именно в такой тормозной код и разворачивается.
Нет. С ПМ у вас ровно первый вариант. Проверка на окончание итерирования аналогична проверке, Null ли список или Cons head tail. Функция суммирования же используется одна и никакого диспатча нет.
Я же утверждаю, что когда ПМ разворачивается в "такой тормозной код", то и без ПМ будет он же.
Основываюсь на том, что "хороший код" без ПМ можно перевести в код с ПМ, где классы перейдут в кортежи, а ветвления в варианты АлгТД (например nullable ссылки (ветвление по null и not-null) в Maybe).
VE>Нет. С ПМ у вас ровно первый вариант. Проверка на окончание итерирования аналогична проверке, Null ли список или Cons head tail. Функция суммирования же используется одна и никакого диспатча нет.
VE>Я же утверждаю, что когда ПМ разворачивается в "такой тормозной код", то и без ПМ будет он же.
с этим утверждением не поспоришь, если брать в общем виде.
я больше говорил в разрезе, что если брать ПМ как он реализован в том же Haskell-е, то он будет генерить тормозной код, не сильно лучше, чем какая-нибудь динамика аля питон.
в тоже время я согласен, что теоретически в ПМ информации содержится больше, и его проще конвертнуть во что-нибудь хорошее.
VE>>Я же утверждаю, что когда ПМ разворачивается в "такой тормозной код", то и без ПМ будет он же.
DG>с этим утверждением не поспоришь, если брать в общем виде.
Не понимаю только, как после этой фразы вы пишете сразу же ей противоречащую:
DG>я больше говорил в разрезе, что если брать ПМ как он реализован в том же Haskell-е, то он будет генерить тормозной код, не сильно лучше, чем какая-нибудь динамика аля питон.
Нет, он будет генерить код не тормознее любой другой статики. Разумеется, не в абсолютной величине, а относительно обсуждаемого вопроса. Т.е. если статика а-ля C# обходится 10 if'ами, ПМ тоже обойдётся ими (с учётом тех, что в АлгТД).
DG>в тоже время я согласен, что теоретически в ПМ информации содержится больше, и его проще конвертнуть во что-нибудь хорошее.
VE>Не уверен, что там это есть. Тогда соответственно назревает вопрос. Как вы предлагаете проверять то, что грязь убирается? VE>Можно на таком примере: isEven (rnd * 2)
для функций isEven и '*' можно восстановить условные pre/post-conditions
соответственно, на основе этих condition-ов для выражения isEven(rnd * 2) вообще вычисляется, что его post-condition: true
и что соответственно его можно заменить целиком на true при компиляции
VE>Ну или на ваш вкус
возьмем типичную ручную мемоизацию
class MyXY
{
public MyXY(object x)
{
this.x = x;
}
readonly object x;
public object Get()
{
if (_cache != null)
_cache = Zzz.My(x);
return _cache;
}
object _cache;
}
где Zzz.My — чистая функция возвращающая ненулевой результат
в этом коде Get.result = F(x, _cache, Zzz.My) и соответственно чистота(Get.result) = чистота(x) && чистота(_cache) && чистота(Zzz.My). x — чистая переменная, т.к. readonly, Zzz.My — чистая по определению, остался _cache.
_cache — может быть в двух состояниях: или null, или результат Zzz.My. Нулевое состояние _cache снаружи наблюдать(обнаружить) невозможно, т.к. для метода get: F(x, null, Zzz.My) == F(x, != null, Zzz.My), а других способов получить доступ к _cache нет.
Здравствуйте, VoidEx, Вы писали:
VE>Вы могли бы сказать, что в первом случае у нас используется само число, в во втором доступ к членам, но VE>1. Это далеко не всегда так, может, foo3 и foo4 этот указатель в списки добавляют VE>2. Разницы всё равно нет, так как foo1 и foo2 могут на основе значения бегать по таблицам, использовать числа как смещения и т.п. Грубо говоря вообще можно написать такой код, что foo1 и foo2 будут через таблицы эмулировать доступы в объектам.
Ну это ты далеко зашел в рассуждениях. Естественно, на любом тьюринг-полном языке можно написать некий интрепретатор другого тьюринг-полного языка с его системой типов, отличной от исходной. Этот случай обсуждать заведомо неинтересно.
V>>Я могу спросить в ответ привести пример, когда классы типов Хаскеля не являются костылем? VE>А они костыль.
А что не так без них?
Дело в том, что С++ static_if в большинстве случаев не нужен, он удобен лишь для диагностики, остальное система типов С++ разруливает и так. Т.е. всё, что может быть описано через static_if, может быть описано и без него или даже не описано совсем, а получится "по факту". Т.е. это не костыль, а сахар. А вот классы типов — натуральный костыль, без которого многие трюки в Хаскеле просто не взлетят. А костыль это потому, что изначально в языке отсутствовала концепция контрактов/интерфейсов. И вот ее заменяет этот недокостыль. Исключительно из-за скудной системы типов, где все пользовательские типы данных — это АлгТД.
Здравствуйте, VoidEx, Вы писали:
VE>Если смотреть с этой стороны, то с этим никто не спорит. Спорят с тем, что это что-то вроде динамической типизации. Проблема в том, что термин "типизация" тут как-то не определили.
Определили. Это проверка/тестирование типа в рантайм.
Да и о чем спорить? По-определению АлгТД — это такой тип данных, в который упаковываются значения других типов. Вариант, он и в Африке вариант.
VE>Если смотреть в целом, то всё-таки нет, ибо паттерны могут быть вложенными, есть view-patterns, в Agda для функции, определённой через ПМ можно проводить доказательства (т.к. например для оператора сравнения будет существовать утверждение типа succ x == succ y = x == y), а через if-else будет значительно сложнее (т.к. приведённое выше утверждение уже отсутствует и им не попользуешься при доказательствах, его само сначала придётся доказать), и т.п.
Во-первых речь о Хаскеле, во вторых, Agda — это язык с зависимыми типами, у него типизация зависит от логических утверждений относительно значений в коде, в т.ч. от значений дискриминатора АлгТД. В любом случае, динамическая типизация никуда не девается. Просто ПМ по АлгТД — это такой встроенный трюк, что в конкретной ветке ПМ мы уже опять имеем статическую типизацию, но уже по фактическому выясненному содержимому АлгТД (а не по исходному, завернутому типу). Компилятор лишь гарантирует нам типобезопасность через отсутствие других способов реинтерпретировать память, занимаемую значениями АлгТД, кроме как через ПМ.
Здравствуйте, VoidEx, Вы писали:
VE>Любой if — это вызов блока в if или блока в else на основе таблицы из true/false.
В случае динамической типизации — не любой.
DG>>а почти весь ПМ (при условии, что есть несколько веток) в это и транслируется. VE>Любая программа содержит условные переходы, но динамической типизацией это называть было бы крайне странно. VE>Поэтому первостепенной задачей было бы как раз решить, что же такое динамическая типизация.
Это такое условное ветвление, целью которого является выбрать ветку алгоритма, умеющего работать с неким конкретным устройством значения (т.е. с неким конкретным типом).
Т.е. имеем следующее: в compile-time пишем алгоритмы под все возможные (или уникально обрабатываемые) типы, ожидаемые в некоей точке алгоритма, а в рантайм через ветвление выбираем одну из веток алгоритма, согласно фактически поданному типу значения.
Для работоспособности этой схемы обязательно нужен некий механизм реинтерпретации памяти. Например, в C# это приведение типов as или через (Type1), в С++ через dynamic_cast или через reinterpret_cast, еще через встроенный рантайм-полиморфизм, в ML-языках через паттерн-матчинг.
VE>То, что при наличии разных вариантов у АлгТД происходит ветвление — это динамическая типизация в той же мере, что и ветвление по 0 и не 0 в факториале.
Нет.
VE>Назвать это динамической типизацией можно только при большой фантазии, да и то, с натяжкой. VE>С другой стороны динамическую типизацию можно было представить как кортеж из имени типа и значения.
Так и представляют, например АлгТД в Хаскеле. Разве что область значения нетипизирована в представлении, т.е. это просто область байт неизвестного содержания. Неизвестного до тех пор, пока не будет проверена метка типа.
VE>Фундаментальную разницу я вижу в том, что при динамической типизации тип ровно один: dynamic.
V>>Я тебя спросил — покажи как это работает в рантайм? Ведь вызываемая затем ф-ия scalarProduct — рекурсивная, а глубина рекурсии заведомо неизвестна. Т.е. я понимаю твои предположения, если полностью раскрутить и переписать рекурсию, просчитав всё с конца списка. Давай тогда добавим побочный эффект в код scalarProduct, чтобы избежать этого. Думаю, добавление IO не должно сломать тайпчекер.
DG>в данном случае речь о том, что scalarProduct в runtime-е будет следующим с гарантией, что First никогда не выкинет исключение, и что закоментированный if не нужен: DG>
Я именно это и утверждаю, а оппонент не соглашается.
Только при большом кол-ве задействованных инстансов класса типов обычно применяют табличную диспетчеризацию, как более эффективную. Т.е. полный аналог виртуальных ф-ий, о чем я сразу и сказал. Но про диспетчеризацию на if, как возможную реализацию, тоже упомянул.
Знаешь какая ключевая фишка в обсуждаемом коде? Что Nil — это тип, независимый от Cons. Поэтому я и попросил кто может по-быстрому сгенерить сишный код компилятором, бо любопытно посмотреть диспетчеризацию по типам, не входящим в один АлгТД. Я ставлю на то, что компилятор унутре будет использовать ту же технику, что для обычных АлгТД, просто он объявит нужный алгТД сам, неявно.
Здравствуйте, vdimas, Вы писали:
VE>>Спорят с тем, что это что-то вроде динамической типизации. Проблема в том, что термин "типизация" тут как-то не определили.
V>Определили. Это проверка/тестирование типа в рантайм.
Это фиговое и неправильное определение.
Типизация — это навешивание на каждое выражение в программе некоторого ярлыка-типа. Можем в компайл-тайме навесить достаточно конкретные типы на все — получили статическую типизацию. Не можем и вынуждены навешивать один неконкретный ярлык dynamic — получили динамическую.
V>Да и о чем спорить? По-определению АлгТД — это такой тип данных, в который упаковываются значения других типов. Вариант, он и в Африке вариант.
Это фиговое и неправильное определение, ведь "внутри" может оказаться он сам, не только "другие типы".
Здравствуйте, D. Mon, Вы писали:
DM>Это фиговое и неправильное определение, ведь "внутри" может оказаться он сам, не только "другие типы". DM>
DM>data Tree a = Leaf | Node a (Tree a) (Tree a)
DM>
DM>Вариант так умеет?
Если под вариантом имеется в виду упоминавшийся выше boost::variant то умеет http://www.boost.org/doc/libs/1_49_0/doc/html/variant/tutorial.html#variant.tutorial.recursive
Он вообще в принципе все почти возможности АТД эмулирует, конечно местами громоздко и коряво,
но включая даже паттерн матчинг с статистическим контролем полноты распаковки, для этого
правда нужно писать отдельный класс реализующий перегруженные операторы для всех типов
входящих в вариант:
DG>я больше говорил в разрезе, что если брать ПМ как он реализован в том же Haskell-е, то он будет генерить тормозной код, не сильно лучше, чем какая-нибудь динамика аля питон.
Если брать OCaml, в котором примерно похожий на Хаскель ПМ, реализуется очень эффективно,
например такой (бессмысленный) код:
type test = Int of int | Float of float | String of string
let dummy_id = function
| Int n -> n
| Float _ -> -1
| String _ -> -2
let _ = Printf.printf "%d\n" (dummy_id (Int 4))
превращается
camlPm_tst__dummy_id_65:
.L103:
movq %rax, %rbx
movzbq -8(%rbx), %rax
cmpq $1, %rax
je .L101
jg .L100
.L102:
movq (%rbx), %rax
ret
.align 4
.L101:
movq $-1, %rax
ret
.align 4
.L100:
movq $-3, %rax
ret
То есть всего несколько ассемблерных инструкций, вполне близко к тому во что компилируют
switch C++ компиляторы.
DG>соответственно, на основе этих condition-ов для выражения isEven(rnd * 2) вообще вычисляется, что его post-condition: true DG>и что соответственно его можно заменить целиком на true при компиляции
В общем, как я понимаю, предлагается использовать механизм доказательства теорем для доказательства независимости от какой-то грязи.
DG>в этом коде Get.result = F(x, _cache, Zzz.My) и соответственно чистота(Get.result) = чистота(x) && чистота(_cache) && чистота(Zzz.My). x — чистая переменная, т.к. readonly, Zzz.My — чистая по определению, остался _cache. DG>_cache — может быть в двух состояниях: или null, или результат Zzz.My. Нулевое состояние _cache снаружи наблюдать(обнаружить) невозможно, т.к. для метода get: F(x, null, Zzz.My) == F(x, != null, Zzz.My), а других способов получить доступ к _cache нет.
При этом грязь, имхо, лучше вводить через типы. Если формализовать и выделить виды грязи, то убирание чистоты сведётся к написанию функции вида SomeDirt a -> a.
Здравствуйте, vdimas, Вы писали:
V>Ну это ты далеко зашел в рассуждениях. Естественно, на любом тьюринг-полном языке можно написать некий интрепретатор другого тьюринг-полного языка с его системой типов, отличной от исходной. Этот случай обсуждать заведомо неинтересно.
Крайний случай неинтересно, но так как мы уже встали на шаткую тропинку, называя диспатч по вариантам АлгТД "динамической типизацией", тогда хотелось бы разобраться, где именно находится та граница. А для этого надо чётко формалировать это понятие.
V>А что не так без них?
Без них всё ок.
V>Дело в том, что С++ static_if в большинстве случаев не нужен, он удобен лишь для диагностики, остальное система типов С++ разруливает и так. Т.е. всё, что может быть описано через static_if, может быть описано и без него или даже не описано совсем, а получится "по факту". Т.е. это не костыль, а сахар.
enable_if, надо полагать, тоже "всего лишь сахар"?
V>А вот классы типов — натуральный костыль, без которого многие трюки в Хаскеле просто не взлетят.
Например?
V>А костыль это потому, что изначально в языке отсутствовала концепция контрактов/интерфейсов.
Классы из Haskell98 полностью заменяются обычными АлгТД. Более того, они обычные АлгТД и есть. Это как раз примитивнейший сахар, в отличие от static_if, который либо есть, либо нету, и ничем не заменить.
V>И вот ее заменяет этот недокостыль. Исключительно из-за скудной системы типов, где все пользовательские типы данных — это АлгТД.
Здравствуйте, vdimas, Вы писали:
DG>>>а почти весь ПМ (при условии, что есть несколько веток) в это и транслируется. VE>>Любая программа содержит условные переходы, но динамической типизацией это называть было бы крайне странно. VE>>Поэтому первостепенной задачей было бы как раз решить, что же такое динамическая типизация.
V>Это такое условное ветвление, целью которого является выбрать ветку алгоритма, умеющего работать с неким конкретным устройством значения (т.е. с неким конкретным типом).
И if именно это и делает. Не путайте то, какие байтики лежат, с типом. В случае указателя конкретное устройство значения одно и то же — указатель, но тип тем не менее разный.
V>Т.е. имеем следующее: в compile-time пишем алгоритмы под все возможные (или уникально обрабатываемые) типы, ожидаемые в некоей точке алгоритма, а в рантайм через ветвление выбираем одну из веток алгоритма, согласно фактически поданному типу значения.
V>Для работоспособности этой схемы обязательно нужен некий механизм реинтерпретации памяти. Например, в C# это приведение типов as или через (Type1), в С++ через dynamic_cast или через reinterpret_cast, еще через встроенный рантайм-полиморфизм, в ML-языках через паттерн-матчинг.
Какая связь типов с памятью?
Вот тут, например, о каком мезанизме реинтерпретации памяти вообще в принципе может идти речь?
VE>>То, что при наличии разных вариантов у АлгТД происходит ветвление — это динамическая типизация в той же мере, что и ветвление по 0 и не 0 в факториале.
V>Нет.
Да.
Я вас удивлю, но (x:int){x==0} — это тип. У него ровно одно значение. И if это проверяет, а затем работает с этим типом.
Formally, a type can be defined as "any property of a programme we can determine without executing the program"
Вы же почему-то под типом подразумеваете какое-то внутреннее представление. Мол, что (x:int){x==0}, что (x:int){x>0}, один чёрт int, 4 байта, да? Ну так и указатель любой 4 (или 8) байта, ан нет, типы-то разные!
Здравствуйте, samius, Вы писали:
S>Здравствуйте, VoidEx, Вы писали:
VE>>Классы типов — это АлгТД. S>Можно развернуть мысль?
Можно.
module Equal (
) where
import Prelude hiding (Eq(..))
data Eq a = Eq {
equal :: a -> a -> Bool,
notEqual :: a -> a -> Bool }
boolEq :: Eq Bool
boolEq = Eq eq neq where
eq True True = True
eq False False = True
eq _ _ = False
neq x y = not $ eq x y
intEq :: Eq Int
intEq = Eq eq neq where
eq 0 0 = True
eq 0 x = False
eq x 0 = False
eq x y = eq (pred x) (pred y)
neq x y = not $ eq x y
foo :: Eq a -> a -> a -> a -> Bool
foo (Eq eq neq) x y z = eq x y || eq y z
test1 = foo boolEq True False False
test2 = foo intEq 0 2 5
Здравствуйте, VoidEx, Вы писали:
VE>Здравствуйте, samius, Вы писали:
S>>Здравствуйте, VoidEx, Вы писали:
VE>>>Классы типов — это АлгТД. S>>Можно развернуть мысль?
VE>Можно.
VE>
VE>data Eq a = Eq {
VE> equal :: a -> a -> Bool,
VE> notEqual :: a -> a -> Bool }
VE>
Ясно. Это вполне ожидаемо, я лишь упустил из виду что record это частный случай АлгТД.
DM>Типизация — это навешивание на каждое выражение в программе некоторого ярлыка-типа. Можем в компайл-тайме навесить достаточно конкретные типы на все — получили статическую типизацию. Не можем и вынуждены навешивать один неконкретный ярлык dynamic — получили динамическую.
навешивание типа на выражение — это определение статической типизации, а не типизации вообще.
тип int от string в скриптах отличается? каким образом?
по твоему определению получается что различных типов в скриптах вообще нет.
Здравствуйте, DarkGray, Вы писали:
DM>>Типизация — это навешивание на каждое выражение в программе некоторого ярлыка-типа. Можем в компайл-тайме навесить достаточно конкретные типы на все — получили статическую типизацию. Не можем и вынуждены навешивать один неконкретный ярлык dynamic — получили динамическую.
DG>навешивание типа на выражение — это определение статической типизации, а не типизации вообще.
Ccылаясь на TAPL Пирса, педивикия подсказывает:
In computer science, a type system may be defined as "a tractable syntactic framework for classifying phrases according to the kinds of values they compute".
DG>тип int от string в скриптах отличается? каким образом?
Механикой работы статического типа dynamic. C т.з. компилятора они могут не отличаться. Для какого-нибудь похапе это все zvalue.
DG>по твоему определению получается что различных типов в скриптах вообще нет.
Есть один — dynamic. Но я согласен, это не единственный и возможно не самый лучший подход к определению.
если брать типы вообще, а не только внутри какого-то одного языка, то одна из задач типа — определить как данные хранятся в памяти.
можно, например, вспомнить чем тип BSTR отличается от типа WCHAR* — и то, и другое unicode строка, но вот хранение в памяти разное.
с информационной точки зрения тип задает следующие виды информации:
1. фиксирование кол-ва поддерживаемых вариантов
2. описание способа хранения в памяти (в тексте и т.д.)
3. набор поддерживаемых операций
4. информацию об отличии типов друг от друга (обычно это отражает ту информацию, которая остается во внешнем мире за границей программы)
short/int/long/big-integer — отличаются по первому пункту
int/network-int, WCHAR*/BSTR, list/array — отличаются по второму пункту
bool/enum{on/off} — отличаются по третьему пункту
enum{on/off}, enum{много/мало} — отличаются по четвертому пункту
VE>>Какая связь типов с памятью?
DG>если брать типы вообще, а не только внутри какого-то одного языка, то одна из задач типа — определить как данные хранятся в памяти. DG>можно, например, вспомнить чем тип BSTR отличается от типа WCHAR* — и то, и другое unicode строка, но вот хранение в памяти разное.
Взять, например int и unsigned int.
И хранятся одинаково, и кол-во вариантов одинаковое.
Отличие у них только в операции, выбираемой по умолчанию при перегрузке. Т.е. x < y выберет либо unsigned_less либо signed_less в зависимости от.
Можно вообще все unsigned int на int поменять (и наоборот), только придётся ручками указывать, какой именно мы less хотим.
Т.е. они по сути оба strong typedef на одно и то же. Собственно, можно как раз про strong typedef и вспомнить. Неужто они определяет способ хранения в памяти, вводя новый тип?
DM>Ccылаясь на TAPL Пирса, педивикия подсказывает: DM>In computer science, a type system may be defined as "a tractable syntactic framework for classifying phrases according to the kinds of values they compute".
во-первых: это определение type system, а не type, во-вторых: это ужасное определение: определение должно определять понятие через более простые, а в данном случае — определение элементарного понятия "type system" идет через такиие неопределяемые понятия как: syntactic, framework, classifying, phrases, kind, compute, according.
можно получить строгое формализованное определение всех перечисленных понятий?
DG>>тип int от string в скриптах отличается? каким образом?
DM>Механикой работы статического типа dynamic. C т.з. компилятора они могут не отличаться. Для какого-нибудь похапе это все
zvalue.
т.е. весь мир сужен только до задач решаемых компилятором? почему и зачем?.
с точки зрения интерпретатора типы int и string отличаются? например, с точки зрения освобождения памяти? и с точки зрения вызова правильной версии полиморфной операции + (которая по разному складывает числа и строки).
я правильно понимаю, что ты считаешь, что в c# типы есть, а когда ровно с теми же .net-данными(классами, числами и т.д.) работает ironpython, то все типы сразу куда пропадают?
DM> Для какого-нибудь похапе это все zvalue.
так это однородный тип? или у него есть явные подтипы? например: zvalue-number, zvalue-string, zvalue-dictionary
DG>>по твоему определению получается что различных типов в скриптах вообще нет.
DM>Есть один — dynamic. Но я согласен, это не единственный и возможно не самый лучший подход к определению.
ps
даже в вики такие определения:
1) Тип (от греч. τυπος — отпечаток, форма, образец) — единица расчленения изучаемой реальности в типологии
2) Тип данных определяет множество значений, набор операций[1], которые можно применять к таким значениям, и, возможно, способ реализации хранения значений и выполнения операций. Любые данные, которыми оперируют программы, относятся к определённым типам.
3) Тип (сорт) — относительно устойчивая и независимая совокупность элементов, которую можно выделить во всём рассматриваемом множестве (предметной области).[2]
4) In computer programming, a data type is a classification identifying one of various types of data, such as floating-point, integer, or Boolean, that determines the possible values for that type; the operations that can be done on values of that type; the meaning of the data; and the way values of that type can be stored
pps
Языки с неявным определением типов. Казалось бы, BASIC является примером языка без типов. Однако это строго типизированый язык: в нём различаются строковые типы (добавляется символ $), массивы (добавляется []) и числовые типы (ничего не добавляется).
VE>>>Какая связь типов с памятью?
DG>>если брать типы вообще, а не только внутри какого-то одного языка, то одна из задач типа — определить как данные хранятся в памяти. DG>>можно, например, вспомнить чем тип BSTR отличается от типа WCHAR* — и то, и другое unicode строка, но вот хранение в памяти разное.
VE>Взять, например int и unsigned int. VE>И хранятся одинаково, и кол-во вариантов одинаковое.
хранятся они по разному — у одного первый бит отвечает за знак, у другого знак подразумевается не явно.
как минимум это проверяется через операцию конвертации в строковое представление.
более формально: функция которая выводит тип int/uint правильно на экран (в строковое представление) должна принимать минимум 4байта+1бит информации. 4 байта определяют кол-во вариантов самого типа, а доп. 1 бит является идентификатором типа.
или другими словами, невозможно написать корректную функцию переводящую int/uint из одного способа хранения в другой (из байтового представления в строковое), если она принимает на вход не больше 4 байт информации.
для строгости стоит рассмотреть наборы операций:
int/uint хранятся одинаково с точки зрения unchecked операций +/-/*/'/' (можно написать функцию, которая корректно обработает тип int/uint и принимающую только 4 байта информации), но с точки зрения checked-операций, они уже хранятся по разному (необходим +1бит информации для того, чтобы отличить одно от другого)
VE>Отличие у них только в операции, выбираемой по умолчанию при перегрузке. Т.е. x < y выберет либо unsigned_less либо signed_less в зависимости от.
раз необходимо делать выбор правильной версии операции — это уже означает, что есть что-то разное (или разная семантика операции, или разная семантика хранения в памяти)
VE>Можно вообще все unsigned int на int поменять (и наоборот), только придётся ручками указывать, какой именно мы less хотим. VE>Т.е. они по сути оба strong typedef на одно и то же. Собственно, можно как раз про strong typedef и вспомнить. Неужто они определяет способ хранения в памяти, вводя новый тип?
по видимому ты хотел спросить: определяет ли typedef новый(отличный от предыдущего) способ хранения в памяти, вводя новый тип?
нет, "typedef x type-expr;" не задает обычно нового способа хранения, но при этом она определяет каким образом x будет хранится в памяти — эта информация берется из типа указанного в type-expr.
Здравствуйте, DarkGray, Вы писали:
VE>>>>Какая связь типов с памятью?
DG>>>если брать типы вообще, а не только внутри какого-то одного языка, то одна из задач типа — определить как данные хранятся в памяти. DG>>>можно, например, вспомнить чем тип BSTR отличается от типа WCHAR* — и то, и другое unicode строка, но вот хранение в памяти разное.
VE>>Взять, например int и unsigned int. VE>>И хранятся одинаково, и кол-во вариантов одинаковое.
DG>хранятся они по разному — у одного первый бит отвечает за знак, у другого знак подразумевается не явно.
Правильно ли я понимаю, что можно глядя на память понять, int там или unsigned int?
DG>для строгости стоит рассмотреть наборы операций: DG>int/uint хранятся одинаково с точки зрения unchecked операций +/-/*/'/' (можно написать функцию, которая корректно обработает тип int/uint и принимающую только 4 байта информации), но с точки зрения checked-операций, они уже хранятся по разному (необходим +1бит информации для того, чтобы отличить одно от другого)
Ага! Т.е. одинаковость хранения не от типа зависит, да? Вот и я о чём.
DG>раз необходимо делать выбор правильной версии операции — это уже означает, что есть что-то разное (или разная семантика операции, или разная семантика хранения в памяти)
Операции разные, очевидно. Причём можно обе применять. Сам по себе int или unsigned int не определяет, какое там число (ну только "на словах"). А вот операция сравнения уже несёт такую информацию, так как определена по-разному в зависимости от интерпретации того, что ей передают.
DG>по видимому ты хотел спросить: определяет ли typedef новый(отличный от предыдущего) способ хранения в памяти, вводя новый тип? DG>нет, "typedef x type-expr;" не задает обычно нового способа хранения, но при этом она определяет каким образом x будет хранится в памяти — эта информация берется из типа указанного в type-expr.
По-моему, вы ставите лошадь впереди телеги. Тип несёт информацию о множестве допустимых значений, а как это хранить, выбирает реализация. Разумеется, вводя новые типы, вы основываете их на уже имеющихся, и реализация имеет механизм, позволяющий ей выбрать способ хранения нового типа. Но это не отновится к свойствам типизации как таковой, а побочный эффект от того, что тип накладывает ограничения.
Мы можем с успехом написать такую реализацию, которая будет strong typedef хранить иначе, например, xor'я каждый байт каким-нибудь ключом (сгенерированным от имени typedef'а). Типизация останется абсолютно той же, а детали реализации останутся лишь деталями реализации. То, что эти детали используют информацию о типах и типы таким образом косвенно влияют на способ хранения — дело десятое.
В общем-то, мы уже далеко отошли от изначальной темы. Речь шла о том, что типизация будет динамическая, если происходит выбор типа и реинтерпретация памяти.
Я уточнил, что
1. банальное сравнение с константой есть выбор типа
2. выбор типа не означает реинтерпретацию памяти
Таким образом нам либо придётся согласиться, что if (x == 0) — это динамическая типизация, либо что выбор типа надо рассматривать в рамках той системы, о которой идёт речь (тогда в Си, разумеется, if (x == 0) не выбирает никакого типа, так как в Си не существует типа (x:int){x=0})
Но тогда и в Haskell при выборе АлгТД не происходит выбора типа, так как тип один и остаётся тем же, а реинтерпретации памяти вполне может и не быть, например в случае Bool или Either Int Int.
DM>>Ccылаясь на TAPL Пирса, педивикия подсказывает: DM>>In computer science, a type system may be defined as "a tractable syntactic framework for classifying phrases according to the kinds of values they compute".
DG>во-первых: это определение type system, а не type
Ну так речь шла о типизации, а не о типе.
DG>, во-вторых: это ужасное определение: определение должно определять понятие через более простые, а в данном случае — определение элементарного понятия "type system" идет через такиие неопределяемые понятия как: syntactic, framework, classifying, phrases, kind, compute, according. DG>можно получить строгое формализованное определение всех перечисленных понятий?
Можно. А нужно?
DG>img
Я недавно видел отличное определение: натуральные числа — это объект категории Пеано, представляющий singleton функтор I. Или просто инициальный объект в категории Пеано.
DG>>>тип int от string в скриптах отличается? каким образом? DM>>Механикой работы статического типа dynamic. C т.з. компилятора они могут не отличаться. Для какого-нибудь похапе это все zvalue. DG>т.е. весь мир сужен только до задач решаемых компилятором? почему и зачем?
Нет. Потому и затем, что типизация особенно актуальна именно там.
DG>с точки зрения интерпретатора типы int и string отличаются? например, с точки зрения освобождения памяти?
Не более, чем разные значения одного типа. Собственно, про параллель с алгебраиками тут уже много писали.
DG>я правильно понимаю, что ты считаешь, что в c# типы есть, а когда ровно с теми же .net-данными(классами, числами и т.д.) работает ironpython, то все типы сразу куда пропадают?
В разных язаках разные системы типов, разумеется. C#, Python и MSIL — три разных языка с разными системами типов.
DM>> Для какого-нибудь похапе это все zvalue. DG>так это однородный тип? или у него есть явные подтипы? например: zvalue-number, zvalue-string, zvalue-dictionary
Это одна структура в Си, содержащая внутри union. Она трактуется по-разному в зависимости от значения определенного поля.
DG>даже в вики такие определения:
Я и говорю, подходов к определению может быть много.
Здравствуйте, DarkGray, Вы писали:
FR>>Если брать OCaml, в котором примерно похожий на Хаскель ПМ, реализуется очень эффективно,
DG>как при этом раскроется: DG>
let dummy2 items = Array.map dummy_id |> Array.fold_left (+) 0 in
dummy2 [|Int 1; Int 2; Int 3|];
dummy2 [|String "1"; String "2"; String "3" |]
Причем никто не мешает вызвать
dummy2 [|String "1"; Int 2; Float 3.0 |]
Это ведь массив значений одного типа test.
Никакой специализации компилятор не сделает, на эффективность dummy_id твой код не повлияет, там внутри по-прежнему будет по 2 if'a.
DG>как только появляется смешивание ad-hoc полиморфизма с параметрическим, то вся эффективность пропадает
VE>Правильно ли я понимаю, что можно глядя на память понять, int там или unsigned int?
не вижу как связана верности/неверность этого утверждения и предыдущее утверждение.
DG>>раз необходимо делать выбор правильной версии операции — это уже означает, что есть что-то разное (или разная семантика операции, или разная семантика хранения в памяти)
VE>Операции разные, очевидно. Причём можно обе применять. Сам по себе int или unsigned int не определяет, какое там число (ну только "на словах"). А вот операция сравнения уже несёт такую информацию, так как определена по-разному в зависимости от интерпретации того, что ей передают.
что в твоем утверждение означает "можно применять"?
если я тебе передаю 8 байт и говорю — это два числа, раздели одно на другое, пожалуйста.
означает ли это что что ты их можешь делить как хочешь: как int/uint/float?
VE>По-моему, вы ставите лошадь впереди телеги. Тип несёт информацию о множестве допустимых значений, а как это хранить, выбирает реализация. Разумеется, вводя новые типы, вы основываете их на уже имеющихся, и реализация имеет механизм, позволяющий ей выбрать способ хранения нового типа. Но это не отновится к свойствам типизации как таковой, а побочный эффект от того, что тип накладывает ограничения.
тип отделяется от реализации (или сужение понятия типа до семантической составляющей) происходит только в чисто математических языках, таких как haskell.
и такого подхода недостаточно, как только например решаются вопросы эффективного выполнения, или использования одних и тех же типов данных разными языками.
VE>Таким образом нам либо придётся согласиться, что if (x == 0) — это динамическая типизация,
давай согласимся, что да, если это выполняется в runtime.
данный if отделяет два типа друг от друга: 0 и не 0.
VE> либо что выбор типа надо рассматривать в рамках той системы, о которой идёт речь (тогда в Си, разумеется, if (x == 0) не выбирает никакого типа, так как в Си не существует типа (x:int){x=0})
не удобный переход, в частности, тогда невозможно
провести параллели между типами в разных языках,
два языка не могут работать с одной и той же памятью и т.д.
зы
наука нас учить, что систему невозможно полностью понять находясь внутри системы, обязательно необходим выход в надсистему.
невозможно понять что такое тип находясь внутри только haskell-а(или только C), необходим выход в надсистему. Если рассмотреть, что такое тип одновременно с точки зрения haskell, C и python-а, работающих над одними и теми же данными, уже можно выйти на понятие типа.
Здравствуйте, DarkGray, Вы писали:
VE>>Правильно ли я понимаю, что можно глядя на память понять, int там или unsigned int?
DG>не вижу как связана верности/неверность этого утверждения и предыдущее утверждение.
Я ссылался на "необходимость реинтерпретации памяти", из которой следует взаимнооднозначное соответствие между типом и представлением в памяти (ибо иначе никакой реинтерпретации может и не понадобиться). Фраза не твоя, но разговор пошёл оттуда.
DG>что в твоем утверждение означает "можно применять"?
Не отвечаю, так как речь всё о том же была. Что разные типы могут лежать в памяти одинаково.
И разница между int и float не больше и не меньше, чем между (x:int){x=0} и (x:int){x>10}
VE>>Таким образом нам либо придётся согласиться, что if (x == 0) — это динамическая типизация,
DG>давай согласимся, что да, если это выполняется в runtime. DG>данный if отделяет два типа друг от друга: 0 и не 0.
Разумеется, речь о runtime. Проблема в том, что с системе типов Си++ нет таких типов, как "0" и "не 0". Поэтому в данном случае это не динамическая типизация, тип остаётся тот же. Однако если смотреть на Си++ через призму другой системы типов, это будет динамической типизацией.
Аналогично и в Haskell, выбор между вариантами АлгТД не меняет тип. То, что, глядя на это под каким-то другим углом с другой системой типов, начинает вдруг прорисовываться общность с проверкой типа runtime, типизацию динамической не делает.
DG>не удобный переход, в частности, тогда невозможно DG> провести параллели между типами в разных языках, DG> два языка не могут работать с одной и той же памятью и т.д.
Почему невозможно?
DG>зы DG>наука нас учить, что систему невозможно полностью понять находясь внутри системы, обязательно необходим выход в надсистему. DG>невозможно понять что такое тип находясь внутри только haskell-а(или только C), необходим выход в надсистему. Если рассмотреть, что такое тип одновременно с точки зрения haskell, C и python-а, работающих над одними и теми же данными, уже можно выйти на понятие типа.
Смотреть на типы с т.з. haskell, C и python — это не выйти в надсистему, а перейти в другую систему и при этом пытаться применить термины из одной системы с другой.
А вот если построить обобщённую систему, то различие в том, что в python тип один — dynamic. С т.з. python различий между строкой и числом не больше, чем между 10 и 0 с т.з. Haskell. В том же Haskell есть тип Dynamic, пара TypeRep и ссылка. Мы туда можем запихнуть строку и число, но x :: Dynamic и y :: Dynamic — это два значения одного и того же типа. А вот fromDyn x :: String и fromDyn y :: Int уже разных.
DM>>>Ccылаясь на TAPL Пирса, педивикия подсказывает: DM>>>In computer science, a type system may be defined as "a tractable syntactic framework for classifying phrases according to the kinds of values they compute".
DG>>во-первых: это определение type system, а не type
DM>Ну так речь шла о типизации, а не о типе.
если в понятие типа упоминается о способе хранения в памяти, а в более сложном понятии (типизация или type system) — этой составляющей нет, то можно сделать вывод, что определение сложного понятия как минимум не полное.
DG>>, во-вторых: это ужасное определение: определение должно определять понятие через более простые, а в данном случае — определение элементарного понятия "type system" идет через такиие неопределяемые понятия как: syntactic, framework, classifying, phrases, kind, compute, according. DG>>можно получить строгое формализованное определение всех перечисленных понятий?
DM>Можно. А нужно?
строго необходимо, если стоит задача получить корректное описание реальности. Если стоит задача покидаться ничего не значащими фразами из википедии, то не нужно
DM>Это одна структура в Си, содержащая внутри union. Она трактуется по-разному в зависимости от значения определенного поля.
здесь неявно подразумевается, что одна структура в C — это строго один математических тип.
из какого определения это следует?
DM>Я и говорю, подходов к определению может быть много.
определения все одинаковые?, или можно их классифицировать чем они лучше/хуже по тем или иным признакам?
пока ты копируешь определения из википедии — никакого сознания нет, есть лишь тот самый автомат из китайской комнаты, который механически подставляет необходимую ссылку на википедию, когда встречает ключевые слова.
сознание определяют как умение выстраивать логические цепочки и гибкое адаптирование к контексту.
при копировании определения из вики — ни первого, ни второго — нет.
DG>он внутри dummy_id как был так и остался, а поверх него навернут параметриче
DG>int dummy2(array<test> items) = items.Select(item => dummy_id(item)).Sum();
Как выше уже писали ничего ни изменится, dummy_id каким был таким и останется,
да и с чего ему меняться он же всегда принимает не полиморфный а конкретный тип
test.
VE>>>Правильно ли я понимаю, что можно глядя на память понять, int там или unsigned int?
DG>>не вижу как связана верности/неверность этого утверждения и предыдущее утверждение.
VE>Я ссылался на "необходимость реинтерпретации памяти", из которой следует взаимнооднозначное соответствие между типом и представлением в памяти (ибо иначе никакой реинтерпретации может и не понадобиться). Фраза не твоя, но разговор пошёл оттуда.
давай чуть вернемся.
изначально было твой вопрос: VE> Какая связь типов с памятью?
который я интерпретировал, что ты утверждаешь, что нет связи между типами и представлением в памяти
сейчас ты перешел к: взаимооднозначное соответствие между типом и представлением в памяти.
как связаны между собой: наличие/отсутствие взаимоднозначного соответствия между типами и представлением в памяти и утверждением, что нет связи между типом и представлением в памяти?
DG>>он внутри dummy_id как был так и остался, а поверх него навернут параметриче
DG>>int dummy2(array<test> items) = items.Select(item => dummy_id(item)).Sum();
FR>Как выше уже писали ничего ни изменится, dummy_id каким был таким и останется, FR>да и с чего ему меняться он же всегда принимает не полиморфный а конкретный тип FR>test.
потому что для данного кода можно сгенерить следующий код, который будет эффективнее и в нем не будет динамической типизации(кроме проверки на конец последовательности внутри Sum):
int dummy_id_int(int x) {return x;}
int dummy_id_float(float x) {return -1;}
int dymmy_id_string(string x) {return -2;}
int dummy2_int(array<int> items) = items.Sum();
int dummy2_float(array<float> items) = -1 * items.length;
int dummy2_string(array<string> items) = -2 * items.length;
[1, 2, 3].dummy2_int();
["1", "2", "3"].dummy2_string();
можно и вызовы dummy2_* развернуть, но для более явного примера, будем считать,
что длина и значения массивов не известны на этапе компиляции,
но тип элементов известен и он одинаковый для всех элементов внутри каждого массива
FR>Как выше уже писали ничего ни изменится, dummy_id каким был таким и останется, FR>да и с чего ему меняться он же всегда принимает не полиморфный а конкретный тип FR>test.
Это утверждение неявно использует допущение, что описание функции в ЯП и генеренный для нее код соотносится как 1 к 1.
Такое допущение сильно упрощает действительность, и и является неверным и опасным.
VE>Разумеется, речь о runtime. Проблема в том, что с системе типов Си++ нет таких типов, как "0" и "не 0".
что ты вкладываешь в "есть тип или нет типа в языке"?
какие необходимые, достаточные и желательные условия необходимы, чтобы можно было сказать, что в ЯП Zzz есть тип Xxx?
Здравствуйте, D. Mon, Вы писали:
VE>>>Спорят с тем, что это что-то вроде динамической типизации. Проблема в том, что термин "типизация" тут как-то не определили. V>>Определили. Это проверка/тестирование типа в рантайм.
DM>Это фиговое и неправильное определение. DM>Типизация — это навешивание на каждое выражение в программе некоторого ярлыка-типа. Можем в компайл-тайме навесить достаточно конкретные типы на все — получили статическую типизацию. Не можем и вынуждены навешивать один неконкретный ярлык dynamic — получили динамическую.
Речь была о динамической типизации в рамках статически-типизированных языков. И с какой радости dynamic должен быть один? Это нигде не ограничивается, кроме как в реализации некоторых языков. Например, в С++ таких корневых dynamic может быть бесчисленное мн-во.
V>>Да и о чем спорить? По-определению АлгТД — это такой тип данных, в который упаковываются значения других типов. Вариант, он и в Африке вариант.
DM>Это фиговое и неправильное определение, ведь "внутри" может оказаться он сам, не только "другие типы". DM>
DM>data Tree a = Leaf | Node a (Tree a) (Tree a)
DM>
Вот, популярная ошибка. В общем виде такая структура в статически-типизированном языке представима только через боксирование (уже доказано чуть выше по ветке, почему именно так и никак иначе). Поэтому хранится не само значение типа, а его боксированное представление. Т.е. еще раз более обще (не обязательно ограничиваясь АлгТД): рекурсивный тип в общем случае НЕ МОЖЕТ хранить значение собственного типа, он может хранить только боксированное значение собственного типа.
DM>Вариант так умеет?
Ес-но умеет: make_recursive_variant. Через точно такую же технику, ес-но.
Здравствуйте, VoidEx, Вы писали:
V>>Ну это ты далеко зашел в рассуждениях. Естественно, на любом тьюринг-полном языке можно написать некий интрепретатор другого тьюринг-полного языка с его системой типов, отличной от исходной. Этот случай обсуждать заведомо неинтересно.
VE>Крайний случай неинтересно, но так как мы уже встали на шаткую тропинку, называя диспатч по вариантам АлгТД "динамической типизацией", тогда хотелось бы разобраться, где именно находится та граница. А для этого надо чётко формалировать это понятие.
Классная беседа.
Тогда нафига классы типов вообще нужны?
V>>Дело в том, что С++ static_if в большинстве случаев не нужен, он удобен лишь для диагностики, остальное система типов С++ разруливает и так. Т.е. всё, что может быть описано через static_if, может быть описано и без него или даже не описано совсем, а получится "по факту". Т.е. это не костыль, а сахар. VE>enable_if, надо полагать, тоже "всего лишь сахар"?
Да, коль и так требуемый результат достижим через имеющуюся систему типов. Для случая классов типов в Хаскеле — не достижим.
V>>А вот классы типов — натуральный костыль, без которого многие трюки в Хаскеле просто не взлетят.
VE>Например?
Например, обсуждаемый пример, где Nil и Cons — это совершенно независимые типы, а не конструкторы одного АлгТД. И где де-факто рекурсия была сделана на полиморфном аргументе, а не рекурсивном АлгТД. Т.е. классы типов позволяют использовать в алгоритмах независимые типы.
V>>А костыль это потому, что изначально в языке отсутствовала концепция контрактов/интерфейсов.
VE>Классы из Haskell98 полностью заменяются обычными АлгТД. Более того, они обычные АлгТД и есть. Это как раз примитивнейший сахар, в отличие от static_if, который либо есть, либо нету, и ничем не заменить.
Мне только что доказывали, что это не так. Хотя я именно настаиваю, что "унутре" реализация классов-типов может быть реализована на той же технике, что АлгТД. Почему и просил распечатку сишного промежуточного кода примера.
V>>И вот ее заменяет этот недокостыль. Исключительно из-за скудной системы типов, где все пользовательские типы данных — это АлгТД.
VE>Классы типов — это АлгТД.
Таки нет. АлгТД имеет вполне конкретное определение. А тот факт, что классы типов конкретно в Хаскеле, с его, скажем так, особенностью представления модулей, вполне можно выразить через неявные АлгТД — это частность, которая может исчезнуть при другом представлении модулей. Просто сейчас компиляторам доступен "исходник" (пусть даже в распаршенном виде) всех используемых модулей, что можно в процессе компиляции статически сгенерить АлгТД по всем инстансам класса.
Здравствуйте, DarkGray, Вы писали:
VE>>Я ссылался на "необходимость реинтерпретации памяти", из которой следует взаимнооднозначное соответствие между типом и представлением в памяти (ибо иначе никакой реинтерпретации может и не понадобиться). Фраза не твоя, но разговор пошёл оттуда.
DG>давай чуть вернемся.
Давай.
DG>изначально было твой вопрос: VE>> Какая связь типов с памятью?
Нет, изначально было то, на что я задал такой вопрос. И я даже процитировал.
DG>который я интерпретировал, что ты утверждаешь, что нет связи между типами и представлением в памяти
Утерял контекст, а потом интерпретировал.
DG>сейчас ты перешел к: взаимооднозначное соответствие между типом и представлением в памяти.
Здравствуйте, DarkGray, Вы писали:
DG>если в понятие типа упоминается о способе хранения в памяти
Ничего там не упоминается. Сишный int можно хранить хоть boxed. Система типов (и сами типы) останется той же. Другое дело, что стандарт описывает не только систему типов.
VE>>Разумеется, речь о runtime. Проблема в том, что с системе типов Си++ нет таких типов, как "0" и "не 0".
DG>что ты вкладываешь в "есть тип или нет типа в языке"? DG>какие необходимые, достаточные и желательные условия необходимы, чтобы можно было сказать, что в ЯП Zzz есть тип Xxx?
Очевидно, возможность статически проверить принадлежность в такому типу.
DG>>не удобный переход, в частности, тогда невозможно DG>> провести параллели между типами в разных языках, DG>> два языка не могут работать с одной и той же памятью и т.д.
VE>Почему невозможно?
если ты говоришь, что каждая type system полностью независима от другой, и что в типе не указано как он хранится в памяти, то невозможно с map<string, array<int>> работать из haskell-я, из C++ и из питона, обращаешь к представлению данного типа в памяти напрямую.
DG>>зы DG>>наука нас учить, что систему невозможно полностью понять находясь внутри системы, обязательно необходим выход в надсистему. DG>>невозможно понять что такое тип находясь внутри только haskell-а(или только C), необходим выход в надсистему. Если рассмотреть, что такое тип одновременно с точки зрения haskell, C и python-а, работающих над одними и теми же данными, уже можно выйти на понятие типа.
VE>Смотреть на типы с т.з. haskell, C и python — это не выйти в надсистему, а перейти в другую систему и при этом пытаться применить термины из одной системы с другой.
при выходе в надсистему появляется обобщенный Type (будем его писать с большой буквы для однозначности), которые включает в себя C-тип, Haskell-тип, python-тип, ATS-тип и т.д
Соответственно Type включает в себя все возможности, которые были у C/Haskell/python/ats-типов и т.д.:
если C-тип умеет описывать как данные хранятся в памяти, значит и Type умеет,
если Haskell умеет описывать параметрические типы (функциональные типы и т.д.), значит и Type умеет,
если ATS делит типы на те, которые переносятся в runtime и те, которые не переносятся, значит и Type тоже такое умеет,
если Haskell умеет скрывать детали реализации (способ хранения типа в памяти), а типы C(или тем более asm) этого не делают — значит Type умеет и так, и так — в зависимости от необходимости (или угла рассмотрения)
и т.д.
DG>потому что для данного кода можно сгенерить следующий код, который будет эффективнее и в нем не будет динамической типизации(кроме проверки на конец последовательности внутри Sum):
В моем коде нет динамической типизации.
Рантаймное ветвление при ПМ есть, почему ты и vdimas называете это динамической типизацией я не понимаю,
хотя признаюсь всю ветку внимательно не читал.
DG>можно и вызовы dummy2_* развернуть, но для более явного примера, будем считать, DG>что длина и значения массивов не известны на этапе компиляции, DG>но тип элементов известен и он одинаковый для всех элементов внутри каждого массива
Мне кажется ты не понимаешь что в том коде который привел я (и по его мотивам D. Mon)
нет разных типов и нет полиморфизма.
Есть один единственный тип:
type test = Int of int | Float of float | String of string
и фукция dummy_id мономорфна и работает с этим типом а функция dummy2 которую привел
D. Mon с массивами этого типа, а не с массивами int, float или string. Так что такая
развертка которую ты хочешь просто невозможна.
DG>Это утверждение неявно использует допущение, что описание функции в ЯП и генеренный для нее код соотносится как 1 к 1. DG>Такое допущение сильно упрощает действительность, и и является неверным и опасным.
Генеренный код в данном случае делает ровно то что описывает ЯП а именно статически типизированную
неполиморфную функцию.
Какое отношение боксирование имеет к динамической типизации?
VE>>Без них всё ок.
V>Классная беседа. V>Тогда нафига классы типов вообще нужны?
Чтобы не передавать Eq a явно. Правда, для этого вовсе не обязательно надо было городить классы типов.
В любом случае, ни о какой "скудности системы типов" и "трюках, которые не взлетят без классов типов" речи вообще не идёт.
V>Да, коль и так требуемый результат достижим через имеющуюся систему типов. Для случая классов типов в Хаскеле — не достижим.
Ну-ка, что, например, недостижимо в Хаскеле без классов типов?
V>Например, обсуждаемый пример, где Nil и Cons — это совершенно независимые типы, а не конструкторы одного АлгТД. И где де-факто рекурсия была сделана на полиморфном аргументе, а не рекурсивном АлгТД. Т.е. классы типов позволяют использовать в алгоритмах независимые типы.
Этот пример переписывается без использования классов типов один в один.
V>Мне только что доказывали, что это не так. Хотя я именно настаиваю, что "унутре" реализация классов-типов может быть реализована на той же технике, что АлгТД. Почему и просил распечатку сишного промежуточного кода примера.
Не знаю, кто вам что доказывал, но если этот пример таки написать на Си, у вас там будут те же самые проверки в том же объёме. Cons у вас будет не шаблоном, а struct Cons { int value; void * p; } и т.п.
Статическую проверку можно будет наложить шаблоном и кастами void* <-> T*
На исходном Си++ примере же у нас кодогенерация, которая не даёт тех же возможностей, что и Haskell, но зато избавлена от проверок. Ну дак и на TemplateHaskell это можно сделать точно так же. Без проверок.
Итого: либо оба примера работают, как хотел МигМит, с одинаковым кол-вом проверок, либо оба не используют проверки, как Си++, но и никакой вводимой длины списка не будет, а только определённая в compile-time.
V>Таки нет. АлгТД имеет вполне конкретное определение. А тот факт, что классы типов конкретно в Хаскеле, с его, скажем так, особенностью представления модулей, вполне можно выразить через неявные АлгТД — это частность, которая может исчезнуть при другом представлении модулей. Просто сейчас компиляторам доступен "исходник" (пусть даже в распаршенном виде) всех используемых модулей, что можно в процессе компиляции статически сгенерить АлгТД по всем инстансам класса.
Это шутка что ли или непонимание?
foo :: Eq a -> a -> a -> Bool
foo (Eq eq neq) x y = eq x y
Где тут используются "особенности представления модулей"?
Eq самый обыкновенный ADT с одним конструктором.
Давайте-ка я перепишу:
foo :: (a -> a -> Bool, a -> a -> Bool) -> a -> a -> Bool
foo (eq, neq) x y = eq x y
Что теперь? Туплы и ФВП у нас работают тоже благодаря особенностям представления модулей?
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, VoidEx, Вы писали:
VE>>>>Классы типов — это АлгТД. S>>>Можно развернуть мысль?
VE>>Можно.
V>А можно, для не имеющих достаточной практики в Хаскеле его Prelude пояснить, где здесь используются классы типов?
Можно.
module Equal (
) where
import Prelude hiding (Eq(..))
-- class Eq a where
-- (==) :: a -> a -> Bool
-- (/=) :: a -> a -> Booldata Eq a = EqCtor {
equal :: a -> a -> Bool,
notEqual :: a -> a -> Bool }
-- instance Eq Bool where
-- (==) = eq
-- (/=) = neq
boolEq :: Eq Bool
boolEq = EqCtor eq neq where
eq True True = True
eq False False = True
eq _ _ = False
neq x y = not $ eq x y
-- instance Eq Int where
-- (==) = eq
-- (/=) = neq
intEq :: Eq Int
intEq = EqCtor eq neq where
eq 0 0 = True
eq 0 x = False
eq x 0 = False
eq x y = eq (pred x) (pred y)
neq x y = not $ eq x y
-- foo :: Eq a => a -> a -> a -> Bool
foo :: Eq a -> a -> a -> a -> Bool-- foo x y z = (==) x y || (==) y z
foo (EqCtor eq neq) x y z = eq x y || eq y z
test1 = foo boolEq True False False
test2 = foo intEq 0 2 5
Тип Eq a полностью аналогичен классу Eq.
Отличие в том, что instance может быть только один, и передаётся он неявно.
FR>Рантаймное ветвление при ПМ есть, почему ты и vdimas называете это динамической типизацией я не понимаю,
дай формальное опредение, которое бы строгим образом отличало одно (динамическую типизацию) от другого (runtime-ветвление)?
причем чтобы это определение не зависело от конкретного ЯП, и было одинаково подходящим для произвольного языка.
ззы
так как такого формального определения разницы дать нельзя, то я считаю — что это одно и тоже, а то что некоторые люди пытаются отличать одно от другого является чистой вкусовщиной и субъективизмом
зы
Если один и тот же код над данными выполняет разные операции значит это данные разного типа.
if в генеренном коде, как раз и это обозначает. что код вроде бы один, но данные обрабатывает по разному.
если операция деления выглядит как:
div(a, b)=
if (b == 0)
throw error
else
посчитать сколько раз b входит в a
то этого достаточно для утверждения, что для операции div если два типа: 0 и не 0:
а также достаточно для выделения двух функций:
type number_not_0:number where number != 0;
type 0:number where number == 0;
div(a, number_not_0 b) =
посчитать сколько раз b входит в a
div(_, number_0) =
throw error
Здравствуйте, DarkGray, Вы писали:
VE>>Почему невозможно?
DG>если ты говоришь, что каждая type system полностью независима от другой, и что в типе не указано как он хранится в памяти, то невозможно с map<string, array<int>> работать из haskell-я, из C++ и из питона, обращаешь к представлению данного типа в памяти напрямую.
В типе — не указано. В языке — указано.
VE>>Смотреть на типы с т.з. haskell, C и python — это не выйти в надсистему, а перейти в другую систему и при этом пытаться применить термины из одной системы с другой.
DG>при выходе в надсистему появляется обобщенный Type (будем его писать с большой буквы для однозначности), которые включает в себя C-тип, Haskell-тип, python-тип, ATS-тип и т.д DG>Соответственно Type включает в себя все возможности, которые были у C/Haskell/python/ats-типов и т.д.: DG>если C-тип умеет описывать как данные хранятся в памяти, значит и Type умеет,
C-тип не умеет этого описывать. Как хранятся конкретные типы, описывает язык Си, а не тип. Типизировать программу можно вне зависимости от того, как хранятся данные в памяти. Не надо мешать всё в кучу.
DG>>Это утверждение неявно использует допущение, что описание функции в ЯП и генеренный для нее код соотносится как 1 к 1. DG>>Такое допущение сильно упрощает действительность, и и является неверным и опасным.
FR>Генеренный код в данном случае делает ровно то что описывает ЯП а именно статически типизированную FR>неполиморфную функцию.
FR>Мне кажется ты не понимаешь что в том коде который привел я (и по его мотивам D. Mon) FR>нет разных типов и нет полиморфизма.
с точки зрения какой формальной логики ты утверждаешь, что там этого нет?
с точки зрения конструктивной логики там есть вся необходимая информация для построения разных типов и полиморфизма.
FR>Есть один единственный тип:
FR>type test = Int of int | Float of float | String of string
с точки зрения конструктивной логики (и наличия операций композиции и декомпозиции) — это как минимум 4 типа (вся необходимая информация есть), при необходимости можно построить и другие типы (в частности, type Number = Int of int | Float of float и т.д.)
FR>и фукция dummy_id мономорфна и работает с этим типом а функция dummy2 которую привел FR>D. Mon с массивами этого типа, а не с массивами int, float или string. Так что такая FR>развертка которую ты хочешь просто невозможна.
Здравствуйте, VoidEx, Вы писали:
V>>Это такое условное ветвление, целью которого является выбрать ветку алгоритма, умеющего работать с неким конкретным устройством значения (т.е. с неким конкретным типом).
VE>И if именно это и делает.
Там, где в рамках статически-типизированного языка не выполняется реинтерпретация памяти, то if этого не делает ни разу.
VE>Не путайте то, какие байтики лежат, с типом. В случае указателя конкретное устройство значения одно и то же — указатель, но тип тем не менее разный.
Это ты путаешь. Одинаковое устройство даже двух объявленных одинаково типов не гарантируется в том же С++. А в плане указателей (при чем тут они, кстати?), если работаем именно со значением указателя, а не с указываемой областью памяти, то одинаковое устройство указателей проявляется в полной мере — мы можем присвоить void * значение типизированного указателя, преобразовать в число, получить из числа и т.д. Попробуй преобразовать в число, скажем, struct A {}.
V>>Т.е. имеем следующее: в compile-time пишем алгоритмы под все возможные (или уникально обрабатываемые) типы, ожидаемые в некоей точке алгоритма, а в рантайм через ветвление выбираем одну из веток алгоритма, согласно фактически поданному типу значения.
V>>Для работоспособности этой схемы обязательно нужен некий механизм реинтерпретации памяти. Например, в C# это приведение типов as или через (Type1), в С++ через dynamic_cast или через reinterpret_cast, еще через встроенный рантайм-полиморфизм, в ML-языках через паттерн-матчинг.
VE>Какая связь типов с памятью?
Такая, что в статически-типизированном языке компилятору требуется знать представление любого типа в памяти для генерации кода.
VE>Вот тут, например, о каком механизме реинтерпретации памяти вообще в принципе может идти речь?
А где по ссылке ограничения в реализации, присущие компиляторам статически-типизированных языков? где там вообще идет речь о компиляции vs интерпретации или же где там вообще обсуждается реализация?
В общем, мы уже договорились чуть выше, что статическая типизация и статическая компиляция — вовсе не одно и то же. А ты продолжаешь их путать.
Заметь, в SystemF присутствуют т.н. абстрактные типы данных с конструкторами. А реализация абстрактных типов данных без какой-либо техники динамической диспетчеризации по конкретным устройствам значений, то бишь по конкретным типам, невозможна.
Итого, для статической реализации SystemF нужна вспомогательная система типов, с помощью которой уже можно будет описать абстракции самой SystemF в терминах нижележащей вычислительной модели, например, в плоской памяти, как в Гарвардской или Фон-Неймановской архитектуре.
VE>>>То, что при наличии разных вариантов у АлгТД происходит ветвление — это динамическая типизация в той же мере, что и ветвление по 0 и не 0 в факториале.
V>>Нет.
VE>Да.
VE>Я вас удивлю, но (x:int){x==0} — это тип. У него ровно одно значение. И if это проверяет, а затем работает с этим типом.
Ничем не удивил. Эта такая абстракция Хаскеля на вырожденных АлгТД, потому что натуральные числа или символы в Хаскеле представлены лишь значением дискриминатора. Но если тело "варианта" пустое для всех значений дискриминатора по-определению, то никакой реинтерпретации памяти не требуется, бо все пустые туплы совместимы по представлению... в отличие от случая, когда вместе с дискриминатором хранится некое значение, структура которого неизвестна до момента проверки дискриминатора варианта.
VE>
VE>Formally, a type can be defined as "any property of a programme we can determine without executing the program"
VE>Вы же почему-то под типом подразумеваете какое-то внутреннее представление.
Почему нет, если речь идет непосредственно об этом представлении? Например, когда мы обсуждаем компилятор статически-типизируемого языка, а не некий интерпретатор.
И да, содержимое АлгТД, кроме вырожденных случаев (на которые тебя упорно тянет), невозможно определить ДО выполнения. Именно поэтому в ПМ по дискриминатору АлгТД более одной ветки, под все необходимые случаи, возможные в рантайм. А ведь в реальности многие из этих веток могут быть "мертвыми", т.е. не быть вызванными ни разу. Как тебе такое "property of a programme we can determine without executing the program"?
VE>Мол, что (x:int){x==0}, что (x:int){x>0}, один чёрт int, 4 байта, да?
Да, значение дискриминатора (т.е. некое его представление) само имеет некий тип, а что? Тебя смущает, что этот тип тебе не доступен в общем случае? ИМХО, лишь для того, чтобы не ограничивать способ реализации. Но для случая встроенных интегральных типов Хаскеля — доступен. Т.е. для некоего мн-ва встроенных типов, для которых объявлено, что они являются АлгТД, прямо в спецификации указан способ представления дискриминаторов этих АлгТД. Теперь полегчало?
VE>Ну так и указатель любой 4 (или 8) байта, ан нет, типы-то разные!
Ну и каша... В одном и том же варианте все значения дискриминаторов представлены одним и тем же типом by design алгебраического типа. Т.е. заведомо совместимы. А указатели разных типов в С++ сделаны несовместимыми, в отличие от С, именно затем, чтобы процедуру реинтерпретации памяти описывать в коде явно через специально предназначенные синтаксические конструкции реинтерпретации памяти, а не через простое присвоение указателей как в С.
Здравствуйте, DarkGray, Вы писали:
DG>с точки зрения какой формальной логики ты утверждаешь, что там этого нет? DG>с точки зрения конструктивной логики там есть вся необходимая информация для построения разных типов и полиморфизма.
Я утверждаю с точки зрения конкретного языка (вернее семейства ML языков) программирования.
Для Standard ML 97 есть полное формальное описание как синтаксиса так и семантики, если нужна полная
математическая формализация ищи это описание.
FR>>Есть один единственный тип:
FR>>type test = Int of int | Float of float | String of string
DG>с точки зрения конструктивной логики (и наличия операций композиции и декомпозиции) — это как минимум 4 типа (вся необходимая информация есть), при необходимости можно построить и другие типы (в частности, type Number = Int of int | Float of float и т.д.)
Понятно пошла альтернативная терминология и логика тут я пас, на десятки страниц обсуждения
ни о чем нет времени.
Здравствуйте, VoidEx, Вы писали:
VE>Можно вообще все unsigned int на int поменять (и наоборот), только придётся ручками указывать, какой именно мы less хотим. VE>Т.е. они по сути оба strong typedef на одно и то же.
Именно строгий. Только это и позволяет использовать статический полиморфизм в операциях <, >.
Хотя, бывают и другие плюшки, например массив int в дотнете приводим к массиву uint, т.е. среда исполнения допускает такую реинтерпретацию памяти. А вот со ссылочными значениями уже обломс.
VE>Собственно, можно как раз про strong typedef и вспомнить. Неужто они определяет способ хранения в памяти, вводя новый тип?
Дык, представление значения типа в памяти — это не единственный признак типа. Один из. Прдеставь, что ты делаешь такой typedef в каком-нить интерпретаторе, где вся информация о типах нужна в момент исполнения. Тогда у тебя не будет возможности создать в памяти два одинаковых значения разного типа, т.к. будет всегда отличаться минимум одно поле — признак типа. Считай, что в статически-типизируемом происходит тоже самое, только признак типа "сокращается" компилятором. Но даже при компиляции статически-типизированного языка, признак типа сокращается не везде, а только там, где рантайм полиморфизм заведомо недоступен. Если же полиморфизм доступен в рантайм, то признак типа обязательно хранится вместе со значением.
VE>C-тип не умеет этого описывать. Как хранятся конкретные типы, описывает язык Си, а не тип. Типизировать программу можно вне зависимости от того, как хранятся данные в памяти. Не надо мешать всё в кучу.
на твой взгляд, какие типы умеют описывать хранение в памяти?
ASN.1 подойдет?
давай его тогда добавим к нашей надсистеме.
FR>Я утверждаю с точки зрения конкретного языка (вернее семейства ML языков) программирования. FR>Для Standard ML 97 есть полное формальное описание как синтаксиса так и семантики, если нужна полная FR>математическая формализация ищи это описание.
и какой смысл фиксировать такие общие определения, как: тип, типизация, полиморфизм, динамическая типизация и т.д. на основе кодогенерации конкретной реализации конкретного языка?
Здравствуйте, DarkGray, Вы писали:
FR>>Рантаймное ветвление при ПМ есть, почему ты и vdimas называете это динамической типизацией я не понимаю,
DG>дай формальное опредение, которое бы строгим образом отличало одно (динамическую типизацию) от другого (runtime-ветвление)? DG>причем чтобы это определение не зависело от конкретного ЯП, и было одинаково подходящим для произвольного языка.
DG>ззы DG>так как такого формального определения разницы дать нельзя, то я считаю — что это одно и тоже, а то что некоторые люди пытаются отличать одно от другого является чистой вкусовщиной и субъективизмом
Невозможность надо доказывать. То, что пока не привели такого определения, не означает, что его нет.
Например, навскидку, динамическая типизация:
Каждому типу a ставится в соответствие множество типов T свободных по a таких, для каждого t : T существуют примитивные параметрически полиморфные (с ограничением по множеству T) функции
cast : a -> t
dynamicCast : t -> Maybe a
При этом выполняется
backCast : a -> Prop
backCast x = dynamicCast (cast x : t) === Just x
Уточнение: сводобных по a означает, что t : T не содержит в своем выражении тип a. Т.е. например Either a b туда не подходит.
Не динамическая типизация (в случае, конечно, если они внутри не используют её некоторым образом):
left :: Either a b -> Maybe a
template <class T>
T * cast(Some<T> * p);
Динамическая типизация:
fromDyn :: (Typeable a) => Dynamic -> Maybe a
template <class T, class U>
U * myDynamicCast(T * p);
Т.е. в Haskell каждому Typeable a ставится в соотсветствие Dynamic, и есть функции туда-обратно с нужными свойствами:
toDyn :: Typeable a => a -> Dynamic
fromDyn :: Typeable a => Dynamic -> Maybe a
В Си++ такая "функция" dynamic_cast и встроенный каст в родителю.
Не является динамической типизацией функция
foo :: a -> Maybe b
foo _ = Just undefined
Так как результат не определён.
Не является динамической типизацией функция
bar :: a -> Maybe b
bar _ = Nothing
Так как (bar 4 :: Maybe Int) =/= (Just 4 :: Maybe Int)
Не является динамической типизацией функция
template <class T, class U>
T * notDyn(U * x) { return 0; }
Так как notDyn<int, int>(p) != 0
Не является динамической типизацией функция
template <class T, class U>
class NotDyn
{
public:
T * cast(U *) { return 0; }
};
template <class T>
class NotDyn<T, T>
{
public:
T * cast(T * x) { return x; }
};
Здравствуйте, DarkGray, Вы писали:
DG>и какой смысл фиксировать такие общие определения, как: тип, типизация, полиморфизм, динамическая типизация и т.д. на основе кодогенерации конкретной реализации конкретного языка?
Во первых не на основе кодогенерации, а на основе спецификации (или стандарта) языка, тут Standart ML как-раз
подходит лучше всех так как имеет полностью математически формализованную спецификацию.
Общие определения ни к чему кроме терминологического спора не приведут. Причина простая в большинстве языков
эти определения слишком размыты и для разных языков часто противоречивы.
Здравствуйте, VoidEx, Вы писали:
V>>Тогда нафига классы типов вообще нужны? VE>Чтобы не передавать Eq a явно. Правда, для этого вовсе не обязательно надо было городить классы типов.
А что можно?
V>>Например, обсуждаемый пример, где Nil и Cons — это совершенно независимые типы, а не конструкторы одного АлгТД. И где де-факто рекурсия была сделана на полиморфном аргументе, а не рекурсивном АлгТД. Т.е. классы типов позволяют использовать в алгоритмах независимые типы.
VE>Этот пример переписывается без использования классов типов один в один.
А обсуждаемый compile-time контроль длины массива остается?
Моя версия была такова:
V>>Тем более, что обсуждаемый пример можно переписать на АлгТД и обойтись без классов типов.
K>Нельзя. Потому, что нам нужны тайплевел вычисления, а не рантайм-проверка.
Можно, добавив фиктивные тайлевел вычисления, не участвующие в рантайм-вычислениях. Я имел ввиду организацию рекурсивного типа. Сейчас эта рекурсия выполнена на полиморфном аргументе, а можно было сделать явно на алгТД.
V>>Мне только что доказывали, что это не так. Хотя я именно настаиваю, что "унутре" реализация классов-типов может быть реализована на той же технике, что АлгТД. Почему и просил распечатку сишного промежуточного кода примера.
VE>Не знаю, кто вам что доказывал, но если этот пример таки написать на Си, у вас там будут те же самые проверки в том же объёме. Cons у вас будет не шаблоном, а struct Cons { int value; void * p; } и т.п. VE>Статическую проверку можно будет наложить шаблоном и кастами void* <-> T*
VE>На исходном Си++ примере же у нас кодогенерация, которая не даёт тех же возможностей, что и Haskell, но зато избавлена от проверок. Ну дак и на TemplateHaskell это можно сделать точно так же. Без проверок.
VE>Итого: либо оба примера работают, как хотел МигМит, с одинаковым кол-вом проверок, либо оба не используют проверки, как Си++, но и никакой вводимой длины списка не будет, а только определённая в compile-time.
+1000
спасибо за полноценное пояснение происходящего в рантайм, но вопрос насчет compile-time в силе.
V>>Таки нет. АлгТД имеет вполне конкретное определение. А тот факт, что классы типов конкретно в Хаскеле, с его, скажем так, особенностью представления модулей, вполне можно выразить через неявные АлгТД — это частность, которая может исчезнуть при другом представлении модулей. Просто сейчас компиляторам доступен "исходник" (пусть даже в распаршенном виде) всех используемых модулей, что можно в процессе компиляции статически сгенерить АлгТД по всем инстансам класса.
VE>Это шутка что ли или непонимание?
... VE>Что теперь? Туплы и ФВП у нас работают тоже благодаря особенностям представления модулей?
Я имел ввиду, что компилятор "видит" все инстансы классов типов из зависимых модулей.
Здравствуйте, DarkGray, Вы писали:
DM>>Ну так речь шла о типизации, а не о типе.
DG>если в понятие типа упоминается о способе хранения в памяти, а в более сложном понятии (типизация или type system) — этой составляющей нет, то можно сделать вывод, что определение сложного понятия как минимум не полное.
По-твоему, всякое определение должно включать в себя определения и подробности всех используемых терминов? Это нереально. Человечество придумало другой способ, получше: использовать лишь названия терминов, определения которых даются отдельно.
DG>>>, во-вторых: это ужасное определение: определение должно определять понятие через более простые, а в данном случае — определение элементарного понятия "type system" идет через такиие неопределяемые понятия как: syntactic, framework, classifying, phrases, kind, compute, according. DG>>>можно получить строгое формализованное определение всех перечисленных понятий?
DM>>Можно. А нужно?
DG>строго необходимо, если стоит задача получить корректное описание реальности.
Ок, займусь завтра. Только как бы это не вылилось в просьбы с твоей стороны дать определения всем-всем словам русского и английского языков, так мы нескоро закончим. Я (как и цитируемый в вики Пирс) надеялся на наличие у собеседников каких-то общих знаний, но видимо зря.
DM>>Это одна структура в Си, содержащая внутри union. Она трактуется по-разному в зависимости от значения определенного поля.
DG>здесь неявно подразумевается, что одна структура в C — это строго один математических тип. DG>из какого определения это следует?
Из определения структур в языке Си.
DM>>Я и говорю, подходов к определению может быть много. DG>определения все одинаковые?, или можно их классифицировать чем они лучше/хуже по тем или иным признакам?
Здравствуйте, vdimas, Вы писали:
VE>>И if именно это и делает.
V>Там, где в рамках статически-типизированного языка не выполняется реинтерпретация памяти, то if этого не делает ни разу.
Делает, делает.
VE>>Не путайте то, какие байтики лежат, с типом. В случае указателя конкретное устройство значения одно и то же — указатель, но тип тем не менее разный.
V>Это ты путаешь. Одинаковое устройство даже двух объявленных одинаково типов не гарантируется в том же С++. А в плане указателей (при чем тут они, кстати?), если работаем именно со значением указателя, а не с указываемой областью памяти, то одинаковое устройство указателей проявляется в полной мере — мы можем присвоить void * значение типизированного указателя, преобразовать в число, получить из числа и т.д. Попробуй преобразовать в число, скажем, struct A {}.
Типы Base * и Child * — разные. Не Base и Child, а именно Base * и Child *. Где тут реинтепретация памяти?
VE>>
VE>>Formally, a type can be defined as "any property of a programme we can determine without executing the program"
VE>>Вы же почему-то под типом подразумеваете какое-то внутреннее представление.
V>Почему нет, если речь идет непосредственно об этом представлении? Например, когда мы обсуждаем компилятор статически-типизируемого языка, а не некий интерпретатор.
Потому, что тогда мы придём к вырожденному случаю: при любом ветвлении программа ведёт себя с памятью по-разному.
Т.е. проверка на if (x == 0) меняет её работу с памятью, т.е. приводит к различной её интерпретации.
И именно поэтому важно не то, что с памятью работают иначе (ибо это всегда так), а то, как на это дело наложена система типов.
V>И да, содержимое АлгТД, кроме вырожденных случаев (на которые тебя упорно тянет), невозможно определить ДО выполнения. Именно поэтому в ПМ по дискриминатору АлгТД более одной ветки, под все необходимые случаи, возможные в рантайм. А ведь в реальности многие из этих веток могут быть "мертвыми", т.е. не быть вызванными ни разу. Как тебе такое "property of a programme we can determine without executing the program"?
Мёртвыми могут быть ветки и в банальном Си. Иногда компилятор это даже умеет определять и оптимизировать. К типизации это отношения не имеет.
Или что, назовём динамической типизацией лишние if, когда согласно алгоритму в этот if мы никогда не попадём (но при этом выяснить это в compile-time = решить проблему останова)?
VE>>Мол, что (x:int){x==0}, что (x:int){x>0}, один чёрт int, 4 байта, да?
V>Да, значение дискриминатора (т.е. некое его представление) само имеет некий тип, а что? Тебя смущает, что этот тип тебе не доступен в общем случае? ИМХО, лишь для того, чтобы не ограничивать способ реализации. Но для случая встроенных интегральных типов Хаскеля — доступен. Т.е. для некоего мн-ва встроенных типов, для которых объявлено, что они являются АлгТД, прямо в спецификации указан способ представления дискриминаторов этих АлгТД. Теперь полегчало?
Чего? Ты читаешь, что я пишу? Причём тут АлгТД вообще?
Давай перепишу иначе:
(x:int){x > 10} и (x:int) { 10 * x + 23 < 2332 } — это разные типы. Но по-твоему получается, что нет.
Нотация (x:t){f(x)} означает, что x принадлежит подмножеству t такому, что выполняется f(x). Это тип. Никакой f не вычисляется в runtime и даже не существует.
Типы разные, представление — абсолютно одинаковое.
VE>>Ну так и указатель любой 4 (или 8) байта, ан нет, типы-то разные!
V>Ну и каша... В одном и том же варианте все значения дискриминаторов представлены одним и тем же типом by design алгебраического типа. Т.е. заведомо совместимы. А указатели разных типов в С++ сделаны несовместимыми, в отличие от С, именно затем, чтобы процедуру реинтерпретации памяти описывать в коде явно через специально предназначенные синтаксические конструкции реинтерпретации памяти, а не через простое присвоение указателей как в С.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, VoidEx, Вы писали:
V>>>Тогда нафига классы типов вообще нужны? VE>>Чтобы не передавать Eq a явно. Правда, для этого вовсе не обязательно надо было городить классы типов.
V>А что можно?
implicit arguments
V>>>Например, обсуждаемый пример, где Nil и Cons — это совершенно независимые типы, а не конструкторы одного АлгТД. И где де-факто рекурсия была сделана на полиморфном аргументе, а не рекурсивном АлгТД. Т.е. классы типов позволяют использовать в алгоритмах независимые типы.
VE>>Этот пример переписывается без использования классов типов один в один.
V>А обсуждаемый compile-time контроль длины массива остается?
Да.
V>>>Таки нет. АлгТД имеет вполне конкретное определение. А тот факт, что классы типов конкретно в Хаскеле, с его, скажем так, особенностью представления модулей, вполне можно выразить через неявные АлгТД — это частность, которая может исчезнуть при другом представлении модулей. Просто сейчас компиляторам доступен "исходник" (пусть даже в распаршенном виде) всех используемых модулей, что можно в процессе компиляции статически сгенерить АлгТД по всем инстансам класса.
V>Я имел ввиду, что компилятор "видит" все инстансы классов типов из зависимых модулей.
Считайте, что они импортируются неявно. Система типов от этого не страдает.
Здравствуйте, DarkGray, Вы писали:
DG>пока ты копируешь определения из википедии — никакого сознания нет, есть лишь тот самый автомат из китайской комнаты, который механически подставляет необходимую ссылку на википедию, когда встречает ключевые слова. DG>сознание определяют как умение выстраивать логические цепочки и гибкое адаптирование к контексту. DG>при копировании определения из вики — ни первого, ни второго — нет.
Во-первых, не надо путать сознание (consciousness) и интеллект/разум (intelligence), это очень разные вещи. Во-вторых, когда я цитирую википедию, я опираюсь на общую базу знаний и надеюсь, что используемые там слова собеседникам известны. В противном случае коммуникация крайне трудна. В-третьих, китайская комната как раз показывает, что даже демонстрация выстраивания логических цепочек не позволяет определить наличие "настоящего" разума. Зато логические ляпы вроде твоего позволяют показать недостаток разумности.
Здравствуйте, DarkGray, Вы писали:
DM>>Никакой специализации компилятор не сделает, на эффективность dummy_id твой код не повлияет, там внутри по-прежнему будет по 2 if'a.
DG>можно ли при этом сгенерировать более эффективный код? хотя бы руками?
В том конкретном случае, когда все аргументы известны при компиляции, можно суперкомпиляцией сразу получить программу, выводящую ответ, все вычисления выкинув.
Здравствуйте, VoidEx, Вы писали:
VE>Отличие в том, что instance может быть только один, и передаётся он неявно.
Именно, достаточен один экземпляр таблицы ф-ий, который компилятор построит сам.
Странно, что мои аналогичные предположения, как это может быть реализовано, были оспорены здесь: http://www.rsdn.ru/forum/decl/4675175.1.aspx
Здравствуйте, vdimas, Вы писали:
DM>>Типизация — это навешивание на каждое выражение в программе некоторого ярлыка-типа. Можем в компайл-тайме навесить достаточно конкретные типы на все — получили статическую типизацию. Не можем и вынуждены навешивать один неконкретный ярлык dynamic — получили динамическую. V>Речь была о динамической типизации в рамках статически-типизированных языков. И с какой радости dynamic должен быть один?
Я не говорю, что должен. Это лишь пример.
V>>>Да и о чем спорить? По-определению АлгТД — это такой тип данных, в который упаковываются значения других типов. Вариант, он и в Африке вариант. DM>>Это фиговое и неправильное определение, ведь "внутри" может оказаться он сам, не только "другие типы".
data Tree a = Leaf | Node a (Tree a) (Tree a)
V>Вот, популярная ошибка. В общем виде такая структура в статически-типизированном языке представима только через боксирование (уже доказано чуть выше по ветке, почему именно так и никак иначе). Поэтому хранится не само значение типа, а его боксированное представление.
Ну так и сам тип Tree a — такой же точно боксированный, если уж вдаваться в эти тонкости. Поэтому слева и справа от = тип один и тот же упоминается. Так что ошибка у тебя.
Здравствуйте, VoidEx, Вы писали:
VE>>>Чтобы не передавать Eq a явно. Правда, для этого вовсе не обязательно надо было городить классы типов. V>>А что можно? VE>implicit arguments
А придумать синтаксис, чтобы не конфликтовал с имеющимся?
Классы типов Хаскеля хороши тем, что идут в синтаксисе как бы "сбоку", предоставляя затем доступ к своему механизму так же как обычным ф-иям.
V>>Я имел ввиду, что компилятор "видит" все инстансы классов типов из зависимых модулей. VE>Считайте, что они импортируются неявно. Система типов от этого не страдает.
Это если рассматривать систему типов в отрыве от путей ее реализации... Мне во всей этой беседе как раз был интересен момент реализации обсуждаемых св-в системы типов (ПП) и именно для случая статически-компилируемых языков.
Кстати, как раз выяснили в процессе обсуждения, что для полноценного ПП требуется вывод типов чуть помощнее, близкий по мощности к зависимым типам.
VE>Типы Base * и Child * — разные. Не Base и Child, а именно Base * и Child *. Где тут реинтепретация памяти?
Будет различная интерпретация числового значения адреса. Т.е. что касается адресной арифметики С++, то числовые значения адреса ЗАВИСЯТ от их типов, в отличие от С:
Т.е. сравнение указателей, например, происходит в зависимости от типов указателей, а не просто от внутреннего их числового представления.
В общем, адресная арифметика С++ такова, чтобы транзитивно протянуть зависимости от типа указателя до необходимой интерпретации памяти по этому указателю. Для base2Ptr будет сделано числовое смещение адреса относительно childPtr, чтобы указатель попал на область памяти типа Base2 в теле типа Child.
V>>Почему нет, если речь идет непосредственно об этом представлении? Например, когда мы обсуждаем компилятор статически-типизируемого языка, а не некий интерпретатор.
VE>Потому, что тогда мы придём к вырожденному случаю: при любом ветвлении программа ведёт себя с памятью по-разному.
Если представление памяти гарантировано одинаково, то нет. Твой факториал и сравнение на 0 и 1 тому пример, т.к. в Хаскеле целые числа — это случай выродженного алгТД, где значение алгТД представлено только значением дискриминатора. Т.е. преставление остатка значения варианта всегда будет пустой тупл, не требующий никакой интерпретации. А если представление в памяти разное, то я процесс приведения неизвестного устройства значения к известному и называю динамической типизацией для случая статически типизированного языка. Чем тебе не нравится приведение ссылочных типов в дотнете, например, или dynamic_cast в С++? Чем же отличается матч АлгТД?
VE>Т.е. проверка на if (x == 0) меняет её работу с памятью, т.е. приводит к различной её интерпретации.
если x — дискриминатор варианта, а затем происходит работа с непустым телом варианта соответствующего типа для x==0, то несомненно.
Почему на Хаскеле нельзя реализовать свою некую самописную систему алгебраических типов, а на С/С++ можно? Наверно потому, что Хаскель упрятал в себя технику реинтерпретации памяти, требуя от кода некий контракт, в виде конструкции ПМ, а в С/С++ эта техника доступна и позволяет реализовать АлгТД тысячами способов?
Например, бустовский variant абсолютно типобезопасен и так же требует некий контракт для матча дискриминатора варианта, это т.н. static visitor. Хотя конкретный метод визитора выбирается конечно динамически. Прямо как ветка ПМ.
VE>И именно поэтому важно не то, что с памятью работают иначе (ибо это всегда так), а то, как на это дело наложена система типов.
Есть мнение, что система типов — это лишь инструмент для разметки памяти под данные и связи кода с данными.
А при полном отсутствии хоть какого-нибудь вида полиморфизма, как в чистом С или ассемблере, система типов нужна только для разметки памяти. (В ассемблерах есть простые типы данных, а в некоторых даже составные)
V>>И да, содержимое АлгТД, кроме вырожденных случаев (на которые тебя упорно тянет), невозможно определить ДО выполнения. Именно поэтому в ПМ по дискриминатору АлгТД более одной ветки, под все необходимые случаи, возможные в рантайм. А ведь в реальности многие из этих веток могут быть "мертвыми", т.е. не быть вызванными ни разу. Как тебе такое "property of a programme we can determine without executing the program"?
VE>Мёртвыми могут быть ветки и в банальном Си. Иногда компилятор это даже умеет определять и оптимизировать. К типизации это отношения не имеет.
Имеет согласно твоего определения. Т.е. статически компилятор не способен узнать тип завернутого в алгТД значения. Способен только через техники суперкомпиляции, а в этой технике никакой системы типов уже нет. ЧТД.
VE>Или что, назовём динамической типизацией лишние if, когда согласно алгоритму в этот if мы никогда не попадём (но при этом выяснить это в compile-time = решить проблему останова)?
Правильно мыслишь. Поэтому система с зависимыми типами более мощная, что позволяет экономить некоторые из этих if, т.к. для кое-каких ограничений проблему останова решать необязательно, чтобы узнать требуемую ветку в compile-time.
VE>>>Мол, что (x:int){x==0}, что (x:int){x>0}, один чёрт int, 4 байта, да?
V>>Да, значение дискриминатора (т.е. некое его представление) само имеет некий тип, а что? Тебя смущает, что этот тип тебе не доступен в общем случае? ИМХО, лишь для того, чтобы не ограничивать способ реализации. Но для случая встроенных интегральных типов Хаскеля — доступен. Т.е. для некоего мн-ва встроенных типов, для которых объявлено, что они являются АлгТД, прямо в спецификации указан способ представления дискриминаторов этих АлгТД. Теперь полегчало?
VE>Чего? Ты читаешь, что я пишу? Причём тут АлгТД вообще? VE>Давай перепишу иначе: VE>(x:int){x > 10} и (x:int) { 10 * x + 23 < 2332 } — это разные типы. Но по-твоему получается, что нет. VE>Нотация (x:t){f(x)} означает, что x принадлежит подмножеству t такому, что выполняется f(x). Это тип. Никакой f не вычисляется в runtime и даже не существует. VE>Типы разные, представление — абсолютно одинаковое.
Дык, уточняй, что речь уже не о Хаскеле. Я откуда по двум скобкам пойму, что ты там пишешь?
Если ты начал говорить о зависимых типах, то вот тебе и ограничения области действия, которые вполне решают проблему останова. Т.е. нет смысла тащить признак типа в рантайм, коль в рантайм он заведомо не будет проверяться.
Здравствуйте, D. Mon, Вы писали:
V>>Вот, популярная ошибка. В общем виде такая структура в статически-типизированном языке представима только через боксирование (уже доказано чуть выше по ветке, почему именно так и никак иначе). Поэтому хранится не само значение типа, а его боксированное представление.
DM>Ну так и сам тип Tree a — такой же точно боксированный, если уж вдаваться в эти тонкости.
А вот это как раз необязательно, когда речь идет об иммутабельной системе типов, вроде обсуждаемой SystemF. Это может быть само значение, а не ссылка. И умение делать подобную оптимизацию составляет одну из характеристик ML-языков.
ИМХО, то, что некоторые языки не различают ссылочных типов и обычных вносит некоторую путаницу, как value/ref-типы дотнета. Т.к. ссылку на значение начинают считать за само значение. Это абсолютно верно в иммутабельной среде с т.з. значений, согласен, но типы при этом разные!
Т.е. компилятор берет приведение типов из ссылочных в обычные на себя прозрачно для программиста. А подробности всплывают только тогда, когда требуется обеспечить интероперабельность.
VE>Невозможность надо доказывать. То, что пока не привели такого определения, не означает, что его нет. VE>Например, навскидку, динамическая типизация: VE>Каждому типу a ставится в соответствие множество типов T свободных по a таких, для каждого t : T существуют примитивные параметрически полиморфные (с ограничением по множеству T) функции VE>
VE>cast : a -> t
VE>dynamicCast : t -> Maybe a
VE>
VE>При этом выполняется VE>
VE>backCast : a -> Prop
VE>backCast x = dynamicCast (cast x : t) === Just x
VE>
VE>Уточнение: сводобных по a означает, что t : T не содержит в своем выражении тип a. Т.е. например Either a b туда не подходит.
ок
теперь возьмем блок кода TResult F(TSource) такой, что:
1. содержит if
2. if нередуцируем (нельзя переписать данный блок кода так, чтобы он не содержал if-а)
3. код чистый
4. с точки зрения теории множеств, каждый тип есть множество значений
5. обозначим множество значений которое приходит на вход F как MSource, которое передается в block1 как MSource1, в block2 как MSource2 (это можно сделать, так как predicate — чистая функция, и одно и тоже значение из MSource, всегда будет строго попадать или в block1, или в block2)
6. из п.2(функция нередуцируема) множество MSource1 строго меньше MSource
7. из теории множеств следует, что наличие множеств MSource1 и MSource, где MSource1 является строгим подмножеством MSource задает однозначным образом следующие функции отличные от тривиальных
где тип TSource соответствует множеству MSource, тип TSource1 множеству MSource1
что и требовалось доказать, что каждый if в коде однозначно задает набор функций аналогичный набору функций при динамической типизации
соответственно, каждый if в коде образует динамическую типизацию.
Здравствуйте, vdimas, Вы писали:
DM>>Ну так и сам тип Tree a — такой же точно боксированный, если уж вдаваться в эти тонкости.
V>А вот это как раз необязательно, когда речь идет об иммутабельной системе типов, вроде обсуждаемой SystemF. Это может быть само значение, а не ссылка. И умение делать подобную оптимизацию составляет одну из характеристик ML-языков.
Тогда стоит рассмотреть и оптимизацию, когда значение АлгТД, состоящее из одного дискриминатора, хранится небоксированным. В общем, это уже тонкости реализации, к теории типов имеющие мало отношения.
V>ИМХО, то, что некоторые языки не различают ссылочных типов и обычных вносит некоторую путаницу, как value/ref-типы дотнета. Т.к. ссылку на значение начинают считать за само значение. Это абсолютно верно в иммутабельной среде с т.з. значений, согласен, но типы при этом разные!
Зависит от того, что считать типом. По-моему, типы одинаковые.
DM> По-твоему, всякое определение должно включать в себя определения и подробности всех используемых терминов? Это нереально. Человечество придумало другой способ, получше: использовать лишь названия терминов, определения которых даются отдельно.
Мало ли что там придумало человечество, еретиков сжигать тоже оно придумало, и в целом неплохо работало...
Важно не что человечество придумало, а что зафиксировано в научном подходе, а в научном подходе требуется, чтобы все используемые определения можно было однозначно восстановить и при этом требуется, чтобы после восстановления не было циклов.
Соответственно я тебя спрашиваю:
1. каким образом я могу восстановить используемые термины в твоем определении?
2. при попытках восстановления твоего определения я столкнулся с петлями, когда, например, type system определяется через classifying, а classyfing через несколько шагов определяется через type system. что мне с этим делать?
DM>Ок, займусь завтра. Только как бы это не вылилось в просьбы с твоей стороны дать определения всем-всем словам русского и английского языков, так мы нескоро закончим. Я (как и цитируемый в вики Пирс) надеялся на наличие у собеседников каких-то общих знаний, но видимо зря.
попробуй. только скорее всего не получится, навскидку type system является аксиоматическим понятием (как точка, множество или время) и не может быть определено, а может быть лишь описано для понимания через набор свойств.
отмечу что деление на определяемые понятия и на аксиоматические понятия очень важное, потому что во втором случае однозначного определения на самом деле нет и соответственно на него нельзя ссылаться при последующих логических построениях.
для аксиоматических понятий можно вводить лишь свойства и на них ссылаться.
DG>>можно ли при этом сгенерировать более эффективный код? хотя бы руками?
DM>В том конкретном случае, когда все аргументы известны при компиляции, можно суперкомпиляцией сразу получить программу, выводящую ответ, все вычисления выкинув.
позже я специально подчеркнул что я рассматриваю вариант, когда длина массивов и значения не известны на момент компиляции, а появляются в runtime-е. Это позволяет тот же пример сделать более выпуклым.
Здравствуйте, DarkGray, Вы писали:
DG>1. каким образом я могу восстановить используемые термины в твоем определении? DG>2. при попытках восстановления твоего определения я столкнулся с петлями, когда, например, type system определяется через classifying, а classyfing через несколько шагов определяется через type system. что мне с этим делать?
1,2: почитать учебник по теории типов, например. Тот же TAPL.
DM>>Ок, займусь завтра. Только как бы это не вылилось в просьбы с твоей стороны дать определения всем-всем словам русского и английского языков, так мы нескоро закончим. Я (как и цитируемый в вики Пирс) надеялся на наличие у собеседников каких-то общих знаний, но видимо зря.
DG>попробуй. только скорее всего не получится, навскидку type system является аксиоматическим понятием (как точка, множество или время) и не может быть определено, а может быть лишь описано для понимания через набор свойств.
Я не вижу оснований считать систему типов подобным понятием. Это такая же определябельная конструкция, как группа, кольцо, моноид, коалгебра и т.д.
Здравствуйте, DarkGray, Вы писали:
DG>>>можно ли при этом сгенерировать более эффективный код? хотя бы руками?
DM>>В том конкретном случае, когда все аргументы известны при компиляции, можно суперкомпиляцией сразу получить программу, выводящую ответ, все вычисления выкинув.
DG>позже я специально подчеркнул что я рассматриваю вариант, когда длина массивов и значения не известны на момент компиляции, а появляются в runtime-е.
Тогда кроме инлайнинга и кое-какого fusion'a (удаления промежуточных массивов или списков) там ничего ненаоптимизируешь.
DG>>пока ты копируешь определения из википедии — никакого сознания нет, есть лишь тот самый автомат из китайской комнаты, который механически подставляет необходимую ссылку на википедию, когда встречает ключевые слова. DG>>сознание определяют как умение выстраивать логические цепочки и гибкое адаптирование к контексту. DG>>при копировании определения из вики — ни первого, ни второго — нет.
DM>Во-первых, не надо путать сознание (consciousness) и интеллект/разум (intelligence), это очень разные вещи.
я их не путаю, я утверждаю, что первое невозможно без второго
и соответственно, на основе бритвы оккамы утверждаю, что если индивидуум не демонстрирует второе, то значит у него нет первого.
DM> Во-вторых, когда я цитирую википедию, я опираюсь на общую базу знаний и надеюсь, что используемые там слова собеседникам известны. В противном случае коммуникация крайне трудна.
этого не достаточно для коммуникации.
для успешной коммуникации слова должны быть не только известы, но и однозначно восстановимы до необходимый точности.
например, что такое красный всем известно, но невозможно однозначно вне контекста ответить на вопрос "Это красный?", когда человек показывает на фигуру цвета pink, orange, rose, ruby, rgb(255, 10, 0) и т.д.
DM> В-третьих, китайская комната как раз показывает, что даже демонстрация выстраивания логических цепочек не позволяет определить наличие "настоящего" разума. Зато логические ляпы вроде твоего позволяют показать недостаток разумности.
Есть подвох. Из теории информации следует, что для того, чтобы отличить логические ошибки от проблем при коммуникации (неточность при кодировании/декодировании, отличия в используемых словарях и т.д.) требуются специальные алгоритмы и большое кол-во попыток.
Если ты при коммуникации эти алгоритмы не используешь, то как ты отличаешь одно от другого?
Здравствуйте, vdimas, Вы писали:
V>А если представление в памяти разное, то я процесс приведения неизвестного устройства значения к известному и называю динамической типизацией для случая статически типизированного языка. Чем тебе не нравится приведение ссылочных типов в дотнете, например, или dynamic_cast в С++? Чем же отличается матч АлгТД?
В ПМ (для ML семейства) нет "приведения неизвестного устройства значения к известному" это тупо выбор _заранее_ известных вариантов.
Отличие от dynamic_cast принципиальное в рантайме ПМ ничего ни ищет, ни сравнивает типы, просто банально выполняет switch.
Если грубо перевести все на условный си получим:
enum {Type1_id, Type2_id, Type3_id};
struct Type1 {};
struct Type2 {};
struct Type3 {};
struct AlgTD
{
int type;
union
{
Type1 type1;
Type2 type2;
Type3 type3;
// ....
};
};
void tst(AlgTD x)
{
switch(x.type)
{
case Type1_id:
// ...break;
case Type2_id:
// ...break;
case Type3_id:
// ...break;
}
}
И никакой динамической типизации, только простейшее ветвление.
Здравствуйте, DarkGray, Вы писали:
DG>что и требовалось доказать, что каждый if в коде однозначно задает набор функций аналогичный набору функций при динамической типизации DG>соответственно, каждый if в коде образует динамическую типизацию.
Так с тобой-то я не спорю. Я-то как раз того же мнения. Чтоб либо динамическая типизация — любое ветвление, либо тогда АлгТД тоже не динамическая типизация. Мне же vdimas говорит, что if (x == 0) в Си — не динамическая типизация.
DM>Тогда кроме инлайнинга и кое-какого fusion'a (удаления промежуточных массивов или списков) там ничего ненаоптимизируешь.
вот есть следующий библиотечный код
type test = Int of int | Float of float | String of string
let dummy_id = function
| Int n -> n
| Float _ -> -1
| String _ -> -2
let dummy2 = function(ienumerable<test> items) = items.Select(item => dummy_id(item)).Sum();
и стоит задача написать(сгенерить) функцию main, которая на вход принимает последовательность байт (с возможностью random-доступа) вида:
network-int32 len;
последовательность<длина len, тип элемента network-int32> int-array;
network-int32 len2;
последовательность<длина len2, элемент float(4байта)> float-array;
network-int32 len3;
последовательность<длины len3,
элемент test,
элемент test хранится как строка(4байта длины + unicode string),
подтип int хранится как десятичная строка,
подтип float хранится как строка вида digit*[.digit*],
подтип String как строка,
отображение из строки в нужный подтип делается по лучшему совпадению:
строка из десятичных чисел - int,
digit*[.digit*] - float,
другое - String
>
len3 string-array;
и выводит на консоль результат:
dummy2(int-array);
dummy2(float-array);
какой самый эффективный вариант такой функции можно написать/сгенерить?
эффективность программы оценивается как (меньше — лучше): размер программы в байтах + 1e5 * кол-во тактов выполнения функции main
DG>>что и требовалось доказать, что каждый if в коде однозначно задает набор функций аналогичный набору функций при динамической типизации DG>>соответственно, каждый if в коде образует динамическую типизацию.
VE>Так с тобой-то я не спорю. Я-то как раз того же мнения. Чтоб либо динамическая типизация — любое ветвление, либо тогда АлгТД тоже не динамическая типизация. Мне же vdimas говорит, что if (x == 0) в Си — не динамическая типизация.
согласен ли ты с утверждением, что статическая типизация: это когда в исходнике if есть (в том или ином виде: ПМ, overloading и т.д.), а в генеренном коде if-а нет?
FR>Отличие от dynamic_cast принципиальное в рантайме ПМ ничего ни ищет, ни сравнивает типы, просто банально выполняет switch.
если все типы известны при генерации кода (новые типы после генерации не подгружаются, или при подгрузке новых типов происходит перегенерация кода), то dynamic_cast — это тоже банальный switch
Видимо, абстрактные примеры воспринимаются сложнее, поэтому перейду к конкретике.
Возьмём CPS-трансформацию, все АлгТД при этом исчезают. Исчезает ли динамическая типизация?
А теперь примеры:
1. Простой if-else
bool readFromConsole() { ... }
void test()
{
bool x = readFromConsole();
if (x)
foo(10);
else
bar("bar");
}
readFromConsole :: IO (Either Int String)
readFromConsole = do
x <- ...
if x then Left 10 else Right "bar"
test = do
v <- readFromConsole
case v of
Left x -> foo x
Right s -> bar s
readFromConsole :: (Int -> IO r) -> (String -> IO r) -> IO ()
readFromConsole left right = do
x <- ...
if x then left 10 else right "bar"
test = readFromConsole foo bar
2. Накопление результата списка
template <class It, class F>
F for_each(It b, It e, F f)
{
for (; b != e; ++b)
f(*b);
return f;
}
std::list<int> l = { 1, 2, 3 };
void test()
{
int r = 10;
for_each(l.begin(), l.end(); [&r] (int x) { r += x; });
}
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v [] = v
foldl f v (x:xs) = foldl (f v x) xs
l :: [Int]
l = [1, 2, 3]
test = foldl (+) 10 l
data ListAlgebra a r = ListAlgebra r (a -> r -> r)
foldList :: (a -> b -> a) -> a -> ListAlgebra b a
foldList f v = ListAlgebra nil cons where
nil = v
cons x xs = f xs x
l :: ListAlgebra Int r -> r
l (ListAlgebra nil cons) = cons 1 (cons 2 (cons 3 nil))
test = l (foldList (+) 10)
Look! No ADT!
В каких случаях, на твой взгляд, есть динамическая типизация, в каких нет, и почему?
Здравствуйте, DarkGray, Вы писали:
DM>>Во-первых, не надо путать сознание (consciousness) и интеллект/разум (intelligence), это очень разные вещи.
DG>я их не путаю, я утверждаю, что первое невозможно без второго DG>и соответственно, на основе бритвы оккамы утверждаю, что если индивидуум не демонстрирует второе, то значит у него нет первого.
А это ещё надо доказать. Связь между интеллектом и сознанием весьма и весьма туманна.
VE>А это ещё надо доказать. Связь между интеллектом и сознанием весьма и весьма туманна.
если не ударяться в мистику, то никакого тумана там нет.
сознание = способность ставить и решать задачи, а способность ставить и решать задачи определяется способностью применять интеллект (или другими словами, чем выше интеллект, тем более широкий круг задач поддается решению и тем более эффективно они решаются)
в частности, selfhood который часто включают в сознание, это для агента постановка и эффективное решение задачи:
1) декомпозиции мира по степени управляемости агентом
2) декомпозиция мира по степени зависимости от него поведения агента
DG>>>что и требовалось доказать, что каждый if в коде однозначно задает набор функций аналогичный набору функций при динамической типизации DG>>>соответственно, каждый if в коде образует динамическую типизацию.
VE>>Так с тобой-то я не спорю. Я-то как раз того же мнения. Чтоб либо динамическая типизация — любое ветвление, либо тогда АлгТД тоже не динамическая типизация. Мне же vdimas говорит, что if (x == 0) в Си — не динамическая типизация.
DG>согласен ли ты с утверждением, что статическая типизация: это когда в исходнике if есть (в том или ином виде: ПМ, overloading и т.д.), а в генеренном коде if-а нет?
Только если гарантированно.
DG>вот есть следующий библиотечный код
DG>
DG>type test = Int of int | Float of float | String of string
DG>let dummy_id = function
DG> | Int n -> n
DG> | Float _ -> -1
DG> | String _ -> -2
DG>let dummy2 = function(ienumerable<test> items) = items.Select(item => dummy_id(item)).Sum();
DG>
Я так понимаю, намёк на то, что если мы знаем, что у нас сплошные Int'ы, то мы могли бы не пользовать обобщённый test и тем самым избавиться от лишних проверок? Ну так это надо закодировать в системе типов.
А то иначе это аналогично
struct test
{
int type;
union
{
int x;
float y;
string z;
} value;
};
Здравствуйте, DarkGray, Вы писали:
VE>>А это ещё надо доказать. Связь между интеллектом и сознанием весьма и весьма туманна.
DG>если не ударяться в мистику, то никакого тумана там нет. DG>сознание = способность ставить и решать задачи, а способность ставить и решать задачи определяется способностью применять интеллект (или другими словами, чем выше интеллект, тем более широкий круг задач поддается решению и тем более эффективно они решаются)
Нет. Сознание — это не способность ставить и решать задачи.
Это как раз интеллект.
Сознание лишь информируется о решении задачи (и тогда мы восклицаем "Эврика!", но как именно получено решение — не понимаем) интеллектом.
VE>Я так понимаю, намёк на то, что если мы знаем, что у нас сплошные Int'ы, то мы могли бы не пользовать обобщённый test и тем самым избавиться от лишних проверок?
да
VE> Ну так это надо закодировать в системе типов.
так же намек на то, что такое кодирование явно делать не надо — это лишь приведет к дублированию информации в исходнике, и что такое кодирование может быть выведено автоматически при необходимости, вся информация для этого уже есть в приведенном куске кода.
VE>А то иначе это аналогично VE>
VE>struct test
VE>{
VE> int type;
VE> union
VE> {
VE> int x;
VE> float y;
VE> string z;
VE> } value;
VE>};
VE>
да, это описание аналогичное. вся информация уже есть. Но такое описание требует более сложного и трудоемкого алгоритма, который из такого описания выведет ответ аналогичный предыдущему.
VE>>>А это ещё надо доказать. Связь между интеллектом и сознанием весьма и весьма туманна.
DG>>если не ударяться в мистику, то никакого тумана там нет. DG>>сознание = способность ставить и решать задачи, а способность ставить и решать задачи определяется способностью применять интеллект (или другими словами, чем выше интеллект, тем более широкий круг задач поддается решению и тем более эффективно они решаются)
VE>Нет. Сознание — это не способность ставить и решать задачи. VE>Это как раз интеллект. VE>Сознание лишь информируется о решении задачи (и тогда мы восклицаем "Эврика!", но как именно получено решение — не понимаем) интеллектом.
в русском — сознание используется для определения двух разных явлений:
сознание как синоним осознание/понимание,
сознание как противопоставление подсознанию
ты сейчас скорее говоришь о второй трактовке, я же говорил о первой трактовке
VE>Сознание лишь информируется о решении задачи (и тогда мы восклицаем "Эврика!", но как именно получено решение — не понимаем) интеллектом.
эта фраза подразумевает, что мозг с точки зрения операции "решение задач" можно декомпозировать на независимые друг от друга части: сознание и интеллект.
в рамках какой декомпозиции это возможно?
DM>Я не вижу оснований считать систему типов подобным понятием. Это такая же определябельная конструкция, как группа, кольцо, моноид, коалгебра и т.д.
вот только type system и группа(моноид, кольцо и т.д.) соотносятся также как цвет и красный(зеленый, синий и т.д.)
при этом цвет является аксиоматическим понятием, а красный является определяемым понятием
Здравствуйте, DarkGray, Вы писали:
VE>>Нет. Сознание — это не способность ставить и решать задачи. VE>>Это как раз интеллект. VE>>Сознание лишь информируется о решении задачи (и тогда мы восклицаем "Эврика!", но как именно получено решение — не понимаем) интеллектом.
DG>в русском — сознание используется для определения двух разных явлений: DG>сознание как синоним осознание/понимание, DG>сознание как противопоставление подсознанию
DG>ты сейчас скорее говоришь о второй трактовке, я же говорил о первой трактовке
Нет, о первой.
Вот интересная статья по теме. Собственно, из статьи интересно то, чем сознание не является.
Изначальную постановку задачи осуществляет подсознание. Сознание, при неспособности решить задачу, формирует запрос к интеллекту, который уже решает задачу. При этом для автоматизированных задач не нужно сознание, а для простых (не требующих поиска закономерностей), — не нужен интеллект.
Интеллект — способность находить закономерности и проверять их. Сознание определяется интроспективностью, т.е. моделированием себя. Подсознание определяет инстинктивные интересы.
Уж кому кому, а программистам должно быть известно то забавное обстоятельство, что, порой, усиленно думая над особенно сложной проблемой, решения мы найти никак не можем, но стоит пойти прогуляться, как решение может настигнуть в самый неожиданный момент, главное при этом находиться в некоем режиме поиска решения, но не осознанного, ибо на деле мы можем вообще заниматься чем-либо другим. Можно вспомнить того же доктора Хауса, где в каждой серии неизменно его осеняет вовсе не тогда, когда он напряжённо перебирает варианты. И это вполне себе типично. Интеллект, отделённый от сознания, решил поставленную сознанием задачу, и уведомил об этом. В свою очередь сознание, пользуясь решением интеллекта, формирует решение для подсознание по удовлетворению инстинкта.
То, что подсознание обращается к сознанию, сомнения не вызывает. Как только автоматика попадает в исключительную ситуацию (по пути встретилась лужа), она бесцеремонно прерывает вашу беседу с товарищем и требует немедленного принятия решения, как эту лужу обойти. Однако интеллект тут не нужен, достаточно примитивной оценки. Интеллект задействуется при сложных задачах по поиску закономерностей. И если человек дурак, то он может с неделю смотреть на задачу, как баран на новые ворота, но "Эврика!" к нему просто не явится.
Правда, считать ли способность обходить лужи, сознанием, тот ещё вопрос. С одной стороны, у человека именно сознание на это дело прерывается (вспомним, как мы учимся ездить на велосипеде, например, всё сознание сконцентрированно на процессе до тех пор, пока он не будет доведён до автоматизма), с другой стороны самосознание, вполне возможно, не является необходимым. Под самосознанием скорее понимается способность понимать своё собственное существование. Интеллекту это не нужно для выполнения его функций, подсознанию тоже (это API к инстинктам). Сознание же, как звено между интеллектом и подсознанием, должно уметь переводить задачи с языка подсознания (которое интересуется только приземлёнными инстинктами, т.е. эгоцентрично по своей сути) на язык абстрактного интеллекта, которому как раз на конкретный субъект наплевать; а затем делать перевод обратный, пользуясь плодами абстрактного интеллекта применительно к собственной заднице. А для этого как раз нужно осознавать себя.
VE>Нет, о первой. VE>Вот интересная статья по теме. Собственно, из статьи интересно то, чем сознание не является.
VE>Изначальную постановку задачи осуществляет подсознание. Сознание, при неспособности решить задачу, формирует запрос к интеллекту, который уже решает задачу. При этом для автоматизированных задач не нужно сознание, а для простых (не требующих поиска закономерностей), — не нужен интеллект.
слишком упрощенно.
Например, на текущий момент считается, что невозможно научиться неосознанно точно умножать два десятизначных числа (что является элементарной задачей для калькулятора). Такое умножение можно сделать лишь осознанно в столбик в уме (при сильно развитой кратковременной памяти) или на листке бумаги (если объем кратковременной памяти обычный или меньше).
Другими словами подсознание обособленно от сознания не может решить задачи (даже теоретически), где требуется строгое применение развитого математического аппарата.
VE>Интеллект — способность находить закономерности и проверять их.
это скорее способность больших нейронных сетей, чем способность интеллекта.
При этом такое выделение делается неточно, часто с нарушением законов логики (причинно-следственного аппарат, правильности декомпозиции/композции и т.д.)
Соответственно, сознание необходимо для того, чтобы логическим образом перепроверить гипотезы выдвинутые подсознанием.
VE> Сознание определяется интроспективностью, т.е. моделированием себя. Подсознание определяет инстинктивные интересы.
VE>Уж кому кому, а программистам должно быть известно то забавное обстоятельство, что, порой, усиленно думая над особенно сложной проблемой, решения мы найти никак не можем, но стоит пойти прогуляться, как решение может настигнуть в самый неожиданный момент, главное при этом находиться в некоем режиме поиска решения, но не осознанного, ибо на деле мы можем вообще заниматься чем-либо другим.
да такое явление есть: при снятии осознанного контроля растормаживаются области ответственные за эмиссию догадок(гипотез) несвязанных напрямую с решением задачи.
Но в тоже время есть способы генерить догадки осознанно и более эффективно. Часть методов осознанной генерации догадок рассматривается в том же триз-е, технологиях мозгового штурма и т.д.
зы
как в твоих утверждениях соотносится интеллект и рациональное мышление?
для меня это близкие понятия, при этом я утверждаю, что подсознание плохо "поддерживает" рациональное мышление (в частности, подсознание плохо "поддерживает" строгие логические выводы, строгие математические преобразования и т.д.), соответственно для меня немного дико видеть утверждение, что интеллект "зашит" в подсознание
другими словами, мозг (как всякая нейронная сеть) не может научиться выполнять даже достаточно простые точные алгоритмы.
алгоритм перемножение двух десятичных чисел в столбик требует всего 60 бит на вход, 60 бит доп. памяти и порядка ста шагов(если перемножать в десятичной системе), но мозг не может научиться даже такому простому алгоритму.
Здравствуйте, DarkGray, Вы писали:
DG>слишком упрощенно. DG>Например, на текущий момент считается, что невозможно научиться неосознанно точно умножать два десятизначных числа (что является элементарной задачей для калькулятора). Такое умножение можно сделать лишь осознанно в столбик в уме (при сильно развитой кратковременной памяти) или на листке бумаги (если объем кратковременной памяти обычный или меньше).
Подозреваю, что кратковременная память (та, что оперирует объектами) существует в сознании, а не в подсознании. Перемножение чисел — оперирование объектами.
VE>>Интеллект — способность находить закономерности и проверять их.
DG>это скорее способность больших нейронных сетей, чем способность интеллекта.
Я сужу по тестам на IQ. Все они поголовно (нормальные, которые не имеют отсылке к культуре, т.е. там отсутвует "уберите лишний город" и прочие, там только кружочки-квадратики) — на поиск и проверку закономерностей.
Забавно то, что сами тесты закономерны; один раз разгадав эту закономерность, пациент решает процентов 70-80 задач (простых) мгновенно без привлечения интеллекта, пользуясь уже известной теорией.
DG>Соответственно, сознание необходимо для того, чтобы логическим образом перепроверить гипотезы выдвинутые подсознанием.
Интеллектом. При этом действительно, ощущение, что интеллект сам по себе проверку произвести не способен, а лишь выдаёт найденные закономерности. Далее мы сознательно пытаемся проверить, действительно ли закономерность имеет место быть. Если бы этим занимался интеллект, мы могли бы и не проверять. Тем не менее, нередко интеллект ошибается.
DG>зы DG>как в твоих утверждениях соотносится интеллект и рациональное мышление? DG>для меня это близкие понятия, при этом я утверждаю, что подсознание плохо "поддерживает" рациональное мышление (в частности, подсознание плохо "поддерживает" строгие логические выводы, строгие математические преобразования и т.д.), соответственно для меня немного дико видеть утверждение, что интеллект "зашит" в подсознание
Я не утверждал, что интеллект зашит в подсознание, наоборот, это три разных системы: подсознание, сознание и интеллект. При этом самоосознающая — вторая, так как находится на стыке абстрактной и инстинктивной систем.
Рациональное мышление, то, что механически оперирует логическими утверждениями, — сознание. Причём интеллектуальной работы тут нет — подставлять значения в логические выражения и делать выводы — это примитив. Но из-за ограниченности быстрой памяти и тут делают ошибки.
Интересно то, что плодами сознания может пользоваться подсознание, доведя некоторые действия до автоматики. Тогда при запросе "переключить скорость автомобиля" сознание уже не отвлекается.
При этом сознание, судя по всему, может пользоваться плодами интеллекта. Раз осознав указатели, проблем мы более с ними не испытываем, мы уже можем механически делать утверждения, пользуясь этим понятием. Хотя многие поначалу с трудом могут разобрать, что к чему.
Здравствуйте, DarkGray, Вы писали:
DG>другими словами, мозг (как всякая нейронная сеть) не может научиться выполнять даже достаточно простые точные алгоритмы. DG>алгоритм перемножение двух десятичных чисел в столбик требует всего 60 бит на вход, 60 бит доп. памяти и порядка ста шагов(если перемножать в десятичной системе), но мозг не может научиться даже такому простому алгоритму.
Я пока понимаю это так:
1. Подсознание не умеет оперировать объектами, его действия механические.
Например, переключение скорости заключается в переносе руки в область рычага, захват рычага, перемещение по схеме. Если во время действий происходит сбой, например в руку не поступил сигнал захвата рычага (т.е промахнулись), подсознание прерывает сознание на выполнение необходимых действий.
Получив решение от сознания (многократно), подсознание его может запомнить (механизировать) и пользоваться уже без привлечения сознания.
2. Сознание умеет оперировать объектами, но не может найти способы работы с неизвестными объектами.
Т.е. сознание может применять (мысленно) действия к объектам и т.о. выводить логические суждения. Однако натыкаясь на новый объект, сознание оказывается бессильно. Вступает в дело интеллект.
Получив решение от интеллекта, сознание также может впоследствии пользоваться лишь памятью об объекте и о применимых действиях.
3. Интеллект умеет находить закономерности. В частности, умеет классифицировать объект, или обобщать действия.
Если мы найдём какой-то новый орех, мы его бессильно крутим-вертим, пока нас не осенит, что его можно камнем стукнуть, как другие орехи. Т.е. интеллект то и дело пытается его классифицировать (найти общее с другими объектами). При этом иногда он может выдать нечто неординарное, найдя общее между, казалось бы, несвязанными объектами, и тогда говорят, что человек нестандартно мыслит.
Аналогично интеллект умеет находить общее и между действиями.
Стоит отметить, что даже классификация объекта может исполняться сознанием. Например, мы можем тупо переписать в таблицу свойства нового объекта (цвет, форма и т.п.) и найти общность между новым объектом и теми, что мы знаем. Однако сама способность построения таблиц — продукт работы интеллекта, который научил сознание ими оперировать. Что-то неординарное мы вряд ли таким образом можем найти.
Ко всему прочему сознание имеет возможность учиться и других людей. Мы можем научить человека строить таблицы, выписывать свойства, находить закономерности механически, без привлечения интеллекта.
DG>>другими словами, мозг (как всякая нейронная сеть) не может научиться выполнять даже достаточно простые точные алгоритмы. DG>>алгоритм перемножение двух десятичных чисел в столбик требует всего 60 бит на вход, 60 бит доп. памяти и порядка ста шагов(если перемножать в десятичной системе), но мозг не может научиться даже такому простому алгоритму.
VE>Я пока понимаю это так:
VE>1. Подсознание не умеет оперировать объектами, его действия механические.
по смыслу согласен, хотя формулировка мне не очень нравится
VE>2. Сознание умеет оперировать объектами, но не может найти способы работы с неизвестными объектами.
здесь не согласен. Сознание не умеет так делать лишь пока не знает алгоритма(не придумало и не получило его со стороны), что делать с неизвестным объектом.
Стандартный алгоритм работы с новым объектом:
1)"пошевелить" его: применить к нему все те действия, которые были успешны с другими объектами: подавить, потрясти, понюхать, послушать, попробовать на язык, рассмотреть каждую деталь и т.д. Желательно при этом сначала применять те действия, которые предположительно будут более успешными: дадут больше знания о новом предмете.
2)понаблюдать за результатом каждого действия
3)представить это в виде модели: функций, которые связывают вход (действия) с выходом (как объект менялся в ответ на действие), желательно в виде иерархической декомпозиции
4)связать данную модель с уже имеющимися моделями
5)"зазубрить" модель (опустить ее на подсознательный уровень)
зы
кстати п.1-2 могут опускаться при осознанном обучении, когда читается документация к новому объекту.
п.3 может опускаться, если знания об объекте представлены в виде эффективной модели. к сожалению, большинство документации не содержит в себе описания эффективных моделей объекта, а являются лишь сырой информацией.
VE>3. Интеллект умеет находить закономерности. В частности, умеет классифицировать объект, или обобщать действия. VE>Стоит отметить, что даже классификация объекта может исполняться сознанием.
согласен, если оба пункта объединить: закономерности можно выделять, как неосознанно, так и осознанно.
лучше всего использовать комбинированный подход: сознанием генерить различные картинки (например, используя стат. аппарат, разные способы визуализации информации и т.д., а с помощью подсознания пытаться увидеть в этом закономерности и различия)
VE>Стоит отметить, что даже классификация объекта может исполняться сознанием. Что-то неординарное мы вряд ли таким образом можем найти.
здесь не согласен. опять же мы не можем осознанно найти неординарное только пока не знаем алгоритма по поиску неординарного.
VE>Ко всему прочему сознание имеет возможность учиться и других людей. Мы можем научить человека строить таблицы, выписывать свойства, находить закономерности механически, без привлечения интеллекта.
обучение может быть как неосознанным (так называемый ремесленный подход: подмастерье повторяет все действия за мастером),
так и осознанным (так называемый инженерный или научный подход: знания поднимаются из подсознания и переводятся в форму пригодную для тиражирования, ученик — это знания получает, состыковывает со своим мировозрением и опускает на подсознание)
Здравствуйте, FR, Вы писали:
V>>А если представление в памяти разное, то я процесс приведения неизвестного устройства значения к известному и называю динамической типизацией для случая статически типизированного языка. Чем тебе не нравится приведение ссылочных типов в дотнете, например, или dynamic_cast в С++? Чем же отличается матч АлгТД?
FR>В ПМ (для ML семейства) нет "приведения неизвестного устройства значения к известному" это тупо выбор _заранее_ известных вариантов.
Удивил. Разве это:
"приведения неизвестного устройства значения к известному"
вызвало затруднение в прочтении?
Решил сказать тоже самое своими словами? Или в моем случае известно не настолько _заранее_ насколько у тебя?
Спать надо больше, коллега.
FR>Отличие от dynamic_cast принципиальное в рантайме ПМ ничего ни ищет, ни сравнивает типы, просто банально выполняет switch. FR>Если грубо перевести все на условный си получим:
Скипнул код. В коде ты привел классический union, содержимое которого как раз достоверно неизвестно до проверки метки, назначенной одному из завернутый в union типов.
И да, если это было возражение, то ты забыл нарисовать аналогичный пример с динамической типизацией, скажем, для механики дотнета, выраженной в описании этой механики на том же С. Т.к. я говорил, что это одно и то же, надо было привести обе механики и показать, что это не одно и то же.
Т.е. предположим, код на дотнете примерно такой:
if(type == typeof(Type1)) ...
else if (type == typeof(Type2)) ...
Нарисуй происходящее так же на С, потом сравни с собственным вариантом на switch и попробуй найти принципиальные отличия.
Если найдешь, вернемся к обсуждению... покажу, где у тебя ошибка.
Здравствуйте, VoidEx, Вы писали:
VE>Видимо, абстрактные примеры воспринимаются сложнее, поэтому перейду к конкретике.
Я понятия не имею, где у тебя сложности, если честно. Ты бы попытался сформулировать возражения до примеров. Мало ли, как можно трактовать примеры в разных ситемах типов?
VE>Возьмём CPS-трансформацию, все АлгТД при этом исчезают. Исчезает ли динамическая типизация?
В твоих примерах — ес-но. А ты не ты сам не увидел?
VE>1. Простой if-else
1. VE>
VE>readFromConsole :: IO (Either Int String)
VE>readFromConsole = do
VE> x <- ...
VE> if x then Left 10 else Right "bar"
VE>test = do
VE> v <- readFromConsole
VE> case v of
VE> Left x -> foo x
VE> Right s -> bar s
VE>
2. VE>
VE>readFromConsole :: (Int -> IO r) -> (String -> IO r) -> IO ()
VE>readFromConsole left right = do
VE> x <- ...
VE> if x then left 10 else right "bar"
VE>test = readFromConsole foo bar
VE>
Ну? Сам уже понял? Отличные ведь примеры.
В первом случае ты слил в "бутылочное горлышко" АТД, т.е. потерял информацию о конкретном типе, затем ты "разлил из бутылочного горлышка" обратно, протестировав признак типа. А во втором случае вызвал целевые ф-ии ДО потери информации о конкретном типе.
Аналоги в дотнете, если тебе так будет понятней:
1.
object readFromConsole() {
bool x = ...
if(x) return 10; else return"bar";
}
void test() {
object v = readFromConsole();
if(v is int) foo((int)v);
else if(v is string) bar((string)v);
}
Аналог с той разницей, что object — это условно такой АТД, который объединяет всё мн-во типов дотнета, наследников object. Или можно взять какую-нить sealed-иерархию, чтобы ограничить мощность варианта в точности как в примере до {int, string}, но для сути примера это было вовсе не обязательно.
Здравствуйте, DarkGray, Вы писали:
DG>>позже я специально подчеркнул что я рассматриваю вариант, когда длина массивов и значения не известны на момент компиляции, а появляются в runtime-е.
DM>>Тогда кроме инлайнинга и кое-какого fusion'a (удаления промежуточных массивов или списков) там ничего ненаоптимизируешь.
DG>вот есть следующий библиотечный код DG>let dummy2 = function(ienumerable<test> items) = items.Select(item => dummy_id(item)).Sum(); DG>и стоит задача написать(сгенерить) функцию main, которая на вход принимает последовательность байт (с возможностью random-доступа) вида: DG>
network-int32 len;
последовательность<длина len, тип элемента network-int32> int-array;
network-int32 len2;
последовательность<длина len2, элемент float(4байта)> float-array;
...
DG>и выводит на консоль результат:
Стоп-стоп, ты выше ясно сказал, что значения не известны на момент компиляции. Т.е. мы не знаем, что в каком-то массиве будут только инты.
Дальше, ты тут предполагаешь, что компилятор может угадать и вставить неявное преобразование int -> test вида fun x -> Int x.
Но в любом приличном языке это ошибка, так нельзя. Ведь test мог и так быть определен:
type test = Int1 of int | Int2 of int | Float of float | String of string
В общем виде нет однозначного преобразования int -> test. И даже в нашем частном случае нет оснований считать, что упомянутое выше — то, что нам нужно, а не такое, например:
fun x -> Float (float_of_int (x*3))
Ты отдаешь отчет в том, что для компилятора имена дискриминаторов АлгТД не несут смысла? Что вместо Int of int можно написать Bear of int?
DG>слишком упрощенно. DG>Например, на текущий момент считается, что невозможно научиться неосознанно точно умножать два десятизначных числа (что является элементарной задачей для калькулятора). Такое умножение можно сделать лишь осознанно в столбик в уме (при сильно развитой кратковременной памяти) или на листке бумаги (если объем кратковременной памяти обычный или меньше).
Научиться этому, наверное, невозможно. Но люди, которые могли это делать, науке известны. Вот, например:
В XVIII веке внимание ученых привлекали уникумы-математики или чудо-счетчики, как их еще называют. Одним из таких чудо-счетчиков был Джедедая Бакстон. Он был не только неграмотен, но и глуп, однако мог решать фантастические по сложности математические задачи даже во время разговора или работы. Однажды ему задали такую задачу: сколько денег понадобится, чтобы подковать лошадь ста сорока гвоздями, если один гвоздь стоит один фартинг, второй гвоздь стоит в два раза больше первого, третий в два раза больше второго и т. д.? Бакстон дал (почти правильный) ответ: 725 958 096 074 907 868 531 656 993 638 851 106 фунтов, 2 шиллинга и 8 пенсов. Когда его попросили возвести это число в квадрат (2139), он через два с половиной месяца назвал число, состоявшее из семидесяти восьми цифр. Хотя некоторые подсчеты занимали у Бакстона довольно продолжительное время, они не мешали ему работать, жить жизнью обыкновенного человека.
Обычно уникальные математические способности проявляются в раннем возрасте, когда ребенок почти не имеет практики арифметических вычислений. Естественно, такие способности обнаруживаются не только у аутичных детей. Джордж Паркер Биддер рос здоровым ребенком, но уже в раннем возрасте отличался уникальной способностью решать в уме сложные математические задачи. Так, он мог взять в уме логарифм любого числа с точностью до седьмого-восьмого знака после запятой и определить множители любого большого числа. Едва отец Джорджа убедился в его необыкновенных способностях, он тут же отправился с ним в поездку по Англии и Шотландии. Джордж Биддер (ставший впоследствии инженером-строителем с мировым именем) неоднократно пытался понять процесс, с помощью которого осуществляются сложные вычисления. Однако сначала сумел лишь отметить, что результат вычисления «неожиданно вспыхивает в сознании с быстротой молнии»[162]. Уместно отметить, что и сын Биддера обладал блестящими математическими способностями, хотя и не стал чудо-счетчиком.
Здравствуйте, DarkGray, Вы писали:
DG>если все типы известны при генерации кода (новые типы после генерации не подгружаются, или при подгрузке новых типов происходит перегенерация кода), то dynamic_cast — это тоже банальный switch
То это будет не dynamic_cast, а в нем основной смысл в том что он должен работать и на неизвестных во время компиляции типах и
без поиска по ветке наследования не обойтись.
Здравствуйте, vdimas, Вы писали:
FR>>В ПМ (для ML семейства) нет "приведения неизвестного устройства значения к известному" это тупо выбор _заранее_ известных вариантов.
V>Удивил. Разве это: V>"приведения неизвестного устройства значения к известному" V>вызвало затруднение в прочтении?
Нет.
Затруднения вызывает ваша альтернативная логика в данном вопросе.
Путаете динамическое ветвление и динамическую типизацию.
V>Решил сказать тоже самое своими словами? Или в моем случае известно не настолько _заранее_ насколько у тебя?
В твоем, например в случае dynamic_cast, конечно известно намного меньше.
dynamic_cast производит поиск соответствия в рантайме, успех при этом не гарантирован. В результате получаем указатель через который
можем исполнить код _не известный_ на этапе компиляции (например подгруженная dll).
ПМ ничего ни ищет в рантайме, все возможные пути исполнения и код который будет исполнятся известны заранее, он только передает
управление этим заведомо известным путям исполнения.
V>Скипнул код. В коде ты привел классический union, содержимое которого как раз достоверно неизвестно до проверки метки, назначенной одному из завернутый в union типов.
В случае АлгТД точно также содержимое совершенно достоверно известно до проверки метки. И как в случае union метка, в случае АлгТД
ПМ только актуализирует какой конкретный из заранее известных вариантов выбрать.
V>И да, если это было возражение, то ты забыл нарисовать аналогичный пример с динамической типизацией, скажем, для механики дотнета, выраженной в описании этой механики на том же С. Т.к. я говорил, что это одно и то же, надо было привести обе механики и показать, что это не одно и то же.
В дотнете к сожалению не разбираюсь.
V>Т.е. предположим, код на дотнете примерно такой: V>if(type == typeof(Type1)) ... V>else if (type == typeof(Type2)) ...
Не вижу тут полноценной динамической типизации.
Эмуляцию, притом не полную, ее средствами статики да вижу.
При полной эмуляции у нас типы Type1, Type2 и т. д. заранее не известны и (псевдо)код будет примерно такой:
typetable заполняется в рантайме.
V>Нарисуй происходящее так же на С, потом сравни с собственным вариантом на switch и попробуй найти принципиальные отличия. V>Если найдешь, вернемся к обсуждению... покажу, где у тебя ошибка.
Если брать настоящую динамическую типизацию, то паттерн матчинг я не нарисую, так как его смысл в ней
во многом теряется, вернее он вырождается в некую структурную распаковку, сравнивать которую со статическим
ПМ мало осмысленно.
Если взять такой элементарный динамический код:
def tst(a, b):
a.append(b)
То выполнятся он будет примерно так:
. определяем тип 'a' ищем в его списке методов 'append'
. если не нашли просматриваем всех предков и ищем метод там, если не нашли рантаймное исключение
. если нашли пробуем вызывать подставив 'b' если число аргументов не совпало снова рантаймное исключение
. успешно вызываем 'append'
Здравствуйте, vdimas, Вы писали:
V>Я понятия не имею, где у тебя сложности, если честно. Ты бы попытался сформулировать возражения до примеров. Мало ли, как можно трактовать примеры в разных ситемах типов?
Сложности с тем, что одна и та же программа, но по-разному наложенная на типизацию, в одном случае динамически типизирована, в другом — нет.
И ты вроде с этим соглашаешься, но при этом отрицаешь, что важно не поведение программы (поведение идентично, никаких реинтрпретаций памяти лишних не происходит), а только то, как поверх положена типизация.
VE>>Возьмём CPS-трансформацию, все АлгТД при этом исчезают. Исчезает ли динамическая типизация?
V>В твоих примерах — ес-но. А ты не ты сам не увидел?
Дело в том, что эта трансформация может быть произведена автоматически. Но даже если она не произведена, результирующий код эквивалентен.
При этом в одном случае типизация почему-то динамическая, а в другом — нет.
V>В первом случае ты слил в "бутылочное горлышко" АТД, т.е. потерял информацию о конкретном типе, затем ты "разлил из бутылочного горлышка" обратно, протестировав признак типа. А во втором случае вызвал целевые ф-ии ДО потери информации о конкретном типе.
Только вот код эквивалентен и механически преобразуется один в другой.
V>Аналоги в дотнете, если тебе так будет понятней:
Именно, что не аналоги.
V>1. V>
V>object readFromConsole() {
V> bool x = ...
V> if(x) return 10; else return"bar";
V>}
V>void test() {
V> object v = readFromConsole();
V> if(v is int) foo((int)v);
V> else if(v is string) bar((string)v);
V>}
V>
А если v is MyClass? Где передача MyClass onMyClass? HisClass onHisClass? И так для _всех_ типов.
Именно, что это не аналоги. Как говорится, прочувствуй разницу.
Теперь понял?
V>Аналог с той разницей, что object — это условно такой АТД, который объединяет всё мн-во типов дотнета, наследников object. Или можно взять какую-нить sealed-иерархию, чтобы ограничить мощность варианта в точности как в примере до {int, string}, но для сути примера это было вовсе не обязательно.
Не просто все, а все возможные и все добавляемые в будущем.
V>Еще вопросы?
Конечно. Ты пропустил пример про алгебры, где я убрал ADT вообще.
Любая программа может быть так трансформирована автоматически, ADT при этом исчезают, но программы остаются эквивалентными.
Исчезает ли динамическая типизация?
Внимание, вопрос: исчезает ли она, если код остается прежним, но компилятор внутри проводит такую трансформацию при компиляции?
Здравствуйте, DarkGray, Вы писали:
DM>>>>In computer science, a type system may be defined as "a tractable syntactic framework for classifying phrases according to the kinds of values they compute".
DG>>>, во-вторых: это ужасное определение: определение должно определять понятие через более простые, а в данном случае — определение элементарного понятия "type system" идет через такиие неопределяемые понятия как: syntactic, framework, classifying, phrases, kind, compute, according. DG>>>можно получить строгое формализованное определение всех перечисленных понятий?
Итак, попробую строго сформулировать своими словами. Основной пререквизит для понимания материала — теория множеств, буду исходить из того, что понятие множества и основные операции с ними читателю известны.
1. syntactic framework — это пара множеств (B, R), где B — некоторое множество базовых символов, "терминалов", а R — множество синтаксических правил составления выражений (phrases, термов). Правило диктует форму строящегося выражения, а также содержит множество предусловий на использованные в выражении подвыражения.
2. Phrase, выражение, терм определяется индуктивно:
а) всякий символ из множества терминалов B является термом.
б) результат применения правила из множества R к некоторым термам является термом.
Транзитивное замыкание правил R над B дает множество всех возможных фраз (термов) и определяет некоторый язык (чисто синтаксически).
Важные примеры:
1) B — множество вещественных чисел, R — множество правил вида E+E, E-E, E*E, E/E, (E), с предусловием что Е — выражение. Такая пара нам дает syntactic framework для описания арифметических выражений с числами.
2) B — множество примитивных значений языка ML ((), 0, 1, 2, 3,... 0.0, 0.1,... true, false, "", "a", "aa"...), R — множество правил построения составных значений ( (v,v), Left v, Right v, v + v, v — v...). Такая пара дает syntactic framework для описания языка значений (values).
3) В — множество примитивных типов языка ML (unit, int, float, bool, string...), R — множество правил построения составных типов (T*T, T+T, T -> T...). Получаем язык описания типов, но еще не систему типов, пока это просто язык, грамматика.
3. Пусть L — язык, заданный такой парой (BL,RL). Системой типов для языка L назовем пару (TL, TF), где TL — это некоторый syntactic framework для описания языка типов (TL = (BT,RT), где BT — множество базовых типов, RT — множество правил составления сложных типов), а TF — отображение фраз языка L во фразы языка TL. Оно каждой фразе исходного языка L сопоставляет фразу из языка типов TL, другими словами сопоставляет ей элемент множества всех возможных фраз языка TL. Такое сопоставление и есть classifying, типизация.
Обычно язык типов выбирается так, что каждый тип трактуется как абстракное описание некотрого множества значений. Например, тип int может отвечать за множество целочисленных значений из некоторого диапазона.
Важный нюанс: когда L — язык значений, описывающий возможные рантайм-значения, то процесс сопоставления происходит тоже в рантайме, получается динамическая типизация. Когда L — язык выражений, которые записываются в исходном коде на некотором ЯП, т.е. существуют в виде файла, статически, то процесс сопоставления можно провести без запуска программы, тоже статически, тогда получается статическая типизация.
Еще нюанс: TL может быть подмножеством L. Т.е. некоторые выражения L могут быть описаниями типов.
Отношения с памятью: сама по себе система типов в общем случае не обязана описывать отображение типов на байты в памяти программы. Этот отображение — внутренний вопрос конкретного компилятора или интерпретатора. Разные компиляторы/интепретаторы одного и того же языка могут представлять значения одного и того же типа этого языка в памяти по-разному. Например, тип значение "hello" типа String в языке Руби представлено в памяти по-разному в интепретаторах MRI 1.8, IronRuby и JRuby. Другой пример — значение 42 типа int в языке Окамл представлено в памяти по-разному при компиляции в x86 и x64. Но систему типов это не затрагивает.
Здравствуйте, FR, Вы писали:
FR>Затруднения вызывает ваша альтернативная логика в данном вопросе. FR>Путаете динамическое ветвление и динамическую типизацию.
Не путаю. Я уже говорил, что динамическая типизация — это и есть ветвление, результатом которого является выбор ветки кода для работы с уточненным типом. Сюда же можно отнести предикат, т.е. проверку типа.
FR>В твоем, например в случае dynamic_cast, конечно известно намного меньше. FR>dynamic_cast производит поиск соответствия в рантайме, успех при этом не гарантирован.
Ты сделал сразу две ошибки. Даже если брать твой код, то 1.switch — точно такой же поиск. 2. успех каждой отдельно взятой ветки тоже не гарантирован. А если брать полный аналог на С++, т.е. взять точные типы для сравнения, а не базовые, то для успеха каждой ветки потребуется ровно одно сравнение. Т.е. стоимость будет заведомо идентичная, бо во время dynamic_cast сравниваются адреса vtable, а у АлгТД в Хаскеле сравниваются адреса своей внутренней описательной структуры. Всё 1-к-1-му.
Далее, ты намекаешь — на контроль типов в Хаскеле? И как это связано с механикой? Это всего-лишь встроенный механизм типобезопасности. В С++ и в дотнете без проблем можно породить одноуровневую sealed-иерархию от некоего базового типа, чтобы получить точно такие же гарантии. На суть происходящего это не повлияет.
FR>В результате получаем указатель через который FR>можем исполнить код _не известный_ на этапе компиляции (например подгруженная dll).
Через указатель код исполнить нельзя, можно через таблицу ф-ий, т.е. через адрес ф-ии. В Хаскеле точно так же можно подать адрес неизвестной заранее ф-ии и исполнить.
FR>ПМ ничего ни ищет в рантайме, все возможные пути исполнения и код который будет исполнятся известны заранее, он только передает FR>управление этим заведомо известным путям исполнения.
А почему в С++ не передает заведомо известным путям исполнения? Слишком много предположений, хотя код для сравнения приведен так и не был.
FR>В случае АлгТД точно также содержимое совершенно достоверно известно до проверки метки. И как в случае union метка, в случае АлгТД FR>ПМ только актуализирует какой конкретный из заранее известных вариантов выбрать.
Ну так известно заранее, или надо актуализировать? Определись.
V>>И да, если это было возражение, то ты забыл нарисовать аналогичный пример с динамической типизацией, скажем, для механики дотнета, выраженной в описании этой механики на том же С. Т.к. я говорил, что это одно и то же, надо было привести обе механики и показать, что это не одно и то же.
FR>В дотнете к сожалению не разбираюсь.
Ну хорошо, покажи для Java.
V>>Т.е. предположим, код на дотнете примерно такой: V>>if(type == typeof(Type1)) ... V>>else if (type == typeof(Type2)) ...
FR>Не вижу тут полноценной динамической типизации.
Проверка типов — это одна из операций динамической типизации. Для дотнета, если следом написать приведение типа, то верификатор выкинет лишнюю проверку для этого приведения, т.е. произойдет простое копирование указателя. Вуаля, реинтерпретация памяти успешна:
Type type = obj.GetType();
if(type == typeof(Type1))
foo((Type1)obj);
else if(type == typeof(Type2))
bar((Type2)obj);
FR>Эмуляцию, притом не полную, ее средствами статики да вижу.
Нет, это полная динамика. Но вся динамика сводится к проверке адресов дескрипторов типов, так же как в Хаскеле или С++. 1-в-1.
FR>При полной эмуляции у нас типы Type1, Type2 и т. д. заранее не известны и (псевдо)код будет примерно такой:
Упс. Ты уверен, что понимаешь, что есть динамическая типизация?
FR>typetable заполняется в рантайме.
Она заполняется статически. Даже в случае зависимых модулей, навроде DLL, заполняется линковщиком точно так же, как ресолвятся любые зависимые символы при загрузке DLL, т.е. это такая же статическая зависимость от внешнего символа. На досуге нарисуй цепочку связанных vtable для C++, убедись, в какую сторону идет зависимость (коль повторно говоришь о цепочке наследования). Никогда эти связи не идут от базы к наследникам, т.е. никогда vtable динамически не заполняется, даже при ручной динамической подгрузке DLL.
FR>Если брать настоящую динамическую типизацию, то паттерн матчинг я не нарисую, так как его смысл в ней FR>во многом теряется, вернее он вырождается в некую структурную распаковку, сравнивать которую со статическим FR>ПМ мало осмысленно.
Ничего не понял. Что значит термин "структурная распаковка"? Чем не понравилось "реинтерпретация памяти"?
Я повторюсь, что динамическая типизация в статически компиллируемом языке это следующая 2-х тактная операция:
1. проверка метки типа.
2. реинтерпретация интересующей области памяти согласно метки типа.
В вырожденном случае предиката только п.1, но не суть.
FR>Если взять такой элементарный динамический код:
FR>
FR>def tst(a, b):
FR> a.append(b)
FR>
FR>То выполнятся он будет примерно так:
FR>. определяем тип 'a' ищем в его списке методов 'append' FR>. если не нашли просматриваем всех предков и ищем метод там, если не нашли рантаймное исключение FR>. если нашли пробуем вызывать подставив 'b' если число аргументов не совпало снова рантаймное исключение FR>. успешно вызываем 'append'
Еще раз, речь шла о динамической типизации в статически-типизириуемом языке. Даже если взять твой пример, например, для JS просто ищется мембер append в словаре-объекте. А вот затем уже происходит динамическая типизация, когда проверяется тип мембера append. Сначала выясняется, что это именно ф-ия, причем нужной арности, т.е. происходит реинтерпретация append-object к append-function[1] и только затем вызов. Вся разница с обсуждаемым в том, что в динамических языках эта механика происходит "унутре само", а в статически компилируемом ты должен описать ее явно, через динамическое приведение типов или ПМ.
Потому как объект JS это натуральный АлгТД:
String s | Number n | Function f | Dictionary d.
// JS-Array is Dictionary
И вот внутри этого множества типов работает вся динамическая типизация JS. И происходящее при этом в точности равно происходящему в ПМ на Хаскеле.
Для Схемы и Лиспа аналогично, только кол-во встроенных типов данных чуть больше, а Dictionary замени на Cons. Кстати, исходников простеньких реализаций Лиспа и Схемы дофига, можно взглянуть на происходящее там и убедиться, что я тебя не обманываю — точно такой же switch по меткам типа объектов, как ты привел пример на С сообщением выше.
FR>То есть сплошной поиск никак несводимый к switch.
Здравствуйте, vdimas, Вы писали:
V>Еще раз, речь шла о динамической типизации в статически-типизириуемом языке. Даже если взять твой пример, например, для JS просто ищется мембер append в словаре-объекте. А вот затем уже происходит динамическая типизация, когда проверяется тип мембера append. Сначала выясняется, что это именно ф-ия, причем нужной арности, т.е. происходит реинтерпретация append-object к append-function[1] и только затем вызов. Вся разница с обсуждаемым в том, что в динамических языках эта механика происходит "унутре само", а в статически компилируемом ты должен описать ее явно, через динамическое приведение типов или ПМ.
Не, разница ещё и в том, что там runtime словарь-объект, а в статическом — compile-time таблица.
Такая же разница, как void foo(int x) и template<int x> void foo();
можно и вызовы dummy2_* развернуть, но для более явного примера, будем считать,
что длина и значения массивов не известны на этапе компиляции, но тип элементов известен и он одинаковый для всех элементов внутри каждого массива
DM>Дальше, ты тут предполагаешь, что компилятор может угадать и вставить неявное преобразование int -> test вида fun x -> Int x. DM>Но в любом приличном языке это ошибка, так нельзя. Ведь test мог и так быть определен: DM>
DM>type test = Int1 of int | Int2 of int | Float of float | String of string
DM>
тогда это будет другой код, и будет требовать другого подхода
DM>В общем виде нет однозначного преобразования int -> test. И даже в нашем частном случае нет оснований считать, что упомянутое выше — то, что нам нужно, а не такое, например: DM>
fun x -> Float (float_of_int (x*3))
сейчас мы говорим о конкретном коде, и о том насколько его можно оптимизировать
DM>Ты отдаешь отчет в том, что для компилятора имена дискриминаторов АлгТД не несут смысла? Что вместо Int of int можно написать Bear of int?
фактически ты сейчас говоришь, что в коде не хватает описания неявных преобразований:
network_int32 -> Int
Int -> network_int32
DG>>слишком упрощенно. DG>>Например, на текущий момент считается, что невозможно научиться неосознанно точно умножать два десятизначных числа (что является элементарной задачей для калькулятора). Такое умножение можно сделать лишь осознанно в столбик в уме (при сильно развитой кратковременной памяти) или на листке бумаги (если объем кратковременной памяти обычный или меньше).
V_>Научиться этому, наверное, невозможно. Но люди, которые могли это делать, науке известны. Вот, например:
да, среди аутистов встречаются люди, которые умеют быстро считать.
у них фактически та часть мозга, которая отвечает за распознавание (или интеллект, как ты его называешь) "плохо подключена" и не выполняет свою основную задачу, и соответственно те же самые нейроны можно занять числами.
я к тому, что от подсознания не стоит ожидать чуда, и не стоит ожидать что оно само сможет правильно решить сложную задачу, в которой требуется большой объем строгих математических вычислений или логических выводов
Здравствуйте, DarkGray, Вы писали:
DM>>Стоп-стоп, ты выше ясно сказал, что значения не известны на момент компиляции. Т.е. мы не знаем, что в каком-то массиве будут только инты. DG>мне, кажется, ты не читаешь то, что написано в треде (об этом говорилось еще 80 постов назад) DG>можно и вызовы dummy2_* развернуть, но для более явного примера, будем считать, DG>что длина и значения массивов не известны на этапе компиляции, DG>но тип элементов известен и он одинаковый для всех элементов внутри каждого массива
Ну да, все правильно. В массиве [|Int 2; String "aa"; Float 4.0|] тип всех элементов одинаковый — test. Хрен его обсчет соптимизируешь. Подобной информации недостаточно для смелых оптимизаций.
DG>сейчас мы говорим о конкретном коде, и о том насколько его можно оптимизировать
Только ты продолжаешь его дописывать и дописывать.
DG>фактически ты сейчас говоришь, что в коде не хватает описания неявных преобразований: DG>network_int32 -> Int DG>Int -> network_int32 DG>так добавь их и реши задачу
Ты уже выше приводил нечто похожее на решение в таком случае. Там сумма dummy по массиву интов превращалась в умножение длины массива на константу, не вижу смысла повторять.
V>>Я понятия не имею, где у тебя сложности, если честно. Ты бы попытался сформулировать возражения до примеров. Мало ли, как можно трактовать примеры в разных ситемах типов?
VE>Сложности с тем, что одна и та же программа, но по-разному наложенная на типизацию, в одном случае динамически типизирована, в другом — нет.
Набор слов...
VE>И ты вроде с этим соглашаешься, но при этом отрицаешь, что важно не поведение программы (поведение идентично, никаких реинтрпретаций памяти лишних не происходит), а только то, как поверх положена типизация.
Я обсуждаю именно происходящее в рантайм. Т.е. механику. Ну да, одно и то же даже в рантайме можо достичь многими способами — не отрицаю. Но меня интересует общий случай, который не поддается, например, наивной оптимизации.
VE>>>Возьмём CPS-трансформацию, все АлгТД при этом исчезают. Исчезает ли динамическая типизация?
V>>В твоих примерах — ес-но. А ты не ты сам не увидел?
VE>Дело в том, что эта трансформация может быть произведена автоматически. Но даже если она не произведена, результирующий код эквивалентен.
Дело в том, что в момент такой транфсформации работает техника суперкомпиляции, но на этом этапе никаких типов уже нет, потому что это уже инлайнинг, распространение констант и т.д., а не тайплевел-вычисление, т.е. это обычные спекулятивные вычисления, то бишь эмуляция механики, происходящей в райнтайм. А метки типов — это уже такие же константы на этом этапе. И да, для дотнета эта техника так же может работать, т.к. там тоже есть инлайнинг.
VE>При этом в одном случае типизация почему-то динамическая, а в другом — нет.
Потому что если оптимизация не состоялась по каким-либо причинам (нетривиальная зависимость от данных, например), то в одном случае приходится делать это в рантайм, а в другом — не приходится.
V>>В первом случае ты слил в "бутылочное горлышко" АТД, т.е. потерял информацию о конкретном типе, затем ты "разлил из бутылочного горлышка" обратно, протестировав признак типа. А во втором случае вызвал целевые ф-ии ДО потери информации о конкретном типе.
VE>Только вот код эквивалентен и механически преобразуется один в другой.
Это неправда для общего случая. Ты просто привел такой код, который в любом языке может быть оптимизирован, чтобы исключить лишние рантайм-проверки.
V>>Аналоги в дотнете, если тебе так будет понятней:
VE>Именно, что не аналоги.
Полные аналоги.
VE>А если v is MyClass? Где передача MyClass onMyClass? HisClass onHisClass? И так для _всех_ типов. VE>Именно, что это не аналоги. Как говорится, прочувствуй разницу. VE>Теперь понял?
Нет, можно сформулировать почетче?
V>>Аналог с той разницей, что object — это условно такой АТД, который объединяет всё мн-во типов дотнета, наследников object. Или можно взять какую-нить sealed-иерархию, чтобы ограничить мощность варианта в точности как в примере до {int, string}, но для сути примера это было вовсе не обязательно.
VE>Не просто все, а все возможные и все добавляемые в будущем.
В примере идет работа со всеми другими типами, добавленными в будущем? Можно таки услышать аргументированное возражение на выделенное относительно твоих примеров? Просто любопытно.
V>>Еще вопросы?
Еще.
Ты таки знаешь, что есть sealed в дотнете или нет? А как объявить в С++ тип, от которого нельзя наследоваться? А если да, то к чему эта набишая оскомину (сорри) манера хаскелистов, когда нечем крыть, срываться на один и тот же "последний бастион" — автоматическую проверку множества допустимых значений АлгТД? Я ведь этот трюк знаю уже относительно давно, только поэтому приписал выделенное выше. Характерно, что ты это выделенное перескочил и таки привел стандартный "последний аргумент". Поздравляю!
VE>Конечно. Ты пропустил пример про алгебры, где я убрал ADT вообще. VE>Любая программа может быть так трансформирована автоматически, ADT при этом исчезают, но программы остаются эквивалентными.
Не любая, потому что это может порождать теоретически бесконечный код, в котором обыгрываются всевозможные комбинаторные сочетания сколь угодно вложенных АлгТД. И для заведомо неизвестного графа АлгТД в памяти породить такой код заведомо невозможно. Так что, не изобретай лишнего.
В общем, приведенная тобой в качестве возражения техника инлайна мало того, что улыбает сама по себе в кач-ве аргумента обсуждаемому, дык еще работает только там же, где она работает в других языках — где мощность требуемых спекулятивных вычислений не превышает некоего порога. Например для GCC оптимизатором просматривается вложенность всего до 10 уровней... но это уже десятая степень от фиг его знает какого основания (зависит от "ветвистости" кода).
VE>Исчезает ли динамическая типизация? VE>Внимание, вопрос: исчезает ли она, если код остается прежним, но компилятор внутри проводит такую трансформацию при компиляции?
Поправлю. При оптимизации, не при компиляции. Много чего при оптимизации может исчезнуть, не только проверки типов. Ты уверен, что в ту степь пошел в своих рассуждениях?
VE>Думай.
Да че думать, проходили уже более десятка лет назад. Ничего нового пока не услышал. Это всё так... объяснение реально происходящего для самих Хаскелистов.
DG>>если все типы известны при генерации кода (новые типы после генерации не подгружаются, или при подгрузке новых типов происходит перегенерация кода), то dynamic_cast — это тоже банальный switch
FR>То это будет не dynamic_cast, а в нем основной смысл в том что он должен работать и на неизвестных во время компиляции типах и FR>без поиска по ветке наследования не обойтись.
если используется компиляция в виде JIT-а, то оба утверждения верны одновременно:
типы грузятся в runtime-е && все типы известны на момент компиляции.
Для выполнения этого утверждения достаточно знать какой код необходимо перегенерить при последующих подгрузках новых модулей.
Здравствуйте, vdimas, Вы писали:
V>Не путаю. Я уже говорил, что динамическая типизация — это и есть ветвление, результатом которого является выбор ветки кода для работы с уточненным типом. Сюда же можно отнести предикат, т.е. проверку типа.
Нет это не динамическая типизация. Это динамическая диспетчеризация максимум.
V>Ты сделал сразу две ошибки. Даже если брать твой код, то 1.switch — точно такой же поиск.
Нет в случае switch все возможные пути исполнения _гарантировано_ известны на момент компиляции.
V>2. успех каждой отдельно взятой ветки тоже не гарантирован.
В случае АлгТД гарантирован.
V>А если брать полный аналог на С++, т.е. взять точные типы для сравнения, а не базовые, то для успеха каждой ветки потребуется ровно одно сравнение. Т.е. стоимость будет заведомо идентичная, бо во время dynamic_cast сравниваются адреса vtable, а у АлгТД в Хаскеле сравниваются адреса своей внутренней описательной структуры. Всё 1-к-1-му.
Для одного редкого частного случая, который позволяет совсем не использовать dynamic_cast.
dynamic_cast конечно по разному работает в разных компиляторах, но никак ни сводится к сравнению адресов vtable,
без rtti и содержащейся в ней информации об иерархии наследования он не работает.
V>Далее, ты намекаешь — на контроль типов в Хаскеле? И как это связано с механикой? Это всего-лишь встроенный механизм типобезопасности. В С++ и в дотнете без проблем можно породить одноуровневую sealed-иерархию от некоего базового типа, чтобы получить точно такие же гарантии. На суть происходящего это не повлияет.
Повлияет и кардинально, выбор становится полностью ограниченным, никакие рантайм ошибки кроме Match_failure (если ПМ был неполный)
просто невозможны.
FR>>В результате получаем указатель через который FR>>можем исполнить код _не известный_ на этапе компиляции (например подгруженная dll).
V>Через указатель код исполнить нельзя, можно через таблицу ф-ий, т.е. через адрес ф-ии. В Хаскеле точно так же можно подать адрес неизвестной заранее ф-ии и исполнить.
В Хаскеле посредством ПМ над АлгТД невозможно исполнить неизвестный код.
FR>>ПМ ничего ни ищет в рантайме, все возможные пути исполнения и код который будет исполнятся известны заранее, он только передает FR>>управление этим заведомо известным путям исполнения.
V>А почему в С++ не передает заведомо известным путям исполнения? Слишком много предположений, хотя код для сравнения приведен так и не был.
Потому что не может заранее знать все эти пути, они известны только в рантайме. В случае ПМ они известны во время компиляции.
FR>>В случае АлгТД точно также содержимое совершенно достоверно известно до проверки метки. И как в случае union метка, в случае АлгТД FR>>ПМ только актуализирует какой конкретный из заранее известных вариантов выбрать.
V>Ну так известно заранее, или надо актуализировать? Определись.
Известны заранее все возможные варианты ПМ тупо выбирает один из них, полный аналог сишного switch ничего в рантайме ни
добавить ни убавить, все абсолютно детерминировано.
FR>>В дотнете к сожалению не разбираюсь.
V>Ну хорошо, покажи для Java.
Яву тоже не знаю.
V>Нет, это полная динамика. Но вся динамика сводится к проверке адресов дескрипторов типов, так же как в Хаскеле или С++. 1-в-1.
Нет есть коренное отличие, все типы в динамике становятся известны, а большая часть даже и формируются только в рантайме.
FR>>При полной эмуляции у нас типы Type1, Type2 и т. д. заранее не известны и (псевдо)код будет примерно такой:
V>Упс. Ты уверен, что понимаешь, что есть динамическая типизация?
Я уверен что ты не понимаешь.
В динамике я могу без проблем сформировать новый тип во время исполнения, и почти ничего ни могу сказать
о типах во время компиляции.
FR>>typetable заполняется в рантайме.
V>Она заполняется статически. Даже в случае зависимых модулей, навроде DLL, заполняется линковщиком точно так же, как ресолвятся любые зависимые символы при загрузке DLL, т.е. это такая же статическая зависимость от внешнего символа.
DLL может без проблем эскпортировать одну функцию MyInterface *GetInterface(); и линкер ничего не сможет узнать что там
реально внутри. Но dynamic_cast будет работать.
V>На досуге нарисуй цепочку связанных vtable для C++, убедись, в какую сторону идет зависимость (коль повторно говоришь о цепочке наследования). Никогда эти связи не идут от базы к наследникам, т.е. никогда vtable динамически не заполняется, даже при ручной динамической подгрузке DLL.
Не вижу связи с vtable, для dynamic_cast достаточно информации в rtti о наследниках.
Это в С++ она заполняется статически, в питоне во время исполнения.
Но даже в C++ она полностью заполняется только в момент запуска приложения, то есть в рантайме.
V>Ничего не понял. Что значит термин "структурная распаковка"? Чем не понравилось "реинтерпретация памяти"?
Вот в питоне
x, y = (1, 2, 3) это структурная распаковка, то же самое в ML будет ПМ.
Отличие в том что при ПМ типы выведены и известны, при распаковке в динамике просто присваивание.
V>Я повторюсь, что динамическая типизация в статически компиллируемом языке это следующая 2-х тактная операция: V>1. проверка метки типа. V>2. реинтерпретация интересующей области памяти согласно метки типа.
Это не динамическая типизация. Придумай другой термин. Этот только все запутывает
и порождает абсолютно бестолковые и неконструктивные споры.
V>В вырожденном случае предиката только п.1, но не суть.
V>Еще раз, речь шла о динамической типизации в статически-типизириуемом языке. Даже если взять твой пример, например, для JS просто ищется мембер append в словаре-объекте. А вот затем уже происходит динамическая типизация, когда проверяется тип мембера append. Сначала выясняется, что это именно ф-ия, причем нужной арности, т.е. происходит реинтерпретация append-object к append-function[1] и только затем вызов. Вся разница с обсуждаемым в том, что в динамических языках эта механика происходит "унутре само", а в статически компилируемом ты должен описать ее явно, через динамическое приведение типов или ПМ.
Нету в статически-типизириуемом языке никакой динамической типизации.
V>Потому как объект JS это натуральный АлгТД: V>String s | Number n | Function f | Dictionary d. V> // JS-Array is Dictionary
Не знаю как в JS, но в питоне или руби объект никак ни аналог АлгТД, так как содержит неизвестное число вариантов подтипов,
и эти подтипы могут меняться и порождаться (в питоне метаклассы и оператор type) в рантайме.
V>И вот внутри этого множества типов работает вся динамическая типизация JS. И происходящее при этом в точности равно происходящему в ПМ на Хаскеле.
В питоне совершено не соответствует.
V>Для Схемы и Лиспа аналогично, только кол-во встроенных типов данных чуть больше, а Dictionary замени на Cons. Кстати, исходников простеньких реализаций Лиспа и Схемы дофига, можно взглянуть на происходящее там и убедиться, что я тебя не обманываю — точно такой же switch по меткам типа объектов, как ты привел пример на С сообщением выше.
Я смотрел, правда давно, там switch только по известным встроенным типам.
FR>>То есть сплошной поиск никак несводимый к switch.
V>Таки сводимый.
Нет число веток в таком "динамическом switch" неизвестно и может меняться по ходу выполнения.
Здравствуйте, VoidEx, Вы писали:
VE>Не, разница ещё и в том, что там runtime словарь-объект, а в статическом — compile-time таблица.
Ну так и язык обсуждался динамический, а не как у нас. И да, для того же Лиспа, при компиляции во внутреннее представление, некая ф-ия ищется лишь однажды по имени и лишь однажды происходит динамическая типизация найденного объекта, и лишь однажды проверяется ее арность — это происходит во время разбора исходников. А во время работы кода, при повторных вызовах, уже ничего не ищется, а просто вызывается. Т.е. хотя этот некий p-code построен динамически, вызов затем идет статически. Тоже интересный момент. Поэтому классический Лисп относительно шустрый, пошустрее классического JS.
Здравствуйте, vdimas, Вы писали:
V>Я обсуждаю именно происходящее в рантайм. Т.е. механику. Ну да, одно и то же даже в рантайме можо достичь многими способами — не отрицаю. Но меня интересует общий случай, который не поддается, например, наивной оптимизации.
Контрпример можно?
VE>>Дело в том, что эта трансформация может быть произведена автоматически. Но даже если она не произведена, результирующий код эквивалентен.
V>Дело в том, что в момент такой транфсформации работает техника суперкомпиляции, но на этом этапе никаких типов уже нет, потому что это уже инлайнинг, распространение констант и т.д., а не тайплевел-вычисление, т.е. это обычные спекулятивные вычисления, то бишь эмуляция механики, происходящей в райнтайм. А метки типов — это уже такие же константы на этом этапе. И да, для дотнета эта техника так же может работать, т.к. там тоже есть инлайнинг.
Это не суперкомпиляция, это тривиальное преобразование. Причём его можно производить в обе стороны без смены семантики И поведения.
Если это не так, прошу предоставить контр-пример.
V>Потому что если оптимизация не состоялась по каким-либо причинам (нетривиальная зависимость от данных, например), то в одном случае приходится делать это в рантайм, а в другом — не приходится.
Если оптимизация состоится всегда, то это не оптимизация.
V>Это неправда для общего случая. Ты просто привел такой код, который в любом языке может быть оптимизирован, чтобы исключить лишние рантайм-проверки.
Правда для общего случая. Разница в том, что общий случай с АлгТД имеет фиксированное число вариантов, в случае обычного ООП общий случай шире.
Ты мне можешь так же доказывать, что построение таблицы на switch — это оптимизация, потому что в общем случае enum — это число, а какое число — мы не знаем. Но у нас не "любое" число, а enum, и мы знаем при компиляции все возможные принимаемые значения. Т.е. эта т.н. "суперкомпиляция" работает всегда. И потому это не оптимизация.
Вот в некотором другом языке, где эти enum можно расширять, добавляя новые значения, это уже будет суперкомпиляцией и оптимизацией.
V>Полные аналоги.
Нет.
VE>>А если v is MyClass? Где передача MyClass onMyClass? HisClass onHisClass? И так для _всех_ типов. VE>>Именно, что это не аналоги. Как говорится, прочувствуй разницу. VE>>Теперь понял?
V>Нет, можно сформулировать почетче?
Количество наследников открыто. Если сделать закрытую иерархию, то можно будет сделать такое преобразование. Но для ООП — это частный случай, и потому оптимизация (чем ты и пытаешься аргументировать). Для ADT же это общий случай, работающий всегда.
Как с "любым числом" и enum'ом. Можно, конечно, говорить, что компилятор, который учёл, что у нас всего 3 значения enum'а (red green и blue), провёл суперкомпиляцию, но это не так. Вот если потенциально значение enum'ов бесконечно, но компилятор как-то догадался, что в данном случае их три — то суперкомпиляция. Здесь же ситуация другая.
V>Ты таки знаешь, что есть sealed в дотнете или нет? А как объявить в С++ тип, от которого нельзя наследоваться?
Вот в dotnet — это частный случай. А в Haskell — нет.
Аргументы будут? АлгДТ можно преобразовать в алгебру, которая есть кортеж функций.
Вот пример побольше:
-- data List a = Null | Cons a (List a)data ListAlgebra a r = ListAlgebra r (a -> r -> r)
-- foldl :: (a -> b -> a) -> a -> [b] -> a
foldList :: (a -> b -> a) -> a -> ListAlgebra b a
foldList f v = ListAlgebra nil cons where
nil = v
cons x xs = f xs x
-- l :: List Int
l :: ListAlgebra Int r -> r
-- l = [1, 2, 3]
l (ListAlgebra nil cons) = cons 1 (cons 2 (cons 3 nil))
-- test = foldl (+) 10 l
test = l (foldList (+) 10)
-- data Nat = Zero | Succ Natdata NatAlgebra r = NatAlgebra r (r -> r)
-- foldNat :: (a -> a) -> a -> Nat -> a
foldNat :: (a -> a) -> a -> NatAlgebra a
foldNat f v = NatAlgebra v f
-- nat :: Int -> Nat
-- просто для удобства, чтоб не писать succ (succ (succ zero))
nat :: Int -> NatAlgebra a -> a
nat 0 (NatAlgebra z s) = z
nat n alg@(NatAlgebra z s) = s $ nat (n - 1) alg
-- replicate :: a -> Nat -> List a
replicateA :: a -> NatAlgebra (ListAlgebra a r -> r)
replicateA v = NatAlgebra z s where
z (ListAlgebra nil cons) = nil
s f alg@(ListAlgebra nil cons) = cons v (f alg)
-- ADT
testS = foldl (+) 11 (replicate 5 3)
-- Algebras
testA = (nat 3) (replicateA 5) (foldList (+) 11)
В общем, без какого-либо осмысленного контраргумента дальше обсуждать тему бессымсленно.
Здравствуйте, vdimas, Вы писали:
V>Ты сделал сразу две ошибки. Даже если брать твой код, то 1.switch — точно такой же поиск. 2. успех каждой отдельно взятой ветки тоже не гарантирован. А если брать полный аналог на С++, т.е. взять точные типы для сравнения, а не базовые, то для успеха каждой ветки потребуется ровно одно сравнение. Т.е. стоимость будет заведомо идентичная, бо во время dynamic_cast сравниваются адреса vtable, а у АлгТД в Хаскеле сравниваются адреса своей внутренней описательной структуры. Всё 1-к-1-му.
Здравствуйте, FR, Вы писали:
V>>Не путаю. Я уже говорил, что динамическая типизация — это и есть ветвление, результатом которого является выбор ветки кода для работы с уточненным типом. Сюда же можно отнести предикат, т.е. проверку типа.
FR>Нет это не динамическая типизация. Это динамическая диспетчеризация максимум.
Называй как хочешь, но динамическая типизация в статически-компиллируемом языке — это приведение типов с проверкой, то бишь условное приведение, в отличие от безусловной реинтерпретации памяти. В безусловной реинтерпретации не происходит никаких действий в рантайм, связанных с типизацией. Т.е. никакой динамики.
FR>Нет в случае switch все возможные пути исполнения _гарантировано_ известны на момент компиляции.
На стоимость это фактически не влияет. Добавляется одна ветка кода, разве что. И да, в аналогичном С++ коде, где типы тоже заведомо известны, даже эту ветку можно не добавлять.
FR>В случае АлгТД гарантирован.
В случае sealed мн-ва тоже.
FR>Для одного редкого частного случая, который позволяет совсем не использовать dynamic_cast.
Не аргумент, т.к. абсолютно любой случай позволяет не использовать dynamic_cast. Пожалуйста — эмулируй работу динамической типизации сам. Например через АлгТД или наподобие COM.
FR>dynamic_cast конечно по разному работает в разных компиляторах, но никак ни сводится к сравнению адресов vtable, FR>без rtti и содержащейся в ней информации об иерархии наследования он не работает.
Значит ты не в курсе, как хранится rtti и при чем тут vtable для dynamic_cast?
V>>Далее, ты намекаешь — на контроль типов в Хаскеле? И как это связано с механикой? Это всего-лишь встроенный механизм типобезопасности. В С++ и в дотнете без проблем можно породить одноуровневую sealed-иерархию от некоего базового типа, чтобы получить точно такие же гарантии. На суть происходящего это не повлияет.
FR>Повлияет и кардинально, выбор становится полностью ограниченным, никакие рантайм ошибки кроме Match_failure (если ПМ был неполный) FR>просто невозможны.
Я не увидел пояснения отличий механики при этом. А если я в твой swicth добавлю ветку default:, механика кардинально изменится, что ле?
V>>Через указатель код исполнить нельзя, можно через таблицу ф-ий, т.е. через адрес ф-ии. В Хаскеле точно так же можно подать адрес неизвестной заранее ф-ии и исполнить.
FR>В Хаскеле посредством ПМ над АлгТД невозможно исполнить неизвестный код.
Если в алгТД хранится ф-ия — то можно.
V>>А почему в С++ не передает заведомо известным путям исполнения? Слишком много предположений, хотя код для сравнения приведен так и не был.
FR>Потому что не может заранее знать все эти пути, они известны только в рантайме. В случае ПМ они известны во время компиляции.
Это не ответ. Перечитай вопрос еще раз. Куда и какое управление передается во время самого процесса типизации?
V>>Ну так известно заранее, или надо актуализировать? Определись. FR>Известны заранее все возможные варианты ПМ тупо выбирает один из них, полный аналог сишного switch ничего в рантайме ни FR>добавить ни убавить, все абсолютно детерминировано.
Ну так выбирает или детерминировано?
Даже если всего два варианта — уже недетерминировано. Вот я и сравниваю механику приведения недетерминированного типа к детерминированному.
V>>Нет, это полная динамика. Но вся динамика сводится к проверке адресов дескрипторов типов, так же как в Хаскеле или С++. 1-в-1.
FR>Нет есть коренное отличие, все типы в динамике становятся известны, а большая часть даже и формируются только в рантайме.
Это ты какую-то другую динамику имеешь ввиду. Какие еще типы формируются в рантайм для С++?
FR>Я уверен что ты не понимаешь. FR>В динамике я могу без проблем сформировать новый тип во время исполнения, и почти ничего ни могу сказать FR>о типах во время компиляции.
Это не есть динамическая типизация в статически-компилируемом языке. Это ты о каком-то динамическом языке рассказываешь.
V>>Она заполняется статически. Даже в случае зависимых модулей, навроде DLL, заполняется линковщиком точно так же, как ресолвятся любые зависимые символы при загрузке DLL, т.е. это такая же статическая зависимость от внешнего символа.
FR>DLL может без проблем эскпортировать одну функцию MyInterface *GetInterface(); и линкер ничего не сможет узнать что там FR>реально внутри. Но dynamic_cast будет работать.
Ну так я не зря посоветовал на досуге нарисовать связь экземпляров vtable для этого случая. Никакой магии, всё на статике.
V>>На досуге нарисуй цепочку связанных vtable для C++, убедись, в какую сторону идет зависимость (коль повторно говоришь о цепочке наследования). Никогда эти связи не идут от базы к наследникам, т.е. никогда vtable динамически не заполняется, даже при ручной динамической подгрузке DLL.
FR>Не вижу связи с vtable, для dynamic_cast достаточно информации в rtti о наследниках.
Нет никакой информации о наследниках, есть только информация о базе. И сама rtti для типов, могущих быть аргументом dynamic_cast, хранится в vtable по отрицательному смещению. Поэтому идет просто сравнение адресов vtable, и только при неудачном сравнении сравниваются vtable из всех базовых классов рекурсивно (ссылки на которые хранятся в rtti), и т.д. Но если уровень наследования один, то все происходит с примерно такими же затратами, как твой switch. И выход на удачную ветку происходит ровно так же как в ПМ: сравнили адреса vtable, если совпали, то реинтерпретируем область памяти по указателю как значение другого типа.
FR>Это в С++ она заполняется статически, в питоне во время исполнения. FR>Но даже в C++ она полностью заполняется только в момент запуска приложения, то есть в рантайме.
В момент загрузки объектного модуля загрузчиком ОС. Это и называется статически. (Посмотри на таблицу экспорта, когда экспортируешь класс с виртуальными ф-иями из DLL и включен RTTI). Еще более статической техники просто не существует в природе. Ну кроме embedded софта, где нет никакой рантайм-загрузки приложений, а вся программная начинка — это одна скомпилированная программа по абсолютным адресам.
V>>Ничего не понял. Что значит термин "структурная распаковка"? Чем не понравилось "реинтерпретация памяти"?
FR>Вот в питоне
FR>x, y = (1, 2, 3) это структурная распаковка, то же самое в ML будет ПМ. FR>Отличие в том что при ПМ типы выведены и известны, при распаковке в динамике просто присваивание.
Т.е. ты в упор отказываешься сравнивать похожие техники у похожих по характеристикам языков? С чем именно тогда споришь?
V>>Я повторюсь, что динамическая типизация в статически компиллируемом языке это следующая 2-х тактная операция: V>>1. проверка метки типа. V>>2. реинтерпретация интересующей области памяти согласно метки типа.
FR>Это не динамическая типизация. Придумай другой термин. Этот только все запутывает FR>и порождает абсолютно бестолковые и неконструктивные споры.
Другой динамической типизации в статически типизируемом языке не существует, увы.
А нет, вру, существует на vtable при вызове виртуального метода — тоже происходит реинтерпретация типа this. И даже происходит коррекция числового адреса this, согласно реинтерпретации и примера здесь: http://www.rsdn.ru/forum/decl/4689256.1.aspx
V>>Еще раз, речь шла о динамической типизации в статически-типизириуемом языке. Даже если взять твой пример, например, для JS просто ищется мембер append в словаре-объекте. А вот затем уже происходит динамическая типизация, когда проверяется тип мембера append. Сначала выясняется, что это именно ф-ия, причем нужной арности, т.е. происходит реинтерпретация append-object к append-function[1] и только затем вызов. Вся разница с обсуждаемым в том, что в динамических языках эта механика происходит "унутре само", а в статически компилируемом ты должен описать ее явно, через динамическое приведение типов или ПМ.
FR>Нету в статически-типизириуемом языке никакой динамической типизации.
Ну так и надо было не обсуждать скриптовые языки, а сначала выяснить, есть или нет в статически-типизируемых языках динамическая типизация. Потому как есть.
V>>Потому как объект JS это натуральный АлгТД: V>>String s | Number n | Function f | Dictionary d. V>> // JS-Array is Dictionary
FR>Не знаю как в JS, но в питоне или руби объект никак ни аналог АлгТД, так как содержит неизвестное число вариантов подтипов, FR>и эти подтипы могут меняться и порождаться (в питоне метаклассы и оператор type) в рантайме.
Дык, а в Киеве дядька.
V>>И вот внутри этого множества типов работает вся динамическая типизация JS. И происходящее при этом в точности равно происходящему в ПМ на Хаскеле. FR>В питоне совершено не соответствует.
Очень жаль Питон.
V>>Для Схемы и Лиспа аналогично, только кол-во встроенных типов данных чуть больше, а Dictionary замени на Cons. Кстати, исходников простеньких реализаций Лиспа и Схемы дофига, можно взглянуть на происходящее там и убедиться, что я тебя не обманываю — точно такой же switch по меткам типа объектов, как ты привел пример на С сообщением выше.
FR>Я смотрел, правда давно, там switch только по известным встроенным типам.
А других и нет. Невстроенный — это cons, а удачно прочитанная парсером пользовательская ф-ия — это уже опять встроенный тип "пользовательская ф-ия", который имеет ссылку на сons — на аргументы ф-ии. Вот когда мы подаем на eval динамически построенный список, там опять происходит динамическая проверка, чтобы первый элемент списка был ф-ий.
FR>Нет число веток в таком "динамическом switch" неизвестно и может меняться по ходу выполнения.
Здравствуйте, FR, Вы писали:
DG>>если все типы известны при генерации кода (новые типы после генерации не подгружаются, или при подгрузке новых типов происходит перегенерация кода), то dynamic_cast — это тоже банальный switch
FR>То это будет не dynamic_cast, а в нем основной смысл в том что он должен работать и на неизвестных во время компиляции типах и
Сорри, но аргументами dynamic_cast не может быть неизвестный тип. Оба типа известны: из которого надо преобразовать и в который. Тебя, похоже, смущает, что фактически значением по указателю может быть некий наследник, неизвестный в момент компиляции, я правильно понимаю?
Не пробовал нарисовать, как это работает?
FR>без поиска по ветке наследования не обойтись.
Ну так это ветка идет исключительно к базе, какие проблемы? Всех наследников ведь перебирать не надо. А для случая безопасного одноуровневого sealed-множества наследников — это ровно одно сравнение на вариант, 1-в-1 как в ПМ.
Здравствуйте, vdimas, Вы писали:
V>Называй как хочешь, но динамическая типизация в статически-компиллируемом языке — это приведение типов с проверкой, то бишь условное приведение, в отличие от безусловной реинтерпретации памяти. В безусловной реинтерпретации не происходит никаких действий в рантайм, связанных с типизацией. Т.е. никакой динамики.
Надо полагать, мусье примеры ниже тоже называет динамической типизацией?
struct some // not union!
{
bool f;
int x;
float y;
};
void foo(some const & x)
{
if (x.f)
useInt(x.x);
else
useFloat(x.y);
}
А что, используем разные типы!
Если вдруг ты захочешь намекнуть, что для чёткости x и y должны были бы лежать в одном месте (как union), то на это я тебе отвечу:
template <class L, class R>
struct HaskellEither // not union!!!
{
bool which;
L * l;
R * r;
};
Опачки! Т.е. если в Haskell Either компилировать не в union с разметкой, а в вышеописанный struct, то внезапно динамическая типизация пропадает? Вот тебе раз!
Тогда давайте считать, что так и есть! А то, что на самом деле union, — всего лишь оптимизация.
По-моему, твоя теория всё более и более противоречивой становится.
DG>>если используется компиляция в виде JIT-а, то оба утверждения верны одновременно:
FR>JIT же не работает с исходным кодом, он компилирует некий PI-код в котором типизация не совпадает FR>с исходным.
во-первых:и что из этого утверждения следует?
во-вторых: утверждение неполное (и отражает лишь одну часть реального мира).
есть как языки, где JIT компилирует исходный код, есть где промежуточный,
есть как языки, где в PI-коде типизация совпадает с исходной, есть где не совпадает.
DM>Ты уже выше приводил нечто похожее на решение в таком случае. Там сумма dummy по массиву интов превращалась в умножение длины массива на константу, не вижу смысла повторять.
тогда переходим к вопросу: что требуется от type system и ЯП, чтобы ЯП такое решение мог построить самостоятельно?
Здравствуйте, VoidEx, Вы писали:
V>>Ты сделал сразу две ошибки. Даже если брать твой код, то 1.switch — точно такой же поиск. 2. успех каждой отдельно взятой ветки тоже не гарантирован. А если брать полный аналог на С++, т.е. взять точные типы для сравнения, а не базовые, то для успеха каждой ветки потребуется ровно одно сравнение. Т.е. стоимость будет заведомо идентичная, бо во время dynamic_cast сравниваются адреса vtable, а у АлгТД в Хаскеле сравниваются адреса своей внутренней описательной структуры. Всё 1-к-1-му.
VE>Да ну неужели? Не позорься.
Хе. Упоминался как известный факт, что для работы RTTI нужна таблица виртуальных функций. Похоже, ты этого не знал, я прав? И даже не обратил внимание, что по твоей же ссылке во всех примерах вставлена фиктивная виртуальная ф-ия, не участвующая в примере? А в том примере, где нет фиктивной ф-ии, там никакого dynamic_cast нет, это демонстрация в каком случае dynamic_cast заменяется на static_сast, когда наследника приводим к базе.
Внимательнее надо быть.
================
Коллега, вот объясни, с чем же ты спорил тогда, если не знал, как работает dynamic_cast? Чем же ты собирался аргументировать свой протест, что в Хаскеле происходит не то же самое, что в других языках при динамической типизации?
А в курсе как работает динамическое приведение типов в дотнете?
Здравствуйте, vdimas, Вы писали:
FR>>Нет это не динамическая типизация. Это динамическая диспетчеризация максимум.
V>Называй как хочешь, но динамическая типизация в статически-компиллируемом языке — это приведение типов с проверкой, то бишь условное приведение, в отличие от безусловной реинтерпретации памяти. В безусловной реинтерпретации не происходит никаких действий в рантайм, связанных с типизацией. Т.е. никакой динамики.
В общем спасибо за беседу, но дальше продолжать не вижу смысла.
VE>Опачки! Т.е. если в Haskell Either компилировать не в union с разметкой, а в вышеописанный struct, то внезапно динамическая типизация пропадает? Вот тебе раз! VE>Тогда давайте считать, что так и есть!
Легко, ведь действительно никакой динамической типизации делать не надо. Мы можем одновременно пользоваться как L * l, так и R * r; Вот только называть такую структуру Either уже некамильфо. Но копаешь в правильном направлении, еще немного и докопаем.
VE>А то, что на самом деле union, — всего лишь оптимизация.
Вот так с плеча "все лишь"? Ведь это еще изменение семантики, бо одновременно пользоваться l и r можно будет лишь с целью хакнуть типы L и R.
VE>По-моему, твоя теория всё более и более противоречивой становится.
Та хде?
Я пока вижу обратное — ты все больше и больше выясняешь моментов, которыми раньше, скажем так, не интересовался. Очень полезная для тебя дискуссия. Только давай исключим нервозность из этого процесса.
Здравствуйте, VoidEx, Вы писали:
VE>Только sealed мн-во — случай частный. Интересно, сколько раз это придётся повторить.
Наверно, ровно столько раз, сколько будет упомянута набившая оскомину "встроенная безопасность" АлгТД Хаскеля.
Ибо, где такая безопасность нужна — делаем такое же по характеристикам мн-во типов. Тем более, что в рантайме эта особенность ничего не стоит, вернее наоборот — позволяет делать агрессивную оптимизацию.
V>>Называй как хочешь, но динамическая типизация в статически-компиллируемом языке — это приведение типов с проверкой, то бишь условное приведение, в отличие от безусловной реинтерпретации памяти. В безусловной реинтерпретации не происходит никаких действий в рантайм, связанных с типизацией. Т.е. никакой динамики.
FR>В общем спасибо за беседу, но дальше продолжать не вижу смысла. FR>
Жаль, я бы с удовольствием обсудил бы граф обхода RTTI во время dynamic_cast, мне, признаюсь, очень хотелось показать, почему для одноуровневого наследования (скажем, при реализации АлгТД на подтипировании в С++) будет ровно одна проверка на вариант, как в ПМ. Уверен, тебе было бы интересно.
DM> Этот отображение — внутренний вопрос конкретного компилятора или интерпретатора. Разные компиляторы/интепретаторы одного и того же языка могут представлять значения одного и того же типа этого языка в памяти по-разному. Например, тип значение "hello" типа String в языке Руби представлено в памяти по-разному в интепретаторах MRI 1.8, IronRuby и JRuby. Другой пример — значение 42 типа int в языке Окамл представлено в памяти по-разному при компиляции в x86 и x64. Но систему типов это не затрагивает.
если вот это перевести на математику, и отбросить словоблудие, то здесь написано, что:
произвольный код на OCaml(Ruby, C и т.д.) прежде чем быть выполнен — должен все типы конкретизировать способом хранения их в памяти.
или другими словами: всякая функция вида:
int F(int){..}
на самом деле обозначает параметрическую функцию вида:
DM> String в языке Руби представлено в памяти по-разному в интепретаторах MRI 1.8, IronRuby и JRuby
значит можно утверждать, что тип String в Руби на самом деле является параметрическим типом String<TMemoryMapping>, который конкретизируется как:
String<MRI_18.String_Memory_Mapping>, String<IronRuby.String_Memory_Mapping>, String<JRuby.String_Memory_Mapping> в зависимости от интерпретатора
DM> пример — значение 42 типа int в языке Окамл представлено в памяти по-разному при компиляции в x86 и x64
значит тип int в OCaml является параметрическим типом int<TMemoryMapping>, который конкретизируется при компиляции в x86/x64, как: int<x86.int_Memory_Mapping> или int<x64.int_Memory_Mapping>
ps
.net-ный тип Int32 является параметрическим типом Integer<signed, size:4, TBigLittleEndian>, который параметризуется TBigLittleEndian:BigEndian или TBigLittleEndian:LittleEndian в зависимости от процессора.
тип char в c/c++ является параметрическим типом Integer<Signed/Unsigned, size:1/2, BigEndian/LittleEndian> в зависимости от компилятора и его настроек (хотя, конечно, для C/C++ можно найти и экзотические варианты параметризации типа char, например, для экзотических архитектур, где в байте не 8 бит, или где в одном бите хранится 3 значения, а не два)
Здравствуйте, vdimas, Вы писали:
V>Хе. Упоминался как известный факт, что для работы RTTI нужна таблица виртуальных функций. Похоже, ты этого не знал, я прав? И даже не обратил внимание, что по твоей же ссылке во всех примерах вставлена фиктивная виртуальная ф-ия, не участвующая в примере? А в том примере, где нет фиктивной ф-ии, там никакого dynamic_cast нет, это демонстрация в каком случае dynamic_cast заменяется на static_сast, когда наследника приводим к базе.
V>Внимательнее надо быть.
Ты никогда не читаешь то, на что отвечаешь? Я где-то написал, что для RTTI не нужна таблица? Где?
Здравствуйте, vdimas, Вы писали:
VE>>Опачки! Т.е. если в Haskell Either компилировать не в union с разметкой, а в вышеописанный struct, то внезапно динамическая типизация пропадает? Вот тебе раз! VE>>Тогда давайте считать, что так и есть!
V>Легко, ведь действительно никакой динамической типизации делать не надо. Мы можем одновременно пользоваться как L * l, так и R * r; Вот только называть такую структуру Either уже некамильфо. Но копаешь в правильном направлении, еще немного и докопаем.
А мы и так можем. Просто один из них всегда Null, что гарантирует конструктор типа. А изменить мы это сами не можем, данные-то read-only.
V>Та хде? V>Я пока вижу обратное — ты все больше и больше выясняешь моментов, которыми раньше, скажем так, не интересовался. Очень полезная для тебя дискуссия. Только давай исключим нервозность из этого процесса.
Хуже другое, ты для себя так ничего и не выяснил. Вернёмся таки к автоматическому избавлению от АлгТД. Будет контрпример или ты признаёшь, что это может быть произведено автоматически и тогда отличия между теми примерами, что я приводил, нет. Т.е. if-else в Си — тоже динамическая типизация (по твоей терминологии)?
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, VoidEx, Вы писали:
VE>>Только sealed мн-во — случай частный. Интересно, сколько раз это придётся повторить.
V>Наверно, ровно столько раз, сколько будет упомянута набившая оскомину "встроенная безопасность" АлгТД Хаскеля. V>Ибо, где такая безопасность нужна — делаем такое же по характеристикам мн-во типов. Тем более, что в рантайме эта особенность ничего не стоит, вернее наоборот — позволяет делать агрессивную оптимизацию.
А то получается, что разницы между всеми тремя примерами нет, но одно ты зовёшь динамической типизацией, а другое — нет. Самое прекрасное, что там одинаковое количество проверок, но тебя это не смущает.
DM>Итак, попробую строго сформулировать своими словами. Основной пререквизит для понимания материала — теория множеств, буду исходить из того, что понятие множества и основные операции с ними читателю известны.
то, что ты написал можно назвать математической подложкой, но не определениями.
определение — должно включать в себя возможность построения предиката, который принимает произвольную часть мира и возвращает true, если произвольная-часть-мира подходит под определение, и false если не подходит.
утверждение, что цвет есть кортеж(R, G, B) — не является определением цвета, а является лишь способом задать мат. модель для цвета.
утверждение "syntactic framework — это пара множеств (B, R)", также не является определением того, чем является syntactic framework, а является одной из попыток представить syntactic framework в виде теоретико-множественной модели.
и то, и другое не является определениями потому что из этих утверждений нельзя построить предикат произвольная-часть-мира -> bool.
утверждение "цвет есть кортеж (R, G, B)" ничего не говорит о том, является ли модель CMYK цветом или нет.
утверждение "syntactic framework — это пара множеств (B, R)" ничего не говорит о том, является ли тройка множеств(Термы, Комбинаторы-Термов, Предикаты-Описывающие-Исключения) syntactic framework-ом или не является.
Здравствуйте, VoidEx, Вы писали:
V>>Легко, ведь действительно никакой динамической типизации делать не надо. Мы можем одновременно пользоваться как L * l, так и R * r; Вот только называть такую структуру Either уже некамильфо. Но копаешь в правильном направлении, еще немного и докопаем.
VE>А мы и так можем. Просто один из них всегда Null, что гарантирует конструктор типа. А изменить мы это сами не можем, данные-то read-only.
Тогда, увы, рантайм проверку на обязательно null придется делать. В этом случае матч по признаку типа выродится в матч по not null. И тогда распаковка немного усложниться, т.к. распаковка будет заключаться не только в простой реинтерпретации первоначального указателя, как это было бы для случая union, но и потребуется дополнительное смещение адреса, уникальное для каждого хранимого варианта.
VE>Хуже другое, ты для себя так ничего и не выяснил. Вернёмся таки к автоматическому избавлению от АлгТД. Будет контрпример или ты признаёшь, что это может быть произведено автоматически и тогда отличия между теми примерами, что я приводил, нет. Т.е. if-else в Си — тоже динамическая типизация (по твоей терминологии)?
Я полностью пояснил свою позицию в той же ветке. Если ты уже там ответил — дойдут руки, отвечу и я.
Здравствуйте, DarkGray, Вы писали:
DG>то, что ты написал можно назвать математической подложкой, но не определениями.
Дайте своё.
DG>определение — должно включать в себя возможность построения предиката, который принимает произвольную часть мира и возвращает true, если произвольная-часть-мира подходит под определение, и false если не подходит.
Определение — это введение понятия. "Будем называть то-то вот таким словом". Поэтому если ваш syntactic framework не подходит под определение D.Mon. тем хуже для вашего syntactic framework. Его нельзя использовать в рассуждениях или давайте ему определение сами.
Вы попросили дать определение, вам его дали. Ведь вы же это сделали, чтобы выработать общие термины? Аппелировать к тому, что это не определение — убивать дискуссию в зародыше.
DG>и то, и другое не является определениями потому что из этих утверждений нельзя построить предикат произвольная-часть-мира -> bool.
"Произвольная часть мира" — это что такое? Речь вроде о математике.
DG>утверждение "syntactic framework — это пара множеств (B, R)" ничего не говорит о том, является ли тройка множеств(Термы, Комбинаторы-Термов, Предикаты-Описывающие-Исключения) syntactic framework-ом или не является.
Покажите эквивалентность этих определений и будет говорить.
Здравствуйте, vdimas, Вы писали:
V>Тогда, увы, рантайм проверку на обязательно null придется делать. В этом случае матч по признаку типа выродится в матч по not null. И тогда распаковка немного усложниться, т.к. распаковка будет заключаться не только в простой реинтерпретации первоначального указателя, как это было бы для случая union, но и потребуется дополнительное смещение адреса, уникальное для каждого хранимого варианта.
Так я не понял, в том Either — динамическая типизация?
А тогда в примере выше с int x и float y получается тоже?
Здравствуйте, vdimas, Вы писали:
VE>>Хуже другое, ты для себя так ничего и не выяснил. Вернёмся таки к автоматическому избавлению от АлгТД. Будет контрпример или ты признаёшь, что это может быть произведено автоматически и тогда отличия между теми примерами, что я приводил, нет. Т.е. if-else в Си — тоже динамическая типизация (по твоей терминологии)?
V>Я полностью пояснил свою позицию в той же ветке. Если ты уже там ответил — дойдут руки, отвечу и я.
Ты утвеждал, что это оптимизация, и в общем случае не работает. Я уже ответил, что это работает всегда.
Здравствуйте, VoidEx, Вы писали:
V>>Я полностью пояснил свою позицию в той же ветке. Если ты уже там ответил — дойдут руки, отвечу и я.
VE>Ты утвеждал, что это оптимизация, и в общем случае не работает. Я уже ответил, что это работает всегда.
Чтобы ты не стал отвечать в духе "в C# есть sealed", дополню. Вот когда там только sealed и будет, и будет гарантия возможности такой трансформации для общего случая, тогда и обсудим. А пока это сродни оптимизации динамического языка, который иногда может вывести, что функцию зовут только с числами, и потому оптимизирует. Это не статика.
Статика позволяет убрать лишний if гарантированно, а не в частных случаях.
Здравствуйте, DarkGray, Вы писали:
DM>> Этот отображение — внутренний вопрос конкретного компилятора или интерпретатора. Разные компиляторы/интепретаторы одного и того же языка могут представлять значения одного и того же типа этого языка в памяти по-разному. Например, тип значение "hello" типа String в языке Руби представлено в памяти по-разному в интепретаторах MRI 1.8, IronRuby и JRuby. Другой пример — значение 42 типа int в языке Окамл представлено в памяти по-разному при компиляции в x86 и x64. Но систему типов это не затрагивает.
DG>значит можно утверждать, что тип String в Руби на самом деле является параметрическим типом String<TMemoryMapping>, который конкретизируется как: DG>String<MRI_18.String_Memory_Mapping>, String<IronRuby.String_Memory_Mapping>, String<JRuby.String_Memory_Mapping> в зависимости от интерпретатора
Утверждать можно сколько угодно, толку то. Что это изменит и что хорошего даст? Пока что эти утверждения лишь вносят путаницу (разные способы параметризации типов) и противоречат спецификациям языков. Не первый раз ты уже смешиваешь систему типов языка и подробности реализации компиляторов/рантаймов.
Здравствуйте, DarkGray, Вы писали:
DG>то, что ты написал можно назвать математической подложкой, но не определениями.
Еще можно горшком назвать. И единорогом тоже можно.
DG>определение — должно включать в себя возможность построения предиката, который принимает произвольную часть мира и возвращает true, если произвольная-часть-мира подходит под определение, и false если не подходит.
Опять фантазируешь. Дай такое определение предиката и мира для начала тогда. А потом определение завершающейся программы.
Здравствуйте, DarkGray, Вы писали:
DG>и то, и другое не является определениями потому что из этих утверждений нельзя построить предикат произвольная-часть-мира -> bool.
Так и быть, вот тебе такой предикат: если некоторая часть мира подходит под мое определение системы типов (т.е. представляет из себя пару... и далее по тексту), то true, иначе false.
Здравствуйте, VoidEx, Вы писали:
V>>Хе. Упоминался как известный факт, что для работы RTTI нужна таблица виртуальных функций. Похоже, ты этого не знал, я прав? И даже не обратил внимание, что по твоей же ссылке во всех примерах вставлена фиктивная виртуальная ф-ия, не участвующая в примере? А в том примере, где нет фиктивной ф-ии, там никакого dynamic_cast нет, это демонстрация в каком случае dynamic_cast заменяется на static_сast, когда наследника приводим к базе.
V>>Внимательнее надо быть.
VE>Ты никогда не читаешь то, на что отвечаешь? Я где-то написал, что для RTTI не нужна таблица? Где?
Ну так ты возразил насчет моего утверждения, что нужна и что именно её сравнения достаточно. Ты даже употребил усиление "не позорься". А теперь вроде как классически юлишь.
Здравствуйте, VoidEx, Вы писали:
V>>Тогда, увы, рантайм проверку на обязательно null придется делать. В этом случае матч по признаку типа выродится в матч по not null. И тогда распаковка немного усложниться, т.к. распаковка будет заключаться не только в простой реинтерпретации первоначального указателя, как это было бы для случая union, но и потребуется дополнительное смещение адреса, уникальное для каждого хранимого варианта.
VE>Так я не понял, в том Either — динамическая типизация? VE>А тогда в примере выше с int x и float y получается тоже?
VE>В чём разница?
Очевидно в том, что при статической типизации никакие проверки в рантайм делать не надо.
В этих твоих мыслительных экспериментах самое примечательное то, что семантически сохраняя поведение программы, ты реализуешь ее то на встроенных проверках аппарата типизации, предоставляемых языком, то выносишь такие проверки из аппарата типизации в пользовательский код, эмулируя похожую механику типизации вручную.
Я же говорю — в правильном направлении копаешь. Еще немного и поймешь, как все работает. Нет в динамической типизации никакой особой магии. В рантайме — это всегда сравнение значений.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, VoidEx, Вы писали:
V>>>Тогда, увы, рантайм проверку на обязательно null придется делать. В этом случае матч по признаку типа выродится в матч по not null. И тогда распаковка немного усложниться, т.к. распаковка будет заключаться не только в простой реинтерпретации первоначального указателя, как это было бы для случая union, но и потребуется дополнительное смещение адреса, уникальное для каждого хранимого варианта.
VE>>Так я не понял, в том Either — динамическая типизация? VE>>А тогда в примере выше с int x и float y получается тоже?
VE>>В чём разница?
V>Очевидно в том, что при статической типизации никакие проверки в рантайм делать не надо.
Ты не понял. У нас просто код:
if (x.f)
fooInt(x.a);
else
fooFloat(x.b);
Это не проверка типов, просто алгоритм ветвится в зависимости от того, какой выставлен флаг. Почему это не динамическая типизация? Алгоритм работает далее с разными типами данных, делает проверку, чтобы выбрать ветвь.
V>В этих твоих мыслительных экспериментах самое примечательное то, что семантически сохраняя поведение программы, ты реализуешь ее то на встроенных проверках аппарата типизации, предоставляемых языком, то выносишь такие проверки из аппарата типизации в пользовательский код, эмулируя похожую механику типизации вручную.
V>Я же говорю — в правильном направлении копаешь. Еще немного и поймешь, как все работает. Нет в динамической типизации никакой особой магии. В рантайме — это всегда сравнение значений.
Я не копаю, я пытаюсь тебе объяснить, но всё бестолку. Давай от АлгТД не увиливай. Я тебе ещё пример нарисую тогда. Более жизненный, но уверенности, что до тебя дойдёт, уже нет.
-- loadConfig :: IO (Maybe ConnectionData)
cdata <- loadConfig
case cdata of
Just cdata' -> do-- tryReceive :: IO (Either Int SomeData)
sdata <- tryReceive cdata'
case sdata of
Right (SomeData bytes value) -> do
putStrLn $ "received " ++ show bytes ++ " bytes"
process value
Left errorCode -> putStrLn $ "error " ++ show errorCode
Nothing -> putStrLn "not loaded"
Что автоматически трансформируемо в:
-- loadConfig :: (ConnectionData -> IO r) -> IO r -> IO r
loadConfig onLoad onFail where
onLoad cdata = tryReceive onData onError where-- tryReceive :: (SomeData -> IO r) -> (Int -> IO r) -> IO r
onData (SomeData bytes value) -> do
putStrLn $ "received " ++ show bytes ++ " bytes"
process value
onError errorCode = putStrLn $ "error " ++ show errorCode
onFail = putStrLn "not loaded"
Здравствуйте, VoidEx, Вы писали:
V>>Дело в том, что в момент такой транфсформации работает техника суперкомпиляции, но на этом этапе никаких типов уже нет, потому что это уже инлайнинг, распространение констант и т.д., а не тайплевел-вычисление, т.е. это обычные спекулятивные вычисления, то бишь эмуляция механики, происходящей в райнтайм. А метки типов — это уже такие же константы на этом этапе. И да, для дотнета эта техника так же может работать, т.к. там тоже есть инлайнинг.
VE>Это не суперкомпиляция, это тривиальное преобразование. Причём его можно производить в обе стороны без смены семантики И поведения. VE>Если это не так, прошу предоставить контр-пример.
Хорошо. Пример очень простой. Берем некое AST, которое сделали на АлгТД. Берем твои рассуждения: компилятор "видит" как это дерево строится, затем видит, как вызывается некая ф-ия обхода дерева, где мы ветвимся на технике ПМ. Компилятор может провести такое преобразование, где вместо нетипизированного "мешка" под названием АлгТД будет во время построения дерева сохранять в узлах замыкания, которые будут захватывать типизированные значения и содержать в себе необходимые процедуры обработки, перенесенные из веток ПМ исходного кода, т.е. работающего уже с уточненными типами узлов.
Я до сюда правильно излагаю?
Итого, одно дерево, одна операция, преобразование туда-обратно идентичное.
Теперь представим, что у нас по этому дереву будет сотня независимых операций обработки, но выбор лишь из одной и это выбор зависит от данных в рантайм. Причем, выбор этот пусть по неким причинам происходит уже ПОСЛЕ построения дерева.
Теперь компилятор должен будет сохранять в узлах дерева не одно замыкание, а сотню. Дадим этому варианту реализации номер №1. По мне — это очень плохой вариант, т.к. слишком много памяти нужно под сотни замыканий, хотя вызвано будет всегда лишь одно из них. Т.е. мало того что под хранения сотен адресов ф-ий в каждом узле нужна будет лишняя память, дык еще в каждом из замыканий будет продублирован захваченный контекст (данные). В итоге, получим кривую пародию на ООП. Кривую, потому что в ООП независимо от кол-ва ф-ий в vtable нам нужен всего один указатель на нее, и сам объект "замыкает" для ф-ий из vtable данные лишь в одном экземпляре. Т.е. сэкономив на проверках, мы значительно потеряли в памяти, т.е. оптимизации как таковой нет.
Давай попробуем вариант №2. Пусть это будет по-прежнему одно замыкание и одна копия данных узла, но мы внутри должны будем протянуть метку текущей операции (пусть будет некий индекс), затем в каждом узле дополнительно разветвиться по этой метке, выбирая алгоритм согласно некоторого индекса. Если компилятор достаточно умный, то это обойдется нам в +2 лишних уровня косвенности и двойную диспетчеризацию (ООП-визитор). Если недостаточно умный, то будет аналог switch на входе каждой процедуры. Но самое примечательное, что убежав от рантайм проверок в одном месте, мы их приобрели в другом, это проверка ТИПА операции, либо дополнительная двойная диспетчерезация на каждом шаге (обе техники взаимозаменяемы, кстате). Поэтому в вариант №2, сэкономив память, мы опять не сэкономили на рантайм-проверках. Опять нет оптимизации.
ЧТД.
Заметь, мы всё еще рассматриваем ситуацию, когда компилятор видит программу целиком, т.е. когда он видит все входы и выходы. Этот сценарий я и назвал случаем "наивной оптимизации". Дело в том, что для такой программы любой вменяемый оптимизирующий компилятор способен нивелировать большую часть излишних динамических проверок. По крайней мере для С++ в таких "замкнутых" программах я наблюдал выкидывание компилятором до половины кода из рантайм. Для каких-нить экспериментов приходилось вносить побочные эффекты, чтобы компилятор не особо расходился в своем порыве.
А теперь берем ситуацию, когда у нас происходит динамическая загрузка некоего плагина. Т.е. при компиляции этого плагина мы не видели ни как было построено дерево, ни какие еще ф-ии требуют его обхода. Что должен делать компилятор? Вот тут твоя техника НЕ работает.
==============
А вообще, утверждение у тебя абсолютно правильно. Забудь про подробность преобразований конкретно АлгТД. Твоё утверждение сводится к тому, что любую программу МОЖНО переписать так, чтобы избежать динамической типизации, предоставляемой средой/языком.
Конечно можно! Тут два основных способа: это LSP, т.е. вариант №1 в начале поста, т.е. гомоморфная иерархия объектов с фиксированным набором операций, либо же ЭМУЛЯЦИЯ динамической типизации БЕЗ таковой. Как примеры: вариант №2 если делать его вручную, реализация COM или тот твой пример, где ты кодируешь информацию о текущем типе Either путем инициализации null у второго элемента пары. Т.е. это прямая эмуляция другой системы типов и динамических преобразований в ней через инструментарий имеющейся. Помнишь, я еще несколько постов назад пытался предостеречь тебя от рассуждений в эту область, т.к. она не доказывает ничего ни для одной из сторон, коль рассматривалась конкретная имеющаяся механика? Не доказывает, потому что одна и та же семантика может быть реализована кучей способов, как с привлечением неких св-в аппарата типов, так и без, через её эмуляцию/дублирование. Но тебя туда тянет по-прежнему.
В общем, если по мне, то обсуждать давно нечего. Не думал, что таких подробный намеков, как я уже делал неоднократно, недостаточно. Пришлось объяснять известные вещи заново, а именно: LSP хорош только там, где кол-во операций заведомо известно, а все зависимости однонаправленные. Поэтому рамки применения LSP не так чтоы очень широки — в плагинной архитектуре и в прочих компонентных моделях LSP не работает, в таких архитектурах требуется именно что динамическая типизация, предоставляемая средой/языком, либо непосредственная эмуляция этой типизации, примерно как в COM или твоем самописном Either.
V>>Потому что если оптимизация не состоялась по каким-либо причинам (нетривиальная зависимость от данных, например), то в одном случае приходится делать это в рантайм, а в другом — не приходится. VE>Если оптимизация состоится всегда, то это не оптимизация.
Да не всегда. Это заблуждение, т.к. ты пока рассматривал замкнутую систему типов. В замкнутой системе даже над дотнетным object можно было производить такие же преобразования, без всяких твоих "частных случаев" навроде специального sealed, т.к. эти sealed как раз нужны для незамкнутой системы. В замкнутой системе, при обеспечении ссылочной прозрачности, даже аннотации типов теряют всякую актуальность, т.к. всё можно вывести из контекста. Но это в каком-то идеальном мире, а не в реальном.
VE>Правда для общего случая. Разница в том, что общий случай с АлгТД имеет фиксированное число вариантов, в случае обычного ООП общий случай шире.
Нет, он непросто шире, он ОТКРЫТ, этот общий случай. Просто был бы шире — это фигня и сводится к варианту №1 или №2 из показанного в начале поста.
VE>Ты мне можешь так же доказывать, что построение таблицы на switch — это оптимизация, потому что в общем случае enum — это число, а какое число — мы не знаем. Но у нас не "любое" число, а enum, и мы знаем при компиляции все возможные принимаемые значения. Т.е. эта т.н. "суперкомпиляция" работает всегда. И потому это не оптимизация.
Не, ты всерьез считаешь, что если в switch из десятка вариантов добавить ветку default, то что-то принципиально изменится? Я уже второго человека спрашиваю — тишина.
VE>>>А если v is MyClass? Где передача MyClass onMyClass? HisClass onHisClass? И так для _всех_ типов. VE>>>Именно, что это не аналоги. Как говорится, прочувствуй разницу. VE>>>Теперь понял?
V>>Нет, можно сформулировать почетче?
VE>Количество наследников открыто. Если сделать закрытую иерархию, то можно будет сделать такое преобразование. Но для ООП — это частный случай, и потому оптимизация (чем ты и пытаешься аргументировать). Для ADT же это общий случай, работающий всегда.
Для АлгТД этот случай работает, только если компилятор видит все операции, вызываемые над этим АлгТД.
Т.е. от смены координат ничего ведь не изменяется и не должно. Просто для ООП мы рассуждаем об открытости иерархии, а для ФП — об открытости мн-ва доступных ф-ий.
И да, как раз компилятор С++ показывает, что если программа замкнута, то ему наличие "возможных" наследников до фени. Если их гарантировано не будет, то многое выкидывается нафик.
VE>Как с "любым числом" и enum'ом. Можно, конечно, говорить, что компилятор, который учёл, что у нас всего 3 значения enum'а (red green и blue), провёл суперкомпиляцию, но это не так. Вот если потенциально значение enum'ов бесконечно, но компилятор как-то догадался, что в данном случае их три — то суперкомпиляция. Здесь же ситуация другая.
Здесь ты уже потерял нить обсуждения, т.к. в предложенном тобой преобразовании мы парную упаковку/распаковку АлгТД заменяли на непосредственную обработку данных. А для этого надо заведомо "видеть" все случаи, где этот АлгТД обрабатывается. Я же уже одергивал тебя насчет своеобразной системы модулей Хаскеля, которые на самом деле не модули нихрена, а некий аналог #include из С/С++, с той разницей, что хранится уже распаршенное представление кода. Но этот код не черный ящик ни разу. Интеллектуальную собственность в него не спрячешь, поэтому коммерческих библиотек на Хаскеле нет и не будет. Ну разве что в виде распространяемых в исходниках на честном слове.
VE>Вот в dotnet — это частный случай. А в Haskell — нет.
Т.е. на Хаскель нельзя написать интероперабельную с основной программой на Хаскеле DLL?
VE>Аргументы будут?
Что, опять?
VE>АлгДТ можно преобразовать в алгебру, которая есть кортеж функций.
Угу, в кривое ООП. Америку открыл... Преобразовать-то можно, но ровно с теми же ограничениями, которые идут к LSP.
VE>В общем, без какого-либо осмысленного контраргумента дальше обсуждать тему бессымсленно.
В общем, пора бы взглянуть на реально происходящее и на свои собственные предложения со стороны. Твоя настойчивость насчет отказаться от св-в системы типов и эмулировать другую на основе имеющейся попахивает общим непониманием происходящего, сорри. Ты НЕ понимаешь, что система типов — это уже готовый инструмент, который тебе дан, чтобы ты НЕ писал свой, точно такой же. Пользуйся этим инструментом. Все что я писал здесь — это не критика инструмента, это раскрытие механики его работы и сравнение с механикой динамической типизации в других языках. Я показал, что эта механика абсолютно идентична, как бы тебе не больно было слышать это про "статически типизированный Хаскель". И ты это так и НЕ опровергнул, хотя, повторюсь, придумал несколько способов как избавиться от встроенной динамической типизации, но не так и НЕ понял, что избавляешься каждый раз через LSP, либо через эмуляцию динамической типизации...
Здравствуйте, VoidEx, Вы писали:
V>>Очевидно в том, что при статической типизации никакие проверки в рантайм делать не надо.
VE>Ты не понял. У нас просто код:
VE>Это не проверка типов, просто алгоритм ветвится в зависимости от того, какой выставлен флаг. Почему это не динамическая типизация? Алгоритм работает далее с разными типами данных, делает проверку, чтобы выбрать ветвь.
Ага, а чем отличается код первокурсника от кода опытного программиста? ИМХО, помимо прочего еще тем, что первокурсник всё пишет ручками, а опытный программист умеет заставить работать вместо себя систему типов. Типы-то там нужны для того, чтобы переносить на них часть прикладной логики, правильно? А ты в этом примере решил побыть тем самым первокурсником, не сумевшим расписать иерархию в ООП или АлгТД в ФП? И написал ручками свой if?
V>>В этих твоих мыслительных экспериментах самое примечательное то, что семантически сохраняя поведение программы, ты реализуешь ее то на встроенных проверках аппарата типизации, предоставляемых языком, то выносишь такие проверки из аппарата типизации в пользовательский код, эмулируя похожую механику типизации вручную.
V>>Я же говорю — в правильном направлении копаешь. Еще немного и поймешь, как все работает. Нет в динамической типизации никакой особой магии. В рантайме — это всегда сравнение значений.
VE>Я не копаю, я пытаюсь тебе объяснить, но всё бестолку.
Для начала, сформулируй, что имено ты пытаешься обяснить. Пока что ты ничего не пытаешься мне объяснить, а просто ставишь мысленные эксперименты и ждешь моего одобрения. Ну... одобряю. Если же ты хочешь мне что-то объяснить, плиз, сформулируй, что именно. Т.е. доказательством чего именно должен служить приведенный пример? Мы к нему вернемся, не переживай, давай только определимся, о чем он.
VE>Какие ваши аргументы?
Здравствуйте, D. Mon, Вы писали:
DG>>значит можно утверждать, что тип String в Руби на самом деле является параметрическим типом String<TMemoryMapping>, который конкретизируется как: DG>>String<MRI_18.String_Memory_Mapping>, String<IronRuby.String_Memory_Mapping>, String<JRuby.String_Memory_Mapping> в зависимости от интерпретатора
DM>Утверждать можно сколько угодно, толку то. Что это изменит и что хорошего даст? Пока что эти утверждения лишь вносят путаницу (разные способы параметризации типов) и противоречат спецификациям языков. Не первый раз ты уже смешиваешь систему типов языка и подробности реализации компиляторов/рантаймов.
Почему попытку разобраться в реализации всё время пытаются объявить "смешиванием"?
Это несерьезно и малость непрофессионально, ИМХО. Система типов — это не просто абстракция, это инструмент, предназначенный исключительно для реализации прикладных вещей. Т.е. чем больше функциональности мы перекладываем на систему типов, тем достовернее необходимо знать, во что это нам обходится. Иначе нет никакого смысла смысла использовать такую систему типов для реальных задач, коль ее характеристики неизвестны.
Здравствуйте, VoidEx, Вы писали:
VE>Сознание лишь информируется о решении задачи (и тогда мы восклицаем "Эврика!", но как именно получено решение — не понимаем) интеллектом.
Как, как... сопоставлением с образцом...
Образное мышление работает, а это штука слишком большой параллельности/размерности, чтобы объяснить последовательностью слов.
ИМХО, степень сознания человека на самом деле довольно низкая. Ниже, чем хотелось бы или чем многие пытаются приписать человеку. Человек еще слишком слабо себя познал, поэтому "сознание" в человеческом понимании — это банальная совокупность интеллекта + ощущений. Причем, и первое и второе не так чтобы очень сильны, скорее, наоборот. Просто человек слишком много о себе думает и вообще склонен к эзотерике. К 2050-му машины будут гарантированно умнее человека и смогут лучше "понимать" как происходит мыслительный процесс в человеке, чем сам человек. Останется добавить машинам ощущения для формулирования стимулов, как прямых так и производных сколь угодно больших уровней производности (типа как сегодня у человека "долг", "совесть" и прочих несложных стимулов низких уровней производных от стимула "собственная значимость") и идти на свалку истории.
Здравствуйте, vdimas, Вы писали:
V>Теперь представим, что у нас по этому дереву будет сотня независимых операций обработки, но выбор лишь из одной и это выбор зависит от данных в рантайм. Причем, выбор этот пусть по неким причинам происходит уже ПОСЛЕ построения дерева.
Ну, давай. Теперь реализуй это на Си++ и расскажи, куда там денется динамическая типизация.
Или никуда не денется?
VE>>Правда для общего случая. Разница в том, что общий случай с АлгТД имеет фиксированное число вариантов, в случае обычного ООП общий случай шире.
V>Нет, он непросто шире, он ОТКРЫТ, этот общий случай. Просто был бы шире — это фигня и сводится к варианту №1 или №2 из показанного в начале поста.
Мы как глухой со слепым разговариваем. Шире и означает, что он шире качественно, а не количеством вариантов.
V>Не, ты всерьез считаешь, что если в switch из десятка вариантов добавить ветку default, то что-то принципиально изменится? Я уже второго человека спрашиваю — тишина.
Это ты так считаешь. Одни if-else у тебя динамическая типизация, а другие — нет. Почему — чёрт его разберёт. Какая-то "реинтерпретация памяти" приплетается.
V>Т.е. от смены координат ничего ведь не изменяется и не должно. Просто для ООП мы рассуждаем об открытости иерархии, а для ФП — об открытости мн-ва доступных ф-ий.
Что мешает определить новую функцию в ООП?
V>Здесь ты уже потерял нить обсуждения, т.к. в предложенном тобой преобразовании мы парную упаковку/распаковку АлгТД заменяли на непосредственную обработку данных. А для этого надо заведомо "видеть" все случаи, где этот АлгТД обрабатывается. Я же уже одергивал тебя насчет своеобразной системы модулей Хаскеля, которые на самом деле не модули нихрена, а некий аналог #include из С/С++, с той разницей, что хранится уже распаршенное представление кода. Но этот код не черный ящик ни разу. Интеллектуальную собственность в него не спрячешь, поэтому коммерческих библиотек на Хаскеле нет и не будет. Ну разве что в виде распространяемых в исходниках на честном слове.
Зачем их видеть? Получать указатели на продолжения.
VE>>Вот в dotnet — это частный случай. А в Haskell — нет.
V>Т.е. на Хаскель нельзя написать интероперабельную с основной программой на Хаскеле DLL?
Что?
VE>>Аргументы будут?
VE>>В общем, без какого-либо осмысленного контраргумента дальше обсуждать тему бессымсленно.
V>В общем, пора бы взглянуть на реально происходящее и на свои собственные предложения со стороны. Твоя настойчивость насчет отказаться от св-в системы типов и эмулировать другую на основе имеющейся попахивает общим непониманием происходящего, сорри. Ты НЕ понимаешь, что система типов — это уже готовый инструмент, который тебе дан, чтобы ты НЕ писал свой, точно такой же. Пользуйся этим инструментом. Все что я писал здесь — это не критика инструмента, это раскрытие механики его работы и сравнение с механикой динамической типизации в других языках. Я показал, что эта механика абсолютно идентична, как бы тебе не больно было слышать это про "статически типизированный Хаскель".
Нет, это ты не понимаешь, что в случае, когда на Haskell есть т.н. тобой динамическая типизация, она есть и в Си++. При этом в Си++ это может выглядеть как банальный if-else. Даже если там сравнение x с нулем.
V>И ты это так и НЕ опровергнул, хотя, повторюсь, придумал несколько способов как избавиться от встроенной динамической типизации, но не так и НЕ понял, что избавляешься каждый раз через LSP, либо через эмуляцию динамической типизации...
Я утверждаю ровно одно: если от ветвления избавиться нельзя, до либо это динамическая типизация во всех таких случаях (и пофиг, сравнивается там x с нулем или проверяется метка типа), либо нет, и тоже во всех.
Именно поэтому я привожу код в сравнении с Си++ (в котором, на твой взгляд, в тех примерах, что я привожу, динамической типизации почему-то нет, хотя кол-во проверок то же), ты же упорно съезжаешь на обсуждение только хаскельного кода, пытаясь объяснить мне очевидное, даже признавая, что преобразования идентичны. Так я именно на эту идентичность и упираю.
Давай с чистого листа, а то надоело уже.
Дай формальное определение динамической (статической) типизации.
Статичность типизации — возможность гарантированно убрать ветвление.
Т.е. если в обоих языках по 2 ветвления при одинаковом поведении, то их статичность равномощна.
При моём определении две программы ниже статически типизированы в одинаковой мере. Однако третья — более статически типизирована:
И наплевать, что в Си++ просто ветвление, а в Haskell — ADT.
Предложи своё определение.
// int * foo();int * x = foo(10);
if (x)
std::cout << *x << std::endl;
else
std::cout << "null" << std::endl;
-- foo :: IO (Maybe Int)
x <- foo 10;
case x of
Just v -> print v
Nothing -> putStrLn "null"
-- возвращает пруф, что если n - чётно, то результат just
-- foo : (`n : int) -> io (`r : maybe int) {even `n -> is-just `r = true}
x <- foo 10
case x of
just v -> print v
-- nothing -> never happens
Здравствуйте, vdimas, Вы писали:
V>Почему попытку разобраться в реализации всё время пытаются объявить "смешиванием"?
Я согласен, что представлять, во что превращаются данные в памяти, нередко полезно и иногда даже необходимо. Но это просто знание подробностей компилятора. Хорошее, полезное знание. Просто не надо эту информацию толкать обратно в язык и говорить, что "на самом деле" там типы такие и такие. Нет, "на самом деле" компиляторов может быть много, и определение языка от них не должно зависеть. Если вбить себе в голову, что такой-то тип представлен именно так, а не иначе, это может привести к проблемам, когда оптимизатор вдруг возьмет и сделает по-другому. Это даже в Си ежедневно случается, что уж говорить о более сложных языках.
Здравствуйте, DarkGray, Вы писали:
DG>или другими словами: DG>int<TMemoryMapping>
Проблема в том, что этот TMemoryMapping настолько изменчив, что практически не дает никакой полезной информации. Даже в рамках одного компилятора один и тот же тип int может быть отображен на железо по-разному. Простой вопрос: как представлена переменная i в следующем коде?
int a[4];
for(int i=0; i<4; i++)
a[i] = 111;
Она может оказаться на стеке, в регистре, или вообще ее может не быть после инлайнинга. Будешь включать этот нюанс в определение типа int?
Здравствуйте, vdimas, Вы писали:
V>Ага, а чем отличается код первокурсника от кода опытного программиста? ИМХО, помимо прочего еще тем, что первокурсник всё пишет ручками, а опытный программист умеет заставить работать вместо себя систему типов. Типы-то там нужны для того, чтобы переносить на них часть прикладной логики, правильно? А ты в этом примере решил побыть тем самым первокурсником, не сумевшим расписать иерархию в ООП или АлгТД в ФП? И написал ручками свой if?
DG>>или другими словами: DG>>int<TMemoryMapping>
DM>Проблема в том, что этот TMemoryMapping настолько изменчив, что практически не дает никакой полезной информации.
только надо быть честным и формулировать правильно: TMemoryMapping настолько изменчив, там настолько много информации, что я, D.Mon не знаю, как с этим можно работать.
фактически, когда ты говоришь что здесь нет полезной информации, ты на самом деле говоришь, что здесь нет неизменной полезной информации, а как сделать большой объем изменяющейся информации — полезным для себя, ты не знаешь.
вообще, когда говорится о полезности, всегда необходимо говорить о пользе для чего? идет речь.
разметка типов способом их хранения в памяти приносит пользу при построении эффективного кода.
соответственно, если тебе не нужен эффективный код, то тебе не нужна и разметка переменных способом хранения данных в памяти
DM> Даже в рамках одного компилятора один и тот же тип int может быть отображен на железо по-разному. Простой вопрос: как представлена переменная i в следующем коде? DM>
DM>Она может оказаться на стеке, в регистре, или вообще ее может не быть после инлайнинга. Будешь включать этот нюанс в определение типа int?
если необходимо управлять эффективностью этого кода, то да — это необходимо добавить.
и оптимизируеший компилятор когда генерить код, именно этим и занимается, он каждую переменную конкретизирует местом ее хранения (heap, регистр, stack, и т.д.), при условии, конечно, что переменная не была элиминирована в результате оптимизации
соответственно задача звучит так:
хотим ли мы чтобы такие нюансы описывались всегда неявно автоматически компилятором и при этом получался не очень эффективный код,
или хотим чтобы можно было описывать такие нюансы, как явно руками в критических точках, так и неявно автоматически компилятором в остальном коде и получать при этом максимально эффективный код
?
если этот массив часто используется в критичном с точки зрения производительности кода, то данный код может быть вообще размечен как:
array<int<structured>, size:4, dll-data> a = compile-run([1..4].Select(_=>111).ToArray());
выполнить генерацию последовательности 111, 111, 111, 111 на этапе компиляции и поместить в сегмент данных скомпилированного модуля
если размечать данных код без особых оптимизаций под простой набор команд процессора, то будет:
*structured — часть некой большей структуры. Вопросы выделения памяти, освобождения памяти и т.д. решает большая структура.
*code — число хранится напрямую в коде, как часть ассемблерной команды load x
этот же код может быть размечен так (при развертывании for-а на этапе компиляции):
Здравствуйте, DarkGray, Вы писали:
DG>>>int<TMemoryMapping>
DM>>Проблема в том, что этот TMemoryMapping настолько изменчив, что практически не дает никакой полезной информации.
DG>только надо быть честным и формулировать правильно: TMemoryMapping настолько изменчив, там настолько много информации, что я, D.Mon не знаю, как с этим можно работать.
Нет, я о другом. Если включать TMemoryMapping в систему типов языка, то никакой вообще информации там не будет, т.к. значения его не будут известны. Ты не можешь заранее знать и включить в систему типов все подробности всех будущих реализаций компиляторов и других способов исполнения. Программу может вообще не компьютер исполнять, а группа бразильских студенток. Как у них в головах представлен int? Внесешь это в систему типов?
Когда у тебя есть конкретный компилятор, ты действительно можешь сказать, что он в таких-то ситуациях представляет этот тип таким-то образом. Но это информация об этом конкретном компиляторе, а не об исходном языке. Поэтому вносить TMemoryMapping в систему типов нет смысла. В код конкретного компилятора — да, смысл есть и вполне понятный.
DM>Нет, я о другом. Если включать TMemoryMapping в систему типов языка, то никакой вообще информации там не будет, т.к. значения его не будут известны. Ты не можешь заранее знать и включить в систему типов все подробности всех будущих реализаций компиляторов и других способов исполнения. Программу может вообще не компьютер исполнять, а группа бразильских студенток. Как у них в головах представлен int? Внесешь это в систему типов?
в этом тексте смешивается сразу несколько тем:
уровни абстрагирования кода,
должен ли язык быть один для разных уровней абстрагирования или это должны быть разные языки?
должна ли система типов быть одной, разной или единой?
При программировании может делаться много разных уровней абстрагирования разными способами, сейчас остановимся на трех:
1. пока мы описываем математические вычисления как абстрактный алгоритм — нам достаточно типа Number (и на этом уровне не конкретизуется, целое ли это число, комплексное или еще какое-то, как оно хранятся в памяти, каким образом выглядят ошибки вычисления и т.д.)
2. дальше этот алгоритм применяется в какой-то конкретной задаче: здесь мы уже скорее всего знаем, каким число будет: целым, дробным, какие ошибки при вычислении могут быть и как мы их хотим показывать пользователю
3. еще дальше мы эту конкретную задачу хотим выполнить на конкретной платформе: здесь нас начинает интересовать размерность чисел, способ хранения в памяти, какие типы и операции процессор подерживает нативно, скорость выполнения каждой операции и т.д.
Нельзя считать, что задача программиста заканчивается на 2 уровне, или тем более только на первом.
В задачу программиста входит правильно сформировать код на всех трех уровнях. Да, эта задача с одной стороны громоздкая, а с другой стороны — повторяющаяся, и поэтому в ее решении может помочь "машина", но именно помочь, а не заменить программиста полностью. При этом роли распределяются следующим образом: программист принимает ключевые решения, а "машина" на основе этой ключевой информации выстраивает промежуточные связки.
Язык должен быть один для работы со всеми уровнями. 2 + 2 должно записываться одинаковым образом: и при записи общей математической формулы, и при работе на уровне ассемблера. Тоже самое касается логических операций, опрераций с множествами, последовательностями и т.д. Это необходимо чтобы легко было перейти от уровня, а так же смешать в одной программе несколько уровней.
Система типов должна быть разнообразной, но при этом единой. С одной стороны — на каждом уровне свои типы: на первом — number, sequence, collection, set и т.д., на втором — float, integer, array, dictionary и т.д., на третьем — native-int, byte, float32 и т.д., с другой стороны — и программист, и "машина" должны понимать как number переходит integer, а затем в native-int32, и что number=integer<TMemoryMapping, TRange> | float<TMemoryMapping, TRange, TAccuracy> | complex<..>, а x86-native-int32 = integer<signed, size:4, little-endian>
DM>Когда у тебя есть конкретный компилятор, ты действительно можешь сказать, что он в таких-то ситуациях представляет этот тип таким-то образом. Но это информация об этом конкретном компиляторе, а не об исходном языке. Поэтому вносить TMemoryMapping в систему типов нет смысла. В код конкретного компилятора — да, смысл есть и вполне понятный.
Если TMemoryMapping отдается полностью на откуп компилятору, то у программиста пропадает возможность управлять эффективностью выполнения своего кода, не смотря на то, что именно программист намного лучше компилятора знает в каких точках программы ему нужна максимальная производительность, и как этого можно добиться.
Хуже всего, что присутствует самообман. Одной рукой программист для одних данных задает низкоуровневое управление памятью, а другой рукой закрывает себе глаза и говорит, что отказывается смотреть как другие данные будут себя вести на нижнем уровне.
Например, конструкция:
data Tree a = Leaf | Node a (Tree a) (Tree a)
явно задает, что дерево хранится, как ссылочное бинарное дерево.
и т.к. в коде уже всё на это завязано, то, в целом, уже невозможно это дерево хранить в виде упорядоченного массива, которое тоже является бинарным деревом.
на первом уровне абстракции достаточно сказать, что в данном месте мы работает с последовательностью, коллекцией, множеством и т.д..
на втором уровне абстракции уже хочется поговорить о том, какие операции над коллекцией(последовательностью и т.д.) должны делаться быстрее всего,
и только на самом последнем уровне — может идти речь о том, что это будет: ссылочное бинарное дерево, двунаправленный список, отсортированный массив или hashset.
Здравствуйте, VoidEx, Вы писали:
V>>Теперь представим, что у нас по этому дереву будет сотня независимых операций обработки, но выбор лишь из одной и это выбор зависит от данных в рантайм. Причем, выбор этот пусть по неким причинам происходит уже ПОСЛЕ построения дерева.
VE>Ну, давай. Теперь реализуй это на Си++ и расскажи, куда там денется динамическая типизация. VE>Или никуда не денется?
По крайней мере итерация по типам узлов из стоимости O(n) на ПМ превращается в O(1) на двойной диспетчеризации (визитор/мультиметоды), в случае итерации по описанному дереву ПРОИЗВОЛЬНОГО кол-ва алгоритмов.
V>>Не, ты всерьез считаешь, что если в switch из десятка вариантов добавить ветку default, то что-то принципиально изменится? Я уже второго человека спрашиваю — тишина. VE>Это ты так считаешь. Одни if-else у тебя динамическая типизация, а другие — нет.
Дык, если этот if-else идет по результату предиката проверки типа, то динамическая ес-но. Не люблю повторяться, но напомню, что речь о двухтактной схеме: проверка -> реинтерпретация памяти.
VE>Почему — чёрт его разберёт. Какая-то "реинтерпретация памяти" приплетается.
Неужели "туман" еще не рассеялся?
V>>Т.е. от смены координат ничего ведь не изменяется и не должно. Просто для ООП мы рассуждаем об открытости иерархии, а для ФП — об открытости мн-ва доступных ф-ий. VE>Что мешает определить новую функцию в ООП?
Да ничего. Но это не повлияет на твою технику подстановки кода в АлгТД, т.к. объект — это не АлгТД.
V>>Здесь ты уже потерял нить обсуждения, т.к. в предложенном тобой преобразовании мы парную упаковку/распаковку АлгТД заменяли на непосредственную обработку данных. А для этого надо заведомо "видеть" все случаи, где этот АлгТД обрабатывается. Я же уже одергивал тебя насчет своеобразной системы модулей Хаскеля, которые на самом деле не модули нихрена, а некий аналог #include из С/С++, с той разницей, что хранится уже распаршенное представление кода. Но этот код не черный ящик ни разу. Интеллектуальную собственность в него не спрячешь, поэтому коммерческих библиотек на Хаскеле нет и не будет. Ну разве что в виде распространяемых в исходниках на честном слове.
VE>Зачем их видеть? Получать указатели на продолжения.
Не получится. Согласно приведенной тобой технике, тебе надо уметь отследить жизненный путь любого ЭКЗЕМЛЯРА такого АлгТД, создание которого заменяется сразу на обрабатывающий его код. Ты просто порисуй граф преобразований при вложенности более 2-х, увидишь, насколько все интересно... Особенно интересно будет, когда в программе будет несколько независимых АлгТД, но которые "унутре" состоят из пересекающегося мн-ва других типов. После такого "раскрытия скобок" у нас группировка по АлгТД пропадет, но появится новая, по этим типам, входящим в первоначальные АлгТД. И еще могут быть заметны устойчивые повторяющиеся группы ф-ий даже для разных типов. Т.е. произойдет своебразная перегруппировка: первоначальные группы (АлгТД) распадутся, и станут заметны новые группы, схожие не по структуре, а по наборам операций. Кароч, это будет преобразование от декомпозии на АлгТД к классической ООП-декомпозиции.
Да, динамическая типизация по первоначальным типам изчезает, ты абсолютно правильно заметил. Но она исчезает потому, что исчезают исходные типы — в это вся суть фокуса.
V>>Т.е. на Хаскель нельзя написать интероперабельную с основной программой на Хаскеле DLL? VE>Что?
Можно ли на Хаскель написать DLL, которая содержит пересекающееся мн-во типов с основной програмой, тоже написанной на Хаскеле? И при этом взаимодействует с участием типов из этой области пересечения?
VE>Нет, это ты не понимаешь, что в случае, когда на Haskell есть т.н. тобой динамическая типизация, она есть и в Си++.
Не факт. Она будет только если мы на С++ выберем такую же декомпозицию по типам и ролям этих типов. Просто в ООП скорее всего будет чуть другая декомпозия, и многие вещи будут решены через ООП-полиморфизм, что есть O(1) затрат для преобразования this к произвольному наследнику при вызове ф-ий.
VE>При этом в Си++ это может выглядеть как банальный if-else. Даже если там сравнение x с нулем.
Или как вызов виртуальной ф-ии. А в особо клинических случаях — двойная или тройная диспетчеризация.
VE>Я утверждаю ровно одно: если от ветвления избавиться нельзя, до либо это динамическая типизация во всех таких случаях (и пофиг, сравнивается там x с нулем или проверяется метка типа), либо нет, и тоже во всех.
Таки курить LSP до полного просветления. Как раз способ избавиться от цепочки if-else, проверяющей метки типов. Кстати, в Хаскеле тоже доступно, только слабо используется...
Здравствуйте, mima, Вы писали:
V>>Ага, а чем отличается код первокурсника от кода опытного программиста? ИМХО, помимо прочего еще тем, что первокурсник всё пишет ручками, а опытный программист умеет заставить работать вместо себя систему типов. Типы-то там нужны для того, чтобы переносить на них часть прикладной логики, правильно? А ты в этом примере решил побыть тем самым первокурсником, не сумевшим расписать иерархию в ООП или АлгТД в ФП? И написал ручками свой if?
M>Точно-точно! http://www.willamette.edu/~fruehr/haskell/evolution.html
Ооо, этот баян растет вширь.
M>В ваших программах есть if?
Судя по виденному мною Хаскель-коду — на порядок меньше... Стараюсь обходится полиморфизмом или простой +1 косвенностью вместо опроса признаков. if — зло для современных конвейеров процессоров. Циклы/рекурсии на каждый чих — тоже.
M>И ещё, вы часто пишите об "уточнённых типах". Под ними вы подразумеваете конструкторы данных АлгТД? Или типы полей этих конструкторов?
Согласно определению, это "другой тип внутри АлгТД"
M>Вот тут, например, что является уточнёнными типами? M>[haskell] M>data T = A A1 A2 ... M> | B B1 B2 ...
Здравствуйте, VoidEx, Вы писали:
VE>Давай с чистого листа, а то надоело уже. VE>Дай формальное определение динамической (статической) типизации.
Есть еще строгая/нестрогая типизация.
Если своими словами, то так: статическая строгая типизация — это достоверная интерпретация памяти, занимаемой значениями типов, известная в compile-time. Динамическая строгая типизация — это типизация, которая требует телодвижений в рантайм, т.е. когда невозможно достоверно интерпретировать память, занимаемую этим значением, до проверки типа.
Итого, фактически весь мейнстрим статически-типизированных языков содержит возможности динамическоой проверки и приведения типов. От классичесских динамических скриптовых языков статически-тииированные из мейнстрима отличаются только тем, что не располагают инструментом утиной типизации. Т.е. вызов ф-ий (методов) всегда будет статически-типизирован. Это позволяет не хранить имена ф-ий в рантайм.
В общем, если язык статически-типизируемый, то переменная не может быть неопределенного типа, т.е. если на классическое определения динамической типизации наложим ограничения статической, то это должен быть такой тип, известный в compile-time, что переменной такого типа можно присвоить значение другого типа, то бишь согласно логике групп, который может объединить в себе другие типы, к которым переменная такого типа может быть динамически приведена. Для ООП примером может быть некий базовый класс и его наследники. Для Хаскеля — АлгТД.
Далее. Должен существовать оператор динамической проверки и приведения типа, где на входе этого оператора будет неуточненный тип выражения, а на выходе — уточненный. Заметь, приведение к объемлющему типу происходит "бесплатно": для С++ в худшем случае константное смещение указателя/ссылки, если база не первая в списке, а для Хаскеля — это безусловное конструирование АлгТД. Зато переход от объемлющего типа к уточненному требует или dynamic_cast в С++, или динамическое приведение в яве/дотнете, или ПМ в Хаскеле/Немерле. Т.е. первая операция выполняется за O(1), то вторая за O(n), где n — общее кол-во тестируемых уточненных типов.
VE>Статичность типизации — возможность гарантированно убрать ветвление. VE>Т.е. если в обоих языках по 2 ветвления при одинаковом поведении, то их статичность равномощна.
Простое ветвление на if, это если n=2. А если больше, то больше.
Если правильно закодировать метки типов, то ветвление по меткам можно заменить на табличную диспетчеризацию, и тогда вместо O(n) получим O(1). На это трюке сделан ООП-полиморфизм в мейнстриме, который позволяет перейти от объемлющего типа к уточненному за O(1). Поэтому у меня в коде очень мало if. Я уже писал рядом, что if — это зло.
VE>При моём определении две программы ниже статически типизированы в одинаковой мере. Однако третья — более статически типизирована: VE>И наплевать, что в Си++ просто ветвление, а в Haskell — ADT. VE>Предложи своё определение.
Дык, а чего предлагать? В Хаскеле ты требуемый признак вложил в систему типов программы (Maybe), а в С++ этот признак искуственный, проверяется вручную.
На С++ можно так же вложить в систему типов, на статическом полиморфизме, если код разруливается на шаблонах или на динамическом, если не получается.
Здравствуйте, DarkGray, Вы писали:
DG>При программировании может делаться много разных уровней абстрагирования разными способами, сейчас остановимся на трех: DG>1. пока мы описываем математические вычисления как абстрактный алгоритм — нам достаточно типа Number (и на этом уровне не конкретизуется, целое ли это число, комплексное или еще какое-то, как оно хранятся в памяти, каким образом выглядят ошибки вычисления и т.д.)
DG>2. дальше этот алгоритм применяется в какой-то конкретной задаче: здесь мы уже скорее всего знаем, каким число будет: целым, дробным, какие ошибки при вычислении могут быть и как мы их хотим показывать пользователю
DG>3. еще дальше мы эту конкретную задачу хотим выполнить на конкретной платформе: здесь нас начинает интересовать размерность чисел, способ хранения в памяти, какие типы и операции процессор подерживает нативно, скорость выполнения каждой операции и т.д.
Разумно. Хотя третий пункт далеко не всегда наступает, миллионы программистов на похапе и питоне успешно решают свои задачи, не имея представления о об этом третьем уровне. Что уж говорить о тех, кто пишет на JavaScript'e.
DG>Нельзя считать, что задача программиста заканчивается на 2 уровне, или тем более только на первом.
Таки можно очень часто (JS называют самым популярным ЯП, а там третьего уровня нет вообще). Но не всегда, согласен.
DG>Язык должен быть один для работы со всеми уровнями.
Не должен. Таких языков сейчас вообще около двух штук всего.
DG>Система типов должна быть разнообразной, но при этом единой. С одной стороны — на каждом уровне свои типы: на первом — number, sequence, collection, set и т.д., на втором — float, integer, array, dictionary и т.д., на третьем — native-int, byte, float32 и т.д., с другой стороны — и программист, и "машина" должны понимать как number переходит integer, а затем в native-int32, и что number=integer<TMemoryMapping, TRange> | float<TMemoryMapping, TRange, TAccuracy> | complex<..>, а x86-native-int32 = integer<signed, size:4, little-endian>
Я выше уже сказал, почему это невозможно. Потому что система типов фиксируется, а подробности отображения данных на железо все время новые появляются. Без libastral не обойтись. Ты уже решил, как отразить в системе типов устройство бразильских студенток?
DM>>Когда у тебя есть конкретный компилятор, ты действительно можешь сказать, что он в таких-то ситуациях представляет этот тип таким-то образом. Но это информация об этом конкретном компиляторе, а не об исходном языке. Поэтому вносить TMemoryMapping в систему типов нет смысла. В код конкретного компилятора — да, смысл есть и вполне понятный.
DG>Если TMemoryMapping отдается полностью на откуп компилятору, то у программиста пропадает возможность управлять эффективностью выполнения своего кода,
Это не совсем так. Если программист знает, как именно данный компилятор маппит данные на память, он может принимать решения и пытаться управлять эффективностью. Именно это делают сейчас С/С++ программисты. Никто не мешает кроме информации о языке использовать информацию о компиляторе. Просто я за то, чтобы не смешивать их в одном ведре. Более того, компилятор может позволить программисту управлять этим маппингом через опции компилятора. Так это и делается сейчас. И это именно опции компилятора, а не языка.
DG>Хуже всего, что присутствует самообман. Одной рукой программист для одних данных задает низкоуровневое управление памятью, а другой рукой закрывает себе глаза и говорит, что отказывается смотреть как другие данные будут себя вести на нижнем уровне. DG>Например, конструкция: DG>
DG>data Tree a = Leaf | Node a (Tree a) (Tree a)
DG>
DG>явно задает, что дерево хранится, как ссылочное бинарное дерево.
Это у тебя самообман. Данная конструкция ничего такого не задает. Значение типа Tree a может быть отлично представлено функцией, принимающей пару других функций. Почитай про STG-машину, использовавшуюся в GHC, например.
DM>Разумно. Хотя третий пункт далеко не всегда наступает, миллионы программистов на похапе и питоне успешно решают свои задачи, не имея представления о об этом третьем уровне. Что уж говорить о тех, кто пишет на JavaScript'e.
всё смешиваешь в кучу.
если бы их не интересовала производительность, то зачем для этих языков пишутся оптимизирующие компиляторы, mozilla в javascript вводит типизированные массивы и т.д.?
речь идет о том, что эти люди ради производительности не готовы переходить на C++, но в тоже время они готовы оптимизировать свои решения, если бы они при этом остались в рамках парадигмы своего языка, и увеличение производительности не требовало бы от них сильное увеличение доли ручного труда.
имхо, редкий программист отказался бы от wizard-а, который бы говорил, что:
функция zz написана неоптимально — переписать?
функция zz2 написано тоже неоптимально, но автоматически переписать не получается. Перепишите ее, пожалуйста, сами, или давайте перепишем совместно.
DG>>Нельзя считать, что задача программиста заканчивается на 2 уровне, или тем более только на первом.
DM>Таки можно очень часто (JS называют самым популярным ЯП, а там третьего уровня нет вообще). Но не всегда, согласен.
это дилетантский подход человека, который далек от js.
в js лимитирующим фактором является связь с сервером, которая обычно составляет 30-100мс, соответственно, пока код на js маленький и выполняется меньше, чем за 100мс, то смысла его оптимизировать особого нет.
но с каждым годом всё больше переносится функционала на js, он всё больше тормозит, и возникает задача его оптимизировать.
DG>>Язык должен быть один для работы со всеми уровнями.
DM>Не должен. Таких языков сейчас вообще около двух штук всего.
логически не правильно построенная фраза. уровень "как оно есть сейчас", ни коим образом не пересекается с уровнем "как оно должно быть", и соответственно фактами с уровня "как оно есть" нельзя опровергнуть "как оно должно быть".
DM>Я выше уже сказал, почему это невозможно. Потому что система типов фиксируется, а подробности отображения данных на железо все время новые появляются. Без libastral не обойтись. Ты уже решил, как отразить в системе типов устройство бразильских студенток?
и в чем ты видешь проблему?
мне, кажется, что ты считаешь, что система типов — это должно быть что-то статичное, один раз и навсегда заданное, а это не так.
система типов — это динамическая штука, которая перестраивается и вычисляется каждый раз заново при каждом изменении кода.
например, если в систему типов введены pre/post-condition-ы, то каждое изменение кода — эти condition-ы перестраивает.
> Ты уже решил, как отразить в системе типов устройство бразильских студенток?
они используют какое-то своё представление чисел, коллекций и т.д.?
для того, чтобы конкретизировать код для выполнения группой бразильских студенток, достаточно описать устройство этой группы:
какие типы для них являются нативными, какие операции они умеют делать, сколько времени каждая операция занимает, какая вероятность ошибочного выполнения каждой операции.
скорее всего выяснится, что коллекции малого объема и числа с низкой точностью являются нативными типами, остальное поддерживается плохо. значит придется ввести типы: маленькая коллекция/большая коллекция и конкретизировать код с учетом этих типов.
DG>>Если TMemoryMapping отдается полностью на откуп компилятору, то у программиста пропадает возможность управлять эффективностью выполнения своего кода,
DM>Это не совсем так. Если программист знает, как именно данный компилятор маппит данные на память, он может принимать решения и пытаться управлять эффективностью. Именно это делают сейчас С/С++ программисты. Никто не мешает кроме информации о языке использовать информацию о компиляторе. Просто я за то, чтобы не смешивать их в одном ведре.
не надо себя обманывать, то что пишется в исходнике является частью языка, а никак иначе
и почти все реальные программы на C/C++ вовсю используют явное управление памятью.
например, с помощью следующих pragm: http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
DM> Более того, компилятор может позволить программисту управлять этим маппингом через опции компилятора. Так это и делается сейчас. И это именно опции компилятора, а не языка.
наивное представление.
исходники программы — это десятки МБ информации
опции компилятора — это максимум 10 байт информации.
из теории информации следует, что 10 байт информации никогда не сможет управлять маппингом 10МБ информации
DM> Просто я за то, чтобы не смешивать их в одном ведре.
плохо формулируешь.
fill(collection, val)=foreach(ref item in collection)item=val;
fill<x86>(collection<лежащая-одним-куском-в-памяти, item=byte>, item<byte> val)={ecx:=collection.len;eax:=val;rep stosb;}
вот это смешивание? или это все-таки записанное в одном языке правильное явное разделение кода на два уровня абстрагирования, и явное задание, как данная функция должна конкретизироваться при заданной конкретике.
зы
вообще это напоминает маленький детей, которые считают, что если они не будет смотреть на страшное, то страшное не будет смотреть на них.
почему ты считаешь, что если ты не будешь смотреть на то, как компилятор конкретизирует твой код, он от этого сразу станет конкретизироваться правильно и эффективно?
DG>>Хуже всего, что присутствует самообман. Одной рукой программист для одних данных задает низкоуровневое управление памятью, а другой рукой закрывает себе глаза и говорит, что отказывается смотреть как другие данные будут себя вести на нижнем уровне. DG>>Например, конструкция: DG>>
DG>>data Tree a = Leaf | Node a (Tree a) (Tree a)
DG>>
DG>>явно задает, что дерево хранится, как ссылочное бинарное дерево.
DM>Это у тебя самообман. Данная конструкция ничего такого не задает. Значение типа Tree a может быть отлично представлено функцией, принимающей пару других функций. Почитай про STG-машину, использовавшуюся в GHC, например.
приведи программу, в которой функции принимают такое представление дерева, а при конкретизации программы данное дерево мапится на массив
Здравствуйте, DarkGray, Вы писали:
DM>>Разумно. Хотя третий пункт далеко не всегда наступает, миллионы программистов на похапе и питоне успешно решают свои задачи, не имея представления о об этом третьем уровне. Что уж говорить о тех, кто пишет на JavaScript'e.
DG>всё смешиваешь в кучу.
Наоборот, пытаюсь твою кучу разделить на отдельные слои.
DG>если бы их не интересовала производительность, то зачем для этих языков пишутся оптимизирующие компиляторы, mozilla в javascript вводит типизированные массивы и т.д.?
Оптимизирующие компиляторы пишутся как раз людьми, знакомыми с устройством компилятора и рантайма (работающими на третьем уровне абстракции), для людей, которые в эти дебри не опускаются, работая на первых двух. Людьми, пишущими на одном языке (обычно С/С++) для пишущих на другом (похапе, питон, JS...). От того, что какой-нибудь Ларс Бак написал быстрый движок JS, средний JS-программист Коля ничего не узнал о хранении целых чисел в памяти интерпретатора. На разных уровнях разные люди и разные языки. А ты тут смешиваешь все эти языки и уровни в одну кашу.
DM>>Таки можно очень часто (JS называют самым популярным ЯП, а там третьего уровня нет вообще).
DG>это дилетантский подход человека, который далек от js. DG>вот, например, 3-ий уровень в js: DG>https://developer.mozilla.org/en/JavaScript_typed_arrays
Я действительно далек от JS. Но скажи, пожалуйста, входят ли эти массивы в стандарт языка и не являются ли частной оптимизацией конкретного интерпретатора. Можно ли их использовать в IE и Chrome, например. Если нет, то это вряд ли часть языка.
DG>>>Язык должен быть один для работы со всеми уровнями. DM>>Не должен. Таких языков сейчас вообще около двух штук всего. DG>логически не правильно построенная фраза. уровень "как оно есть сейчас", ни коим образом не пересекается с уровнем "как оно должно быть", и соответственно фактами с уровня "как оно есть" нельзя опровергнуть "как оно должно быть".
У тебя "должен" значит "ДаркГрей хочет", а у меня "должен иметь свойство" значит "обязательно имеет", так это слово понимают в математике. Если в ряде успешных языков некоторого свойства нет, значит язык не обязан его иметь, не должен. Но может. И да, кому-то это может быть желательно, но это не называется "должен".
DG>>>Если TMemoryMapping отдается полностью на откуп компилятору, то у программиста пропадает возможность управлять эффективностью выполнения своего кода,
DM>>Это не совсем так. Если программист знает, как именно данный компилятор маппит данные на память, он может принимать решения и пытаться управлять эффективностью. Именно это делают сейчас С/С++ программисты. Никто не мешает кроме информации о языке использовать информацию о компиляторе. Просто я за то, чтобы не смешивать их в одном ведре.
DG>не надо себя обманывать, то что пишется в исходнике является частью языка, а никак иначе
Да, конкретно в случае С/С++ это часть языка. Но я бы не стал обобщать опыт С++ на все остальные языки.
DM>> Более того, компилятор может позволить программисту управлять этим маппингом через опции компилятора. Так это и делается сейчас. И это именно опции компилятора, а не языка. DG>наивное представление. DG>исходники программы — это десятки МБ информации DG>опции компилятора — это максимум 10 байт информации. DG>из теории информации следует, что 10 байт информации никогда не сможет управлять маппингом 10МБ информации
Ну да, конечно. Включение/выключение поддержки исключений задается простым ключом компилятора и очень сильно влияет на компиляцию всего кода, хоть 10МБ, хоть 10000МБ. Аналогично с определением wchar_t и пр. Размер исходников программы вообще не имеет значения.
DM>> Просто я за то, чтобы не смешивать их в одном ведре.
DG>вообще это напоминает маленький детей, которые считают, что если они не будет смотреть на страшное, то страшное не будет смотреть на них. DG>почему ты считаешь, что если ты не будешь смотреть на то, как компилятор конкретизирует твой код, он от этого сразу станет конкретизироваться правильно и эффективно?
С чего ты взял, что я так считаю? Я так не считаю.
А почему ты считаешь, что Y-комбинатор все хотят писать на ассемблере?
DG>>>Например, конструкция: DG>>>data Tree a = Leaf | Node a (Tree a) (Tree a) DG>>>явно задает, что дерево хранится, как ссылочное бинарное дерево.
DM>>Это у тебя самообман. Данная конструкция ничего такого не задает. Значение типа Tree a может быть отлично представлено функцией, принимающей пару других функций. Почитай про STG-машину, использовавшуюся в GHC, например.
DG>приведи программу, в которой функции принимают такое представление дерева, а при конкретизации программы данное дерево мапится на массив
Зачем? И что такое конкретизация программы, кто и когда ее производит?
Здравствуйте, vdimas, Вы писали:
V>По крайней мере итерация по типам узлов из стоимости O(n) на ПМ превращается в O(1) на двойной диспетчеризации (визитор/мультиметоды), в случае итерации по описанному дереву ПРОИЗВОЛЬНОГО кол-ва алгоритмов.
Какая такая итерация в O(n)? Давай-ка псевдокод.
V>Дык, если этот if-else идет по результату предиката проверки типа, то динамическая ес-но. Не люблю повторяться, но напомню, что речь о двухтактной схеме: проверка -> реинтерпретация памяти.
define реинтерпретация памяти.
V>Неужели "туман" еще не рассеялся?
Сейчас ты дашь определение, и окажется, что оно крайне туманно и зависит от конкретной реализации, а не от системы типов.
VE>>Зачем их видеть? Получать указатели на продолжения.
V>Не получится. Согласно приведенной тобой технике, тебе надо уметь отследить жизненный путь любого ЭКЗЕМЛЯРА такого АлгТД, создание которого заменяется сразу на обрабатывающий его код.
Что? Согласно моей технике вместо АлгТД передаётся тупл функций.
V>Можно ли на Хаскель написать DLL, которая содержит пересекающееся мн-во типов с основной програмой, тоже написанной на Хаскеле? И при этом взаимодействует с участием типов из этой области пересечения?
Говоря проще, можно ли из DLL на Haskell передать в программу, например, Maybe Int?
DM>Оптимизирующие компиляторы пишутся как раз людьми, знакомыми с устройством компилятора и рантайма (работающими на третьем уровне абстракции), для людей, которые в эти дебри не опускаются, работая на первых двух. Людьми, пишущими на одном языке (обычно С/С++) для пишущих на другом (похапе, питон, JS...). От того, что какой-нибудь Ларс Бак написал быстрый движок JS, средний JS-программист Коля ничего не узнал о хранении целых чисел в памяти интерпретатора. На разных уровнях разные люди и разные языки. А ты тут смешиваешь все эти языки и уровни в одну кашу.
Результат разработчика оптимизирующего компилятора Ларса Бака можно представить в виде белого открытого ящика доступного из ЯП.
Тогда бы программист Коля мог бы скачать библиотечку от D.Mon-а, вставить ее в JS, и трансформировать видео прямо в браузере.
Пока же исполнение JS остается черным закрытым ящиком — это невозможно.
DM>Я действительно далек от JS. Но скажи, пожалуйста, входят ли эти массивы в стандарт языка и не являются ли частной оптимизацией конкретного интерпретатора. Можно ли их использовать в IE и Chrome, например. Если нет, то это вряд ли часть языка.
в chrome входит, в IE — хз. и это означает, что код для FF и chrome будет работать быстро, а IE — медленно, и доля IE сократится еще в два раза.
и пофигу что там говорит стандарт.
DM>У тебя "должен" значит "ДаркГрей хочет", а у меня "должен иметь свойство" значит "обязательно имеет", так это слово понимают в математике. Если в ряде успешных языков некоторого свойства нет, значит язык не обязан его иметь, не должен. Но может. И да, кому-то это может быть желательно, но это не называется "должен".
в математике нет слова должен.
а вот в RFC есть в виде MUST, и означает, что zz должен иметь yy, чтобы соответствовать xx.
и я утверждаю, что для того, чтобы сгенеренный код для произвольной программы соответствовал обеспечению максимальной производительности, ЯП должен быть один для всех уровней.
DM>Да, конкретно в случае С/С++ это часть языка. Но я бы не стал обобщать опыт С++ на все остальные языки.
и в каких промышленных языках это не так?
DG>>из теории информации следует, что 10 байт информации никогда не сможет управлять маппингом 10МБ информации
DM>Ну да, конечно. Включение/выключение поддержки исключений задается простым ключом компилятора и очень сильно влияет на компиляцию всего кода, хоть 10МБ, хоть 10000МБ. Аналогично с определением wchar_t и пр. Размер исходников программы вообще не имеет значения.
не путай влиять и управлять.
управление генерацией кода имеет в виду, что для максимальной эффективности — каждая функция из 10МБ исходников должна быть собрана со своими настройками. что не может быть закодировано с помощью 10 байт информации.
DM>А почему ты считаешь, что Y-комбинатор все хотят писать на ассемблере?
а где я говорил, что ты хочешь писать его на ассемблере?
DM>>>Это у тебя самообман. Данная конструкция ничего такого не задает. Значение типа Tree a может быть отлично представлено функцией, принимающей пару других функций. Почитай про STG-машину, использовавшуюся в GHC, например.
DG>>приведи программу, в которой функции принимают такое представление дерева, а при конкретизации программы данное дерево мапится на массив
DM>Зачем?
ты выдвинул определенный тезис. подверди его.
DM> И что такое конкретизация программы, кто и когда ее производит?
конкретизация — придание более точного вида чему-либо.
частный случай конкретизации — задание параметров при параметрическом полиморфизме.
конкретизацией занимается явно программист, если язык поддерживает параметрический полиморфизм.
конкретизация может делаться программистом не явно.
большую работу по конкретизации выполняет компилятор, конкретизируется, например, какие переменные должны быть регистрами, какие участками в памяти, какие должны элиминироваться; конкретизируется как типы кладутся на память; конкретизуется каким ассемблерным инструктам будет соответствовать каждая строчка кода и т.д.
Здравствуйте, vdimas, Вы писали:
V>Если своими словами, то так: статическая строгая типизация — это достоверная интерпретация памяти, занимаемой значениями типов, известная в compile-time. Динамическая строгая типизация — это типизация, которая требует телодвижений в рантайм, т.е. когда невозможно достоверно интерпретировать память, занимаемую этим значением, до проверки типа.
Очень размыто, но допустим.
1. Давай реализуем АлгТД как: индекс конструктора + массив указателей на данные. При доступе мы просто берём указатель с нужным индексом. Т.е.
У нас тут оверхед, потому что на деле нужен только один элемент из массива (остальные всё равно средствами самого Haskell не получить из-за свойств языка), но вот такова реализация. Зато память интерпретируется достоверно. Это я для простоты нарисовал массив, на деле-то можно сгенерировать структурку с типизированными указателями.
2. Давай реализуем АлгТД как: функция, принимающая кортеж обработчиков, и вызывающая ровно один из них при каждом case of.
// Either a b
typedef std::function<void (ptr)> onData;
typedef std::function<void (onData, onData)> either;
// foo e = case e of
// Left x -> expr1
// Right y -> expr2
void foo(either const & e)
{
e([] (ptr x) { expr1 }, [] (ptr y) { expr2 });
}
Теперь АДТ — функция.
Что такое "интерпретация памяти"? Когда мы compile-time знаем, какого типа данные лежат в памяти? Ну вот знаем теперь, и что?
V>Далее. Должен существовать оператор динамической проверки и приведения типа, где на входе этого оператора будет неуточненный тип выражения, а на выходе — уточненный. Заметь, приведение к объемлющему типу происходит "бесплатно": для С++ в худшем случае константное смещение указателя/ссылки, если база не первая в списке, а для Хаскеля — это безусловное конструирование АлгТД. Зато переход от объемлющего типа к уточненному требует или dynamic_cast в С++, или динамическое приведение в яве/дотнете, или ПМ в Хаскеле/Немерле. Т.е. первая операция выполняется за O(1), то вторая за O(n), где n — общее кол-во тестируемых уточненных типов.
Т.е. ПМ в Haskell выполняется за O(n), да?
Давайте разбираться.
На вход мы даём выражение одного типа, на выходе (возможно) получаем другое выражение другого типа, так ещё и с другим адресом.
Под эти свойства подходит даже взятие элемента массива или доступ к полю объекта. Или разыменование указателя.
Я тут пытался описать свойства такой функции.
Так вот на мой взгляд эти свойства выглядит так:
-- каст туда-обратно гарантированно возвращает то же выражение
dynamicCast (cast x) = Just x
-- если каст успешен, то исходное выражение было получено кастом в обратную сторону
(dynamicCast e = Just v) -> (e = cast v)
Если будешь дополнять, то лучше псевдокодом, а не словами.
Но вот беда, для моего случая как динамика подходит указатель. int мы можем преобразовать в int*, а обратно — если указатель не равен 0.
Хуже того, int мы можем преобразовать в std::pair<bool, std::pair<int, float> >, а обратно — second.first или second.second в зависимости от флага. Перечисленным выше свойствам это удовлетворяет.
Я догадываюсь, какое свойство ты захочешь ввести, но попробуй его формализовать, и наверняка всплывут двойные трактовки. "Интуитивное" понимание меня волнует мало.
VE>>Статичность типизации — возможность гарантированно убрать ветвление. VE>>Т.е. если в обоих языках по 2 ветвления при одинаковом поведении, то их статичность равномощна.
VE>>При моём определении две программы ниже статически типизированы в одинаковой мере. Однако третья — более статически типизирована: VE>>И наплевать, что в Си++ просто ветвление, а в Haskell — ADT. VE>>Предложи своё определение.
V>Дык, а чего предлагать? В Хаскеле ты требуемый признак вложил в систему типов программы (Maybe), а в С++ этот признак искуственный, проверяется вручную.
Правильно. Но какая разница, вручную или нет, это ведь останется динамической типизацией. То, что в Хаскеле закладывается в систему типов (Maybe, Either, any other ADT), в C++ придётся реализовать искусственно, а значит от "телодвижений в рантайм" никуда не деться. Примеры я приводил, приведу ещё раз:
// int * foo();int * x = foo(10);
if (x)
std::cout << *x << std::endl;
else
std::cout << "null" << std::endl;
По моему определению — здесь в обоих случаях есть динамическая типизация в той же мере, что и в Haskell (в моём определении нет бинарного понятия, есть мера), но в меньшей, чем было бы на JavaScript, например.
V>На С++ можно так же вложить в систему типов, на статическом полиморфизме, если код разруливается на шаблонах или на динамическом, если не получается.
По поводу реинтерпретации памяти. Мне не нравится это интуитивное понятие, потому что на самом нижнем уровне типов уже нет. У нас указатели и вызовы функций, принимающие указатели. Тот же Either a b можно представить как просто флаг и указатель void*. А в зависимости от флага мы вызываем ту или иную функцию, которая принимает нетипизированный указатель. И вот в самом конце, когда наконец данные по указателю будут, например, выводить, произойдёт не реинтерпретация, а просто интепретация, при том ничего не стоящая. И получается, что динамичность типизации зависит от того, как будет проинтерпретирована память когда-то там намного позже. Причём принципиальной разницы между
Здравствуйте, DarkGray, Вы писали:
DG>Результат разработчика оптимизирующего компилятора Ларса Бака можно представить в виде белого открытого ящика доступного из ЯП.
Так ведь ящик доступен не из ЯП (в данном случае JS), а из определенного интерпретатора — V8. В другом интерпретаторе в том же самом языке эти достижения уже недоступны. Поэтому я и говорю, что это не часть языка.
Кажется, ты все время срываешься с обсуждения ЯП в целом и существующих ЯП на обсуждение гипотетического языка твоей мечты, эдакого мегаС++. Который должен уметь то и предоставлять это.
DM>>Я действительно далек от JS. Но скажи, пожалуйста, входят ли эти массивы в стандарт языка и не являются ли частной оптимизацией конкретного интерпретатора. Можно ли их использовать в IE и Chrome, например. Если нет, то это вряд ли часть языка.
DG>в chrome входит, в IE — хз. и это означает, что код для FF и chrome будет работать быстро, а IE — медленно, и доля IE сократится еще в два раза. DG>и пофигу что там говорит стандарт.
А мне пофиг на долю, мы говорим о теории ЯП и систем типов, а не о рынке браузеров. Нет в спецификации языка — значит нет в языке.
DM>>У тебя "должен" значит "ДаркГрей хочет", а у меня "должен иметь свойство" значит "обязательно имеет", так это слово понимают в математике. Если в ряде успешных языков некоторого свойства нет, значит язык не обязан его иметь, не должен. Но может. И да, кому-то это может быть желательно, но это не называется "должен".
DG>в математике нет слова должен. DG>а вот в RFC есть в виде MUST, и означает, что zz должен иметь yy, чтобы соответствовать xx.
Хорошо, вот RFC-шную трактовку и возьмем, она достаточно строгая.
DM>>Да, конкретно в случае С/С++ это часть языка. Но я бы не стал обобщать опыт С++ на все остальные языки. DG>и в каких промышленных языках это не так?
Похапе, JS, вероятно Java. Только зачем приплетать промышленность в разговор о теории ЯП?
DG>не путай влиять и управлять. DG>управление генерацией кода имеет в виду, что для максимальной эффективности — каждая функция из 10МБ исходников должна быть собрана со своими настройками.
Ты только что сузил спектр обсуждаемых языков до ассемблера. Ни в каком другом языке ты не можешь всем этим управлять с нужной точностью.
DM>>А почему ты считаешь, что Y-комбинатор все хотят писать на ассемблере? DG>а где я говорил, что ты хочешь писать его на ассемблере?
Нигде. Это я тебя пародирую, задаю вопрос, включающий ложную посылку.
DG>>>приведи программу, в которой функции принимают такое представление дерева, а при конкретизации программы данное дерево мапится на массив DM>>Зачем? DG>ты выдвинул определенный тезис. подверди его.
Я лишь опроверг твой тезис о том, что то определение типа диктует его определенное представление. Представление может быть и другим, но необязательно таким, какое ты просишь дальше.
DM>Кажется, ты все время срываешься с обсуждения ЯП в целом и существующих ЯП на обсуждение гипотетического языка твоей мечты, эдакого мегаС++. Который должен уметь то и предоставлять это.
обсуждение ЯП в целом включает в себя задачу: как меняются ЯП от прошлого к настоящему и куда эти изменения будут двигаться дальше.
DM>А мне пофиг на долю, мы говорим о теории ЯП и систем типов, а не о рынке браузеров. Нет в спецификации языка — значит нет в языке.
ты сейчас имеешь ввиду спецификацию де юре, а большая часть мира работает по спецификации де факто.
DM>Ты только что сузил спектр обсуждаемых языков до ассемблера. Ни в каком другом языке ты не можешь всем этим управлять с нужной точностью.
в c/c++ сложно, но можно, чем все обычно и занимаются
DM>Я лишь опроверг твой тезис о том, что то определение типа диктует его определенное представление. Представление может быть и другим, но необязательно таким, какое ты просишь дальше.
в реальном коде, кроме определения типа будут еще использованы функции по его созданию, а вот эти функции уже не отмапишь на массив (тем более неявно компилятором)
Здравствуйте, DarkGray, Вы писали:
DG>в chrome входит, в IE — хз. и это означает, что код для FF и chrome будет работать быстро, а IE — медленно, и доля IE сократится еще в два раза. DG>и пофигу что там говорит стандарт.
Ну если уж даже на стандарт пофигу, то нам вас точно не переубедить
DG>>ты сейчас имеешь ввиду спецификацию де юре, а большая часть мира работает по спецификации де факто.
VE>Хотел бы я с вами не системы типов, а, например, теорию категорий пообсуждать. Мне кажется, и там бы вы нашли способ обсуждать практику, а не теорию.
система типов и есть частный случай теории категорий и ее практической применение
DG>>система типов и есть частный случай теории категорий и ее практической применение
VE>То-то и оно. Оказывается, категория, это не класс объектов и морфизмы, а ещё и маппинги объектов на их расположение в памяти.
DG>>>система типов и есть частный случай теории категорий и ее практической применение
VE>>То-то и оно. Оказывается, категория, это не класс объектов и морфизмы, а ещё и маппинги объектов на их расположение в памяти.
DG>для кого оказывается?
Видимо, для того, кто упорно это приплетает в обсуждение системы типов.
Здравствуйте, vdimas, Вы писали:
M>>Вот тут, например, что является уточнёнными типами? M>>[haskell] M>>data T = A A1 A2 ... M>> | B B1 B2 ...
V>Tuple(A1, A2) и Tuple(B1, B2).
Т.е. тип значения A a1 a2 ... — Tuple(A1,A2,..), так что ли?
Здравствуйте, mima, Вы писали:
M>Т.е. тип значения A a1 a2 ... — Tuple(A1,A2,..), так что ли?
Хаскельный алгебраик — это сумма (копроизведение) произведений. Так что формально T — это сумма Tuple(A1, A2, ...) и Tuple(B1, B2, ...). Каждый из них — отдельный тип, их сумма — третий тип, отличный от обоих.
Здравствуйте, VoidEx, Вы писали:
VE>У нас тут оверхед, потому что на деле нужен только один элемент из массива (остальные всё равно средствами самого Haskell не получить из-за свойств языка), но вот такова реализация. Зато память интерпретируется достоверно.
Это она где-то в середине операции интерпретируется достоверно. А до начала операции достоверно ничего неизвестно.
И да, это очередная эмуляция механики происходящего в Хаскель, поздравляю.
VE>Это я для простоты нарисовал массив, на деле-то можно сгенерировать структурку с типизированными указателями.
Это ничего не изменит, так же как в твоей реализации union на "типизированной" структуре их 2-х указателей. Все равно до проверки указателей неизвестно, по какому из них лежит целевое значение, т.е. неизвестно, как интерпретировать содержимое АлгТД-обертки.
VE>2. Давай реализуем АлгТД как: функция, принимающая кортеж обработчиков, и вызывающая ровно один из них при каждом case of.
VE>
VE>// Either a b
VE>typedef std::function<void (ptr)> onData;
VE>typedef std::function<void (onData, onData)> either;
VE>// foo e = case e of
VE>// Left x -> expr1
VE>// Right y -> expr2
VE>void foo(either const & e)
VE>{
VE> e([] (ptr x) { expr1 }, [] (ptr y) { expr2 });
VE>}
VE>
VE>Теперь АДТ — функция.
А что поменялось? Кто-то должен будет сделать диспетчеризацию, т.е. выбрать саму ф-ию в любом случае.
Даже это было необязательно. Бо ты здесь ограничен примитивами С++, а компилятор может сделать простой jump/call по косвенной адресации. При нормальном кодировании метки типа ветвление можно сделать за O(1) на таблице независимо от числа вариантов. Здравствуй ООП-like полиморфизм.
VE>Что такое "интерпретация памяти"? Когда мы compile-time знаем, какого типа данные лежат в памяти? Ну вот знаем теперь, и что?
Если уйдут парные операции запаковки/распаковки — то то. Избавишься от лишних затрат, связанных с динамической распаковкой. Я кстате постоянно критикую увлечение подражательством этой технике на других языках — на дотнете/C#, например, где эту технику непосредственно вручную эмулируют. Мне возражают — это удобства ради. Т.е. это выходит такой способ группировки операций через объединение данных. ИМХО, почти всегда можно обойтись без группировки данных, проведя преобразования кода, которые ты предлагал. Ну, кроме случая зафиксированности таких данных во внешнем контракте, который будет открыт для реализации извне.
V>>Далее. Должен существовать оператор динамической проверки и приведения типа, где на входе этого оператора будет неуточненный тип выражения, а на выходе — уточненный. Заметь, приведение к объемлющему типу происходит "бесплатно": для С++ в худшем случае константное смещение указателя/ссылки, если база не первая в списке, а для Хаскеля — это безусловное конструирование АлгТД. Зато переход от объемлющего типа к уточненному требует или dynamic_cast в С++, или динамическое приведение в яве/дотнете, или ПМ в Хаскеле/Немерле. Т.е. первая операция выполняется за O(1), то вторая за O(n), где n — общее кол-во тестируемых уточненных типов.
VE>Т.е. ПМ в Haskell выполняется за O(n), да?
ХЗ. Мне недавно сказали, что в GHC кодирует не последовательные константы для юзверских АлгТД, а некую ссылку на описатель. Выходит, прямая табличная диспетчеризация недоступна при ветвлении по метке конструктора.
VE>Давайте разбираться.
VE>На вход мы даём выражение одного типа, на выходе (возможно) получаем другое выражение другого типа, так ещё и с другим адресом. VE>Под эти свойства подходит даже взятие элемента массива или доступ к полю объекта. Или разыменование указателя.
И что? Ты ведь сам показал в своих преобразованиях, что почти всегда этого можно НЕ делать. Т.е. если решать задачу таким же образом как в Хаскеле — то да, придется эмулировать работу АлгТД, никто не спорит. Но ведь можно не складывать типы в мешок, можно ведь протаскивать не данные в некий контракт, а наоборот, применить DI и затребовать типизированный обработчик-визитор (твой кортеж типизированных ф-ий). Классика жанра, однако.
VE>Я тут пытался описать свойства такой функции. VE>Так вот на мой взгляд эти свойства выглядит так: VE>
VE>-- каст туда-обратно гарантированно возвращает то же выражение
VE>dynamicCast (cast x) = Just x
VE>-- если каст успешен, то исходное выражение было получено кастом в обратную сторону
VE>(dynamicCast e = Just v) -> (e = cast v)
VE>
VE>Если будешь дополнять, то лучше псевдокодом, а не словами.
Не буду. Я многократно соглашался, что от dynamic_cast убежать более чем легко. Ты мне предлагаешь убежать не через пересмотр всего решения, а через техники-заменители, которые ни разу не изменяют исходное решение. Смысл? Количество if-ов, как ты правильно заметил, в этом случае НЕ изменится. Т.е. то ли ты проверяемый признак загонишь в систему типов, то ли в поле объекта, но если ты не сумел сделать перетащить этот признак из данных в поведение, то кол-во телодвижений будет одинаковое, с точностью до несущественных деталей.
VE>Но вот беда, для моего случая как динамика подходит указатель. int мы можем преобразовать в int*, а обратно — если указатель не равен 0.
Для случая maybe аналогично. Можно поинтересоваться, ты хотел что-то показать этим, или просто вслух рассуждаешь?
VE>Хуже того, int мы можем преобразовать в std::pair<bool, std::pair<int, float> >, а обратно — second.first или second.second в зависимости от флага. Перечисленным выше свойствам это удовлетворяет. VE>Я догадываюсь, какое свойство ты захочешь ввести, но попробуй его формализовать, и наверняка всплывут двойные трактовки. "Интуитивное" понимание меня волнует мало.
Зато я не догадываюсь, о чем догадываешься ты.
Поясни. Как системщик по образованию и призванию, я не делаю принципиальное различие м/у способами кодирования данных. Угу. Потому что при смене кодирования, пространство состояний (как минимум валидных состояний, т.е. интересующих нас), остается неизменно. Удачное кодирование данных может упростить схему, т.е. удешевить каждую/некоторую операцию в отдельности, но не изменить кол-во самих операций в координатах выбранного пространства состояний. Ты же пока упорно ставишь эксперименты в области кодирования этого пространства, доказывая (якобы доказывая), что кол-во операций при этом не изменится... Блин, но я это и так уже почти 20 лет прекрасно знаю. Вот что мне тебе на это отвечать? Давай уж тогда перейдем к вопросам оптимального кодирования состояний, коль оно тебя так волнует... будет чуть поинтересней... бо, скажем прямо, обсуждение maybe на указателях и прочий детсад малость потребляет терпение...
V>>Дык, а чего предлагать? В Хаскеле ты требуемый признак вложил в систему типов программы (Maybe), а в С++ этот признак искуственный, проверяется вручную.
VE>Правильно. Но какая разница, вручную или нет, это ведь останется динамической типизацией. То, что в Хаскеле закладывается в систему типов (Maybe, Either, any other ADT), в C++ придётся реализовать искусственно, а значит от "телодвижений в рантайм" никуда не деться.
Еще как деться. Ты же сам недавно показал неплохие преобразования. Кол-во операций ИЗМЕНЯЕТСЯ, потому что изменяется кол-во состояний. В твоем примере преобразования изчезало промежуточное состояние Either, т.е. изчезали все накладные расходы,связанные с его обслуживанием.
VE>По поводу реинтерпретации памяти. Мне не нравится это интуитивное понятие, потому что на самом нижнем уровне типов уже нет. У нас указатели и вызовы функций, принимающие указатели. Тот же Either a b можно представить как просто флаг и указатель void*.
Здравствуйте, D. Mon, Вы писали:
V>>Почему попытку разобраться в реализации всё время пытаются объявить "смешиванием"?
DM>Я согласен, что представлять, во что превращаются данные в памяти, нередко полезно и иногда даже необходимо. Но это просто знание подробностей компилятора. Хорошее, полезное знание. Просто не надо эту информацию толкать обратно в язык и говорить, что "на самом деле" там типы такие и такие. Нет, "на самом деле" компиляторов может быть много, и определение языка от них не должно зависеть.
Это малость не так. Например, даже в этом обсуждении была обнаружена как минимум пара ограничений на реализацию обсуждаемой системы типов. Т.е. такие ограничения можно вывести вообще без знания о реализации конкретного компилятора.
DM>Если вбить себе в голову, что такой-то тип представлен именно так, а не иначе, это может привести к проблемам, когда оптимизатор вдруг возьмет и сделает по-другому. Это даже в Си ежедневно случается, что уж говорить о более сложных языках.
И в современных оптимизаторах (т.е. суперкомпиляторах, по-сути) тоже никакой магии.
На сегодняшний день глубина просмотра вложеностей лидеров оптимизирующих компиляторов (а это С++ и ничего кроме С++) — порядка десяти уровней. Для того, чтобы скомипилять далеко не самые большие проги с полной оптимизацией, даже на линуксах уже требуются 2 гига оперативки под консолью. А каждый +1 к уровню просмотра оптимизации — это +1 к степени по фиг его знает какому основанию (сколько там ветвлений в средней процедуре?).
Т.е. даже возможности оптимизации современных компиляторов тоже надо предствлять себе, бо эти возможности ограничиваются объективными вещами — характеристиками сегодняшнего ходового железа.
Здравствуйте, vdimas, Вы писали:
DM>>Если вбить себе в голову, что такой-то тип представлен именно так, а не иначе, это может привести к проблемам, когда оптимизатор вдруг возьмет и сделает по-другому. Это даже в Си ежедневно случается, что уж говорить о более сложных языках.
V>На сегодняшний день глубина просмотра вложеностей лидеров оптимизирующих компиляторов (а это С++ и ничего кроме С++) — порядка десяти уровней.
Ну причем тут это. Вон, в хаскеле оптимизатор умеет какие-то промежуточные списки вообще выкидывать, используя fusion и rewriting rules. Видишь в коде список, думаешь, что знаешь как он устроен в памяти, а его на деле вообще может там не оказаться. Или даже проще — тупл. Ты можешь знать, как обычно туплы представлены в памяти, но в конкретном коде при передаче тупла из функции его компоненты могут быть переданы через стек без выделения памяти в куче. И т.д. Вряд ли найдется человек, четко представляющий все-все-все трюки GHC или современных С++ компиляторов.
Здравствуйте, D. Mon, Вы писали:
M>>Т.е. тип значения A a1 a2 ... — Tuple(A1,A2,..), так что ли? DM>Хаскельный алгебраик — это сумма (копроизведение) произведений. Так что формально T — это сумма Tuple(A1, A2, ...) и Tuple(B1, B2, ...). Каждый из них — отдельный тип, их сумма — третий тип, отличный от обоих.
Вопрос был вполне конкретный.
Ок, с другой стороны зайду. Понятие типа включает в себя операции над ним? Если да, то есть операции над типом Tuple(A1, A2...)? Есть. Можно ли их использовать для значений A a1 a2? Нет. И наоборот — операций над "типом" A не бывает. Только над общим типом T. Даже A -> Tuple не может быть операции. Только T -> Maybe Tuple.
Например, в Scala это не так, а в Haskell так: A — не тип. Мы можем использовать его как тапл только через разбор, например, катаморфизм.
В общем-то это и хотелось узнать: понятие типа для vdimas включает или нет операции над ним?
Ну, в хаскеле все еще прозрачней. В данном примере А — это функция, она принимает набор значений типов (A1, A2, ...) и возвращает значение типа T. Соответственно, какие-то операции мы можем делать с ее аргументами, это один тип, какие-то другие — с получаемым значением, это другой тип (T), а с самой А мы можем делать то, что позволительно делать с функциями, это третий набор операций.
"А a1 a2 ..." имеет тип Т, для него применимы операции с Т. "(a1, a2,...)" имеет другой тип, там другие операции доступны.
Тут есть еще тонкость тупл там или просто несколько аргументов, но с учетом карринга будем считать это несущественным.
А вот входят ли операции над типом в его определение — вопрос сложный и неоднозначный. Некоторые говорят, что входят. Мое мнение — не входят. Эти операции сами имеют свои типы, определяемые через типы аргументов, так что тип аргументов уже должен быть определен к моменту описания операций.
Здравствуйте, D. Mon, Вы писали:
DM>Ну, в хаскеле все еще прозрачней. В данном примере А — это функция, она принимает набор значений типов (A1, A2, ...) и возвращает значение типа T. Соответственно, какие-то операции мы можем делать с ее аргументами, это один тип, какие-то другие — с получаемым значением, это другой тип (T), а с самой А мы можем делать то, что позволительно делать с функциями, это третий набор операций.
Это не имеет отношения к вопросу.
DM>"А a1 a2 ..." имеет тип Т, для него применимы операции с Т. "(a1, a2,...)" имеет другой тип, там другие операции доступны.
Да. Обратите внимание, что по vdimas у A a1 a2 уточнённый тип (A1, A2)
DM>Тут есть еще тонкость тупл там или просто несколько аргументов, но с учетом карринга будем считать это несущественным.
Это всё неважно.
DM>А вот входят ли операции над типом в его определение — вопрос сложный и неоднозначный. Некоторые говорят, что входят. Мое мнение — не входят. Эти операции сами имеют свои типы, определяемые через типы аргументов, так что тип аргументов уже должен быть определен к моменту описания операций.
Давайте попробуем как-то с понятием разобраться. data T = A ... | B ... У значений A ... и B ... — один тип или разный? У выражений if flag then A ... else B ... и if not flag then A ... else B ... — один тип или разный?
Здравствуйте, vdimas, Вы писали:
VE>>Правильно. Но какая разница, вручную или нет, это ведь останется динамической типизацией. То, что в Хаскеле закладывается в систему типов (Maybe, Either, any other ADT), в C++ придётся реализовать искусственно, а значит от "телодвижений в рантайм" никуда не деться.
V>Еще как деться. Ты же сам недавно показал неплохие преобразования. Кол-во операций ИЗМЕНЯЕТСЯ, потому что изменяется кол-во состояний.
Странно, система типов одна, код один, а кол-во операций меняется. А динамическая типизация при этом убирается?
V>В твоем примере преобразования изчезало промежуточное состояние Either, т.е. изчезали все накладные расходы,связанные с его обслуживанием.
Spineless Tagless G-machine.
Только эти преобразования могут быть целиком на совести компилятора.
Здравствуйте, mima, Вы писали:
M>Давайте попробуем как-то с понятием разобраться. data T = A ... | B ... У значений A ... и B ... — один тип или разный? У выражений if flag then A ... else B ... и if not flag then A ... else B ... — один тип или разный?
Здравствуйте, D. Mon, Вы писали:
DM>Ну причем тут это. Вон, в хаскеле оптимизатор умеет какие-то промежуточные списки вообще выкидывать, используя fusion и rewriting rules. Видишь в коде список, думаешь, что знаешь как он устроен в памяти, а его на деле вообще может там не оказаться. Или даже проще — тупл. Ты можешь знать, как обычно туплы представлены в памяти, но в конкретном коде при передаче тупла из функции его компоненты могут быть переданы через стек без выделения памяти в куче. И т.д. Вряд ли найдется человек, четко представляющий все-все-все трюки GHC или современных С++ компиляторов.
Я удивляюсь, как можно одновременно ставить во главу угла представление типа в памяти и одновременно плевать на то, как значение типа будет (и будет ли вообще) представлено в программе.
Здравствуйте, mima, Вы писали:
M>Давайте попробуем как-то с понятием разобраться. data T = A ... | B ... У значений A ... и B ... — один тип или разный?
Один — Т.
M> У выражений if flag then A ... else B ... и if not flag then A ... else B ... — один тип или разный?
Здравствуйте, DarkGray, Вы писали:
M>>>Давайте попробуем как-то с понятием разобраться. data T = A ... | B ... У значений A ... и B ... — один тип или разный? DG>один обобщенный — T DG>разные конкретизированные: A, B
Кстати, если бы это был окамл и его полиморфные варианты, такое разделение было бы уместно. А в хаскеле это ересь, скорее всего.
VE>Странно, система типов одна, код один, а кол-во операций меняется.
Ты над ним выполнил IoC, и получил другой код. Одна и та же семантика не означает, что код один и тот же. А то так можно договориться, что хорошо спроектированный код и говнокод — это одно и то же, только потому, что работают одинаково.
VE>А динамическая типизация при этом убирается?
Вообще в ООП через IoC как раз борются с излишней динамической типизацией и прочими нарушениями LSP. Т.е. семантика сохраняется, а динамических проверок типов нет. Хотя, динамическое приведение типов есть, но оно теперь скрыто механикой рантайм-полиморфизма ООП. Чем это лучше ручной преврки и приведения типов? Да всем. В первую очередь тем, что в этом месте исключен человеческий фактор.
V>>В твоем примере преобразования изчезало промежуточное состояние Either, т.е. изчезали все накладные расходы,связанные с его обслуживанием.
VE>Spineless Tagless G-machine. VE>Только эти преобразования могут быть целиком на совести компилятора.
И что? Бета-редукция совмещенная с распространением констант до трети исходного кода способна нивелировать, если не больше, а суперкомпиляция до 3/4... И что это доказывает? НИЧЕГО. Потому что не влияет на исходный артефакт — программу, которую развивать и сопровождать и в которой еще надо будет делать ошибки и потом их искать.
===================
Знаешь что... У нас все время игра в одни ворота. Давай попробуем в другие для разминки. Возьмем пример, обратный твоим. Т.е. ты мне показывал технику из Хаскеля, реализованную на С, где у тебя система типов "там", превращалась в данные "здесь" и ты вопрошал: "а хде динамическая типизация, тут просто if?". Давай наоборот. Возьмём ООП-паттерн State (это совсем не то же самое, что монада State). В этом паттерне экземпляры внутреннего абстрактного объекта State неразличимы, если относятся к одному и тому же типу. Т.е. различимы только экземпляры разных типов. Это ключевое для паттерна. Теперь вспоминаем устройство динамического полиморфизма в мейнстримном ООП и спрашиваем себя — за счет чего различимы экземпляры только лишь различных типов в этом паттерне и при чем тут вообще система типов? Потом делаем этот паттерн в Хаскеле и получаем вместо иерархии типов-наследников абстрактного State обычный набор кортежей ф-ий, т.е. данные и код вместо типов. Вопрос на мильон $$, а куда делись типы в Хаскеле из исходного паттерна на С++?
Вот такого плана нелепыми вопросами ты меня заатаковал уже. Вроде ответил про кодирование состояний, про различные характеристики систем типов, про то, что любая информация о типах в рантайм — это ес-но данные и ничего кроме данных, и эти данные ес-но могут являться частью состояния, как это убедительно показано в ООП-паттерне State.
Здравствуйте, mima, Вы писали:
M>>>Т.е. тип значения A a1 a2 ... — Tuple(A1,A2,..), так что ли? DM>>Хаскельный алгебраик — это сумма (копроизведение) произведений. Так что формально T — это сумма Tuple(A1, A2, ...) и Tuple(B1, B2, ...). Каждый из них — отдельный тип, их сумма — третий тип, отличный от обоих.
M>Вопрос был вполне конкретный.
M>Ок, с другой стороны зайду. Понятие типа включает в себя операции над ним? Если да, то есть операции над типом Tuple(A1, A2...)? Есть. Можно ли их использовать для значений A a1 a2? Нет. И наоборот — операций над "типом" A не бывает.
А в ПМ у тебя что? Все операции, доступные над обычными туплами, доступны так же после распаковки АлгТД. Я не большой знаток Хаскеля, поправьте если не так, но есть ФП языки, где целый тупл может идти одним значением и даже быть поданным в виде такого одного значения в ф-ию соответствуюей размеру тупла арности. В общем, тот факт, что Хаскель не поддерживает в полной мере операции над туплами как в дргих языках не означает, что теперь туплы это не туплы...
M>Только над общим типом T.
Нет. Это ты как-то странно понимаешь термин "операция".
M>Даже A -> Tuple не может быть операции. Только T -> Maybe Tuple.
В условиях отсутствия структурных исключений любая условная проверка может возвратить только Maybe или любой его аналог, кодирующий тем или иным способом признак успешности проверки. А что это должно было доказать или опровергнуть?
M>Например, в Scala это не так, а в Haskell так: A — не тип. Мы можем использовать его как тапл только через разбор, например, катаморфизм.
Это от бедности ср-в над туплами конкретно Хаскеля. Но ведь в нем, если не ошибаюсь, можно назначить туплу алиас точно так же как любому другому типу, так? Т.е. Хаскель где-то "унутре" таки считает туплы неким типом, что даже может давать им алиасы наравне с другими типами.
M>В общем-то это и хотелось узнать: понятие типа для vdimas включает или нет операции над ним?
Ес-но включает. А разве при всей невнимательности Хаскеля к туплам над ними мало операций? А как же группировка, разбиение, возврат тупла как одного значения и т.д.?
M>Да. Обратите внимание, что по vdimas у A a1 a2 уточнённый тип (A1, A2)
Уточненный тип был Tuple(A1, A2) и больше ничего. Как вообще вызов ф-иий может являться типом? Какая-то каша.
M>Давайте попробуем как-то с понятием разобраться. data T = A ... | B ... У значений A ... и B ... — один тип или разный?
A и B в одном контексте представляют из себя вызов ф-ии, а в другом — дискриминаторы АлгТД. В этом втором случае тип значения дескриминатора одного и того же АлгТД ес-но один и тот же, согласно определению АлгТД. Но он тебе не доступен в Хаскель для пользовательских типов. Т.е. конструкция ПМ в Хаскеле такова, что информация о типе дискриминатора АлгТД нигде не участвует, следовательно нет надобности выносить её на уровень пользователя. Тем не менее, "числовое" или там "битовое" представление дискриминатора АлгТД тебе доступно для встроенных интегральных типов, которые являются АлгТД по спецификации.
M>У выражений if flag then A ... else B ... и if not flag then A ... else B ... — один тип или разный?
Это Хаскель или псевдоязык? А то ведь от контекста зависит, но ответ Да для обоих возможных контекстов.
Здравствуйте, D. Mon, Вы писали:
DG>>только надо быть честным и формулировать правильно: TMemoryMapping настолько изменчив, там настолько много информации, что я, D.Mon не знаю, как с этим можно работать.
DM>Нет, я о другом. Если включать TMemoryMapping в систему типов языка, то никакой вообще информации там не будет, т.к. значения его не будут известны. Ты не можешь заранее знать и включить в систему типов все подробности всех будущих реализаций компиляторов и других способов исполнения. Программу может вообще не компьютер исполнять, а группа бразильских студенток. Как у них в головах представлен int? Внесешь это в систему типов?
В С есть явное уточнение кол-ва бит для целочисленного типа. Если бит в исходном типе не хватает, то будет ошибка компиляции.
struct color {
int r:5, g:6, b:5;
};
size_t x:55; // x64 - ok, x86 - error C2034: type of bit field too small for number of bits.
Так что, при надобности легко обеспечить детерминированность целочисленной арифметики.
Ок, база индукции у тебя есть, теперь давай индуктивный переход, чтобы распространить на все другие языки. Мы же тут о языках и системах типов в целом говорим, а не об одном Си.
Здравствуйте, vdimas, Вы писали:
M>>Да. Обратите внимание, что по vdimas у A a1 a2 уточнённый тип (A1, A2) V>Уточненный тип был Tuple(A1, A2) и больше ничего. Как вообще вызов ф-иий может являться типом? Какая-то каша.
Что? Откуда вы взяли этот вызов функции?
M>>Давайте попробуем как-то с понятием разобраться. data T = A ... | B ... У значений A ... и B ... — один тип или разный? V>A и B в одном контексте представляют из себя вызов ф-ии, а в другом — дискриминаторы АлгТД.
A и B тут не при чём. Я спрашиваю про значения A ... и B ..., например, если A Int, а B String, то A 42 и B "hello" имеют один тип или разный?
V>В этом втором случае тип значения дескриминатора одного и того же АлгТД ес-но один и тот же, согласно определению АлгТД. Но он тебе не доступен в Хаскель для пользовательских типов. Т.е. конструкция ПМ в Хаскеле такова, что информация о типе дискриминатора АлгТД нигде не участвует, следовательно нет надобности выносить её на уровень пользователя. Тем не менее, "числовое" или там "битовое" представление дискриминатора АлгТД тебе доступно для встроенных интегральных типов, которые являются АлгТД по спецификации.
Я не понимаю. Вопросы:
Что такое значение дискриминатора? Например, A 42? Если да, то его тип мне доступен, почему нет?
При чём тут вообще ПМ?
Что такое тип дискриминатора? Вообще, если он нигде не участвует, может и не стоит о нём говорить, т.к. его нет? Например, я могу сказать, что в Haskell тип есть у where, но т.к. он нигде не участвует, то нет надобности выносить его на уровень пользователя, но это же бред. Тип бывает у значений. Дискриминатор (если под ним понимается A это или B) — не является значением.
Что такое встроенные интегральные типы?
M>>У выражений if flag then A ... else B ... и if not flag then A ... else B ... — один тип или разный? V>Это Хаскель или псевдоязык? А то ведь от контекста зависит, но ответ Да для обоих возможных контекстов.
Я не понимаю ответа "Да" на вопрос "это один тип или разный".
Здравствуйте, vdimas, Вы писали:
M>>Ок, с другой стороны зайду. Понятие типа включает в себя операции над ним? Если да, то есть операции над типом Tuple(A1, A2...)? Есть. Можно ли их использовать для значений A a1 a2? Нет. И наоборот — операций над "типом" A не бывает. V>А в ПМ у тебя что? Все операции, доступные над обычными туплами, доступны так же после распаковки АлгТД. Я не большой знаток Хаскеля, поправьте если не так, но есть ФП языки, где целый тупл может идти одним значением и даже быть поданным в виде такого одного значения в ф-ию соответствуюей размеру тупла арности. В общем, тот факт, что Хаскель не поддерживает в полной мере операции над туплами как в дргих языках не означает, что теперь туплы это не туплы...
Если есть языки, в которых A 42 13 воспримается не только как значение описанного типа T, но и как тупл (Int, Int), то в них, очевидно это так. В Haskell — да, это не тупл. И т.к. это не тупл, то "факт, что Хаскель не поддерживает в полной мере операции над туплами" с очевидностью ложен.
M>>Только над общим типом T. V>Нет. Это ты как-то странно понимаешь термин "операция".
Под операцией я понимаю функцию.
M>>Даже A -> Tuple не может быть операции. Только T -> Maybe Tuple. V>В условиях отсутствия структурных исключений любая условная проверка может возвратить только Maybe или любой его аналог, кодирующий тем или иным способом признак успешности проверки. А что это должно было доказать или опровергнуть?
Это должно опровергнуть ваше заявление о том, что у A 42 13 есть отдельный тип, отличный от B "hello" "world", скажем. Вы их ещё называете уточнёнными. Смысл в том, что раз нет различных операций над этими типами, то нет и различных типов.
M>>Например, в Scala это не так, а в Haskell так: A — не тип. Мы можем использовать его как тапл только через разбор, например, катаморфизм. V>Это от бедности ср-в над туплами конкретно Хаскеля. Но ведь в нем, если не ошибаюсь, можно назначить туплу алиас точно так же как любому другому типу, так? Т.е. Хаскель где-то "унутре" таки считает туплы неким типом, что даже может давать им алиасы наравне с другими типами.
Вы под туплом что понимаете? Тип или просто структуру? Если тип, то да, тупл — тип, но тогда A 42 13 — не тупл. Если структуру, то да A 42 13 — тупл, но тогда тупл A 42 13 и тупл 42:[] имеют разные типы (и им нельзя назначить алиас, например). Всё в контексе Haskell, разумеется.
M>>В общем-то это и хотелось узнать: понятие типа для vdimas включает или нет операции над ним? V>Ес-но включает. А разве при всей невнимательности Хаскеля к туплам над ними мало операций? А как же группировка, разбиение, возврат тупла как одного значения и т.д.?
Но тогда множества операций над типом T и над типом (Int, Int) — непересекающиеся. Следовательно, типы разные.
P.S. Давайте всё таки говорить о чём-то одном. Мой тезис: никаких уточнённых типов в Haskell нет. По той простой причине, что в коде мы их значения никак не можем отделить от других уточнений общего типа. Ваше замечание о ПМ, например, несостоятельно в том числе по этой причине.
Здравствуйте, vdimas, Вы писали:
VE>>Странно, система типов одна, код один, а кол-во операций меняется.
V>Ты над ним выполнил IoC, и получил другой код.
Здравствуйте, vdimas, Вы писали:
V>А то так можно договориться, что хорошо спроектированный код и говнокод — это одно и то же, только потому, что работают одинаково.
С т.з. того, есть там динамическая типизация или нет, да, одно и то же.
V>=================== V>Знаешь что... У нас все время игра в одни ворота. Давай попробуем в другие для разминки. Возьмем пример, обратный твоим. Т.е. ты мне показывал технику из Хаскеля, реализованную на С, где у тебя система типов "там", превращалась в данные "здесь" и ты вопрошал: "а хде динамическая типизация, тут просто if?". Давай наоборот. Возьмём ООП-паттерн State (это совсем не то же самое, что монада State). В этом паттерне экземпляры внутреннего абстрактного объекта State неразличимы, если относятся к одному и тому же типу. Т.е. различимы только экземпляры разных типов. Это ключевое для паттерна. Теперь вспоминаем устройство динамического полиморфизма в мейнстримном ООП и спрашиваем себя — за счет чего различимы экземпляры только лишь различных типов в этом паттерне и при чем тут вообще система типов? Потом делаем этот паттерн в Хаскеле и получаем вместо иерархии типов-наследников абстрактного State обычный набор кортежей ф-ий, т.е. данные и код вместо типов. Вопрос на мильон $$, а куда делись типы в Хаскеле из исходного паттерна на С++?
Какая разница. Это тебя волнует, куда что девается. Потому что в одном случае у тебя есть динамическая типизация, а в другом — нет.
Хотя семантика одна.
Здравствуйте, mima, Вы писали:
M>Тип бывает у значений. Дискриминатор (если под ним понимается A это или B) — не является значением.
Являются алиасами неких значений не хуже именованных констант. Конкретно в ограничениях Хаскеля ты можешь использовать эти значения только в конструкциях ПМ.
И да, сама по себе фраза "дискриминатор не является значением" — неслыханная ересь. Ес-но значение дескриминатора АлгТД является значением/индексом прямо по определению размеченного объединения.
M>Что такое встроенные интегральные типы?
Кто здесь?
M>>>У выражений if flag then A ... else B ... и if not flag then A ... else B ... — один тип или разный? V>>Это Хаскель или псевдоязык? А то ведь от контекста зависит, но ответ Да для обоих возможных контекстов. M>Я не понимаю ответа "Да" на вопрос "это один тип или разный".
if может вернуть лишь значение одного и того же типа из разных веток, так что в любом случае в компиллируемом примере будет один и тот же тип значения всего выражения. Рассматривай здесь A и B как ф-ии, конструирующие значение одного и того же неуточненного типа. Но если щатем уточнить содержимое такого неуточненного значения на ПМ, то получим, ес-но, разные уточненные типы-туплы.
M>>>Ок, с другой стороны зайду. Понятие типа включает в себя операции над ним? Если да, то есть операции над типом Tuple(A1, A2...)? Есть. Можно ли их использовать для значений A a1 a2? Нет. И наоборот — операций над "типом" A не бывает. V>>А в ПМ у тебя что? Все операции, доступные над обычными туплами, доступны так же после распаковки АлгТД. Я не большой знаток Хаскеля, поправьте если не так, но есть ФП языки, где целый тупл может идти одним значением и даже быть поданным в виде такого одного значения в ф-ию соответствуюей размеру тупла арности. В общем, тот факт, что Хаскель не поддерживает в полной мере операции над туплами как в дргих языках не означает, что теперь туплы это не туплы...
M>Если есть языки, в которых A 42 13 воспримается не только как значение описанного типа T, но и как тупл (Int, Int), то в них, очевидно это так.
Давненько такой густой каши не видел... ))
A 42 13 — это вызов банальной ф-ии, сигнатура которой описана так: Args->ReturnType, где
* ReturnType — T
* Args — Tuple(int, int).
M>В Haskell — да, это не тупл. И т.к. это не тупл, то "факт, что Хаскель не поддерживает в полной мере операции над туплами" с очевидностью ложен.
Что "это"???
Результат вызова ф-ии ес-но никакой не тупл, а значение типа Т. Но согласно семантики Хаскеля это неуточненное значение хранит в себе значение признака-дискриминатора, конкретно здесь A, а так же хранит значение типа Tuple(int, int), конкретно здесь (42, 13). См. моё замечание относительно дуальности идентификаторов конструкторов алгТД в Хаскеле.
V>>Нет. Это ты как-то странно понимаешь термин "операция". M>Под операцией я понимаю функцию.
Это слишком урезанное понятие. Ф-ии бывают не только пользовательские, но и встроенные, а операции бывают не только времени исполнения, но и времени компиляции. Популярные операции над туплами: обединение/рабиение — как раз пример таких операций.
M>>>Даже A -> Tuple не может быть операции. Только T -> Maybe Tuple. V>>В условиях отсутствия структурных исключений любая условная проверка может возвратить только Maybe или любой его аналог, кодирующий тем или иным способом признак успешности проверки. А что это должно было доказать или опровергнуть?
M>Это должно опровергнуть ваше заявление о том, что у A 42 13 есть отдельный тип, отличный от B "hello" "world", скажем.
Никак не должно, а сама фраза "у A 42 13 есть отдельный тип" какая-то нелепица. Что ты тимел ввиду? Тип возвращаемого значения или тип-сигнатуру ф-ии? Если первое — то оно одинаково с B "hello" "world", если второе — то разное.
M>Вы их ещё называете уточнёнными. Смысл в том, что раз нет различных операций над этими типами, то нет и различных типов.
Ес-но есть операции над уточненными типами — это распаковка завернутых туплов на ПМ. Собсно, в Хаскеле другого-то механизма в языке нет, кроме как ПМ, который достаёт уточненные типы из неуточненных АлгТД.
M>>>Например, в Scala это не так, а в Haskell так: A — не тип. Мы можем использовать его как тапл только через разбор, например, катаморфизм. V>>Это от бедности ср-в над туплами конкретно Хаскеля. Но ведь в нем, если не ошибаюсь, можно назначить туплу алиас точно так же как любому другому типу, так? Т.е. Хаскель где-то "унутре" таки считает туплы неким типом, что даже может давать им алиасы наравне с другими типами.
M>Вы под туплом что понимаете?
Некий составной тип, который представляет из себя кортеж неименованных полей.
M>Тип или просто структуру?
OMG
M>Если тип, то да, тупл — тип, но тогда A 42 13 — не тупл.
Ес-но, вызов ф-ии — это не тупл, это вызов ф-ии. Очень познавательно, спасибо.
M>Если структуру, то да A 42 13 — тупл, но тогда тупл A 42 13 и тупл 42:[] имеют разные типы (и им нельзя назначить алиас, например). Всё в контексе Haskell, разумеется.
Брр... разгрести эту кашу я не смог даже со 2-го раза...
M>>>В общем-то это и хотелось узнать: понятие типа для vdimas включает или нет операции над ним? V>>Ес-но включает. А разве при всей невнимательности Хаскеля к туплам над ними мало операций? А как же группировка, разбиение, возврат тупла как одного значения и т.д.? M>Но тогда множества операций над типом T и над типом (Int, Int) — непересекающиеся. Следовательно, типы разные.
Именно это я и показываю, что типы разные. Наверно Хаскель в плане туплов малость беден и не позволяет, скажем, в конструкции ПМ ссылаться на весь тупл через одну переменную (поправь, если я не прав), а затем эту переменную-тупл опять разобрать на составляющие во вложенном ПМ и т.д. Это особенность Хаскеля, что его пользовательские типы всегда алгТД, а набор операций над туплами крайне беден. Эдакий урезанный вариант функционального языка получился. Скорее всего, простоты ради.
M>P.S. Давайте всё таки говорить о чём-то одном. Мой тезис: никаких уточнённых типов в Haskell нет. По той простой причине, что в коде мы их значения никак не можем отделить от других уточнений общего типа.
Да без проблем можем отделить: переменные в шаблоне ПМ при разборе алгТД — это и есть "шаблон" кортежа результирующего тупла, полями которого ты можешь пользоваться в данной ветке ПМ. Видать, ограничения языка Хаскеля совершенно сбивают тебя с толку.
Re[4]: Мифический Haskell
От:
Аноним
Дата:
06.09.12 17:34
Оценка:
MM>По-моему, ты не писал реальных приложений вообще.
MM>Мне приходилось работать в геймдеве. Сейчас я занимаюсь антивирусами.
Геймдев и антивирусы это такие узкие-узкие нишевые области, в которых работают 0,1% программистов. Основная часть имхо ваяет внутрикорпоративное ПО либо сайты/порталы, и там и там GUI важная часть софта
Здравствуйте, VoidEx, Вы писали:
VE>Они уже оставили. Почти все нынешние языки впитали их гены и многие (в том числе Nemerle) являются потомками в том числе функциональных языков. А вот оставят ли потомство ваши, это ещё большой вопрос.
Это да. Но впитали они сами идеи ФП, а не синтаксис.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.