Здравствуйте, alex_public, Вы писали:
_>Нельзя. Т.е. можно, если мы говорим о наследование времени исполнения (своеобразной разновидностью композиции). Но это совсем не эффективно по сравнению с нормальным наследованием времени компиляции. Вот его на библиотеках не реализуешь. Естественно если речь не о чём-то типа библиотеки крутых макросов позволяющих мощное метапрограммирование.
Здравствуйте, D. Mon, Вы писали:
DM>С помощью Dивной статической интроспекции проконтролировать, чтобы передаваемые функции были только действительно чистыми, совсем не сложно.
Насколько я понял, это будет выглядеть как строчка внутри map вроде
static assert(isPure!f)
где isPure — библиотечный код анализа f?
И достаточно будет сигнатуру проанализировать, и доступность исходного кода f не обязательна?
DM>Они с проблемами недостаточно чистых ф-й согласны, но говорят, что если такие ф-и заворачивать в полностью чистые (с иммутабельными параметрами), то сразу получаешь как бенефиты хаскельной чистоты, так и более развязанные руки по реализации этих чистых ф-й внутри.
Кстати, как в D используются бенефиты чистоты (компилятором, рантаймом и т.д.)? И насколько часто в библиотеках чистоту и иммутабельность аннотируют?
'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
Re[90]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Формально то способ есть — отобразить как статические методы обычного класса и всё. Но на практике этот класс вообще не рисуют, т.к. он по сути является всего лишь заменой (и кстати весьма убогой) пространств имён, а функции в нём обычно носят утилитарный характер. Естественно мы говорим про ООП программу.
Ну, между модулями, в отличие от статических классов, могут быть нетривиальные отношения. Соответствующие агрегации, например. Так что их есть смысл на диаграмме показывать.
K>>В C# есть статический класс. Что, C# тоже не совместим с UML, как и хаскель?
_>Ну во всяком случае в стандате UML никакого специального выделения для статических классов нет.
Так ведь разве не для этого в UML стереотипы есть? Есть, допустим, в вашем языке сепулькарии. Берете значек для класса, даете ему стереотип "сепулькарий" — все.
_>Однако это естественно не мешает отображать их при надобности (хотя при классическом ООП это сомнительно) как обычный класс.
Вообще-то языков соответствующих "классическому ООП" сейчас примерно 0 — они все с расширениями (если что, это не смотря на то, что я не смолтокер и классическим ооп его не считаю) а UML все еще по какой-то причине кто-то использует.
_>А вот для генерации подобного кода видимо уже придётся использовать только фирменые инструменты от MS, в которых наверняка это как-то добавлено. )))
Не приходилось генерировать код фирменными инструментами от МС. Сгенерированные диаграммы видел — поддержка стат.классов в них действительно есть (рамки прямоугольников пунктирные, емнип)
_>Даже если забыть про то, что мы так и не договорились по факту наличия модулей на диаграмме классов
Просто потому, что вы высасываете проблемы из пальца. На самом деле нет никаких причин не использовать на диаграммах какие-то "неклассические" элементы.
_>то в любом случае здесь они являются всего лишь разновидностью пространства имён — никаких связей прорисовать не удастся.
Почему не удастся, если они есть? Отлично можно прорисовать.
_>Т.е. получится не связанная диаграмма, некий набор независимых значков. Или может вы например подскажете, как нам например отобразить какие функции из данного модуля работают с каким-то из данного АТД (одного из определённых в том же модуле)?
А как вы рисуете, какие методы одного класса работают с методами другого класса? В чем разница-то? В конце концов, вы можете принять соглашение, что функции определенные на данном АлгТД — должны в его прямоугольнике перечислятся (в диаграмме для реального хаскельного кода с которой все и началось именно так и сделано) для этого в UML ничего добавлять не надо, в конце концов, бывают ООЯ, в которых методы класса выглядят как свободные функции. Более того, ФВП можно изображать на диаграмме как классы, если нужно показать их роль "фабрик" например.
_>Ага, есть инкапсуляция... Ну например:
_>1. Если у нас есть несколько АТД определённых в модуле и плюс имеем в модуле функции, работающие с приватными внутренностями этих АТД, то я правильно понимаю, что все функции внутри этого модуля имеют доступ ко внутренностям всех АТД, даже если реально не имеют к ним никакого отношения?
Да, конечно. Непонятно, правда, что они тогда делают в обсуждаемом модуле, ну да ладно. А методы "внутри" класса имеют доступ ко всем внутренностям класса — вот ужас-то!
_>2. Я могу у какого-то АТД сделать доступ снаружи только к части данных? Т.е. не закрывая всё и вводя публичную функцию типа геттера, а именно доступ к самим данным, но частичный.
Да, конечно, можно закрывать только часть конструкторов и полей, не обязательно все или ничего.
_>Это только то что с ходу в голову пришло, без изучения деталей. Подозреваю, что если начать копаться, то столько всего найти можно...
Точно так же можно найти и плюсы у такой системы декларации приватности/публичности по сравнению с "атрибутной".
_>Вообще то для этих целей в C++ служат пространства имён, которые обладают своими мощными возможностям, уж точно превосходящими классы C++, а скорее всего и модули Хаскеля (тут вообще сложно сравнивать, т.к. предназначения немного различаются всё же — пространства имён никак не завязаны на компиляцию и т.п.)
Некоторое пересечение по функциональности у классов и с пространствами имен есть.
_>Ну естественно можно написать map, принимающий только чистые функции.
Но вы слишком заняты обоснованием ненужности иммутабельности, чтоб это продемонстрировать. Приходится у других спрашивать.
_>Только нафига он мне такой-то, зачем себя ограничивать?
А нафига мне какой-то фубар без свойств вместо мапа со свойствами?
Для того и нужен, чтоб себя не ограничивать в преобразовании кода и в рассуждении о нем.
_>Я вполне могу себе представить сценарии, в которых будет удобно применить map с не чистой функцией.
Ценой отказа от других сценариев.
_>Я так и не понял какое отношение эта тирада имеет к вопросу о нужности требования чистоты от параметров map'a.
Прямое.
K>>Каким образом это можно обеспечить, если "чистые" функции могут изменяемые данные получить?
_>Нуу грубо говоря: _>
_>class A {v=1;};
_>auto a=new A;
_>auto x=[a, a, a];//ссылки на один объект
_>auto f(А p) pure {++p.v; return v;}
_>auto y=map!f(y);//после этого вызова имеем:
_>//x=[A{4}, A{4}, A{4}]
_>//y=[A{2}, A{3}, A{4}]
_>
Я говорил не про это, а про то, что у A есть еще одно поле — ссылка на изменяемый класс, который для нескольких экземпляров A в списке может указывать на один и тот же экземпляр этого самого мутабельного класса.
_>Ну а я вот не уверен, что наличие мутабельных данных (даже не смотря на то, что в самом примере их никто не меняет) никак не влияет на получающийся код.
В каком примере никто мутабельные данные не меняет?
_>На внешность то изначального кода это влияет и довольно заметно...
Ничего не понимаю. Что на внешность изначального кода влияет? Какой изначальный код вообще имеется в виду?
_>работало только благодаря искусственно введённой вами поддержке этого.
Это неправда.
_>Да глупо это и не нормально по сути. Вот объясните мне зачем мне хранить историю изменения некой переменной, если я точно знаю, что старое значение мне больше никогда не понадобится (как в том же примере с обработкой кадров видео)?
Хранить, конечно, незачем. Так она и не будет хранится: версия не нужна, а значит нет ссылки на версию — версия не хранится.
_>Ещё раз, я не говорю о том, что неважна скорость работы сборщика мусора (тем более, если речь о языке, в котором это единственный способ выделения памяти). Я пытаюсь напомнить, что кроме сборщика мусора может ещё множество разных вариантов. Может быть и банальный подсчёт ссылок (кстати, вот Apple выкатили язык вообще базирующий на этом), может быть аллокатор на пуле, может быть вообще ручная работа с указателями, а может быть и моё любимое выделение на стеке (кстати, для некоторых персистентных структур такое вполне может быть интересно, если у них сами данные хранятся единым куском в динамической памяти, а бегают туда сюда различные ссылки на него, которые вполне эффективно хранить на стеке), да и мало ли что можно придумать в языке с полным доступом на низкий уровень. И большинство из перечисленных мною вариантов будут работать быстрее любого сборщика мусора (из любого языка). Ну и соответственно если вдруг очень хочется пострадать иммутабельными структурами, то никто не мешает построить их и вокруг такого базиса.
Замечательно. В форумном диспуте можно, конечно, хоть плац ломом подметать, хоть персистентные структуры на счетчиках ссылок строить, но в реализациях языков, где эти структуры действительно широко используются без всяких страданий вроде "что же я делаю!? зачем мне старые версии?" есть быстрый сборщик мусора. По простой причине: для иммутабельных структур данных и типичных нагрузок связанных с работой с ними оптимальное управление памятью — точный, перемещающий сборщик с поколениями. Не стек, не счетчики ссылок, не пулы, не регионы. Построить их вокруг такого базиса мешают головная боль и тормоза.
'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
Re[92]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Не не не, он как раз на эффективность GC и напирает в данном разговоре. Что мол в D хотя GC и есть, но дохленький и следовательно (по его логике) нормально использовать иммутабельные данные в D нельзя. На что я ему намекаю, что в системных языках есть множество разных инструментов для эффективной работы с памятью. И мы вполне можем использовать иммутабельные данные и поверх них...
Так надо не намекать, а просто воспроизвести мой пример. При этом можно использовать любые "инструменты эффективной работы с памятью". Можете хоть счетчики ссылок использовать, хоть стек, хоть фрактальные пулы сепулькариев в подпространстве. Только вопроизведите мой пример на иммутабельных данных, с заметной нагрузкой на память, по всем условиям задачи, а не некий нерелевантный код, не использующий иммутабельные данные вообще — как в прошлый раз.
Вот тогда и посмотрим:
1) как все это будет выглядеть.
2) с какой скоростью будет работать.
'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
Re[91]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
DM>>С помощью Dивной статической интроспекции проконтролировать, чтобы передаваемые функции были только действительно чистыми, совсем не сложно.
K>Насколько я понял, это будет выглядеть как строчка внутри map вроде K>
K>static assert(isPure!f)
K>
K>где isPure — библиотечный код анализа f?
Примерно так, да. Можно static assert внутри (тогда свое красивое сообщение можно выдать), а можно constraint в виде if перед телом. На скорую руку у меня такой пример заработал:
template isImmutable(T) { enum isImmutable = isScalarType!T || !isMutable!T; }
auto map(alias f, A)(A[] xs)
if (isSomeFunction!f && allSatisfy!(isImmutable, ParameterTypeTuple!f))
{
alias B = ReturnType!f;
auto bs = new B[xs.length];
foreach(i, x; xs)
bs[i] = f(x);
return bs;
}
void main(string[] argv)
{
auto bs = [1,2,3].map!((int x) => x.to!string);
writeln(bs);
}
ฺK>И достаточно будет сигнатуру проанализировать, и доступность исходного кода f не обязательна?
Да, сигнатуры должно быть достаточно.
K>Кстати, как в D используются бенефиты чистоты (компилятором, рантаймом и т.д.)? И насколько часто в библиотеках чистоту и иммутабельность аннотируют?
Аннотируют довольно старательно, где могут. По крайней мере, последнее время. Кроме того, для шаблонных функций (а библиотеки ими полны) чистота выводится компилятором автоматически. А вот используются ли бенефиты компилятором — хз. Брайт говорит, что какие-то оптимизации с чистотой есть, но на практике я их не видел пока.
Re[92]: Есть ли вещи, которые вы прницпиально не понимаете...
I>1.3 Hiding implementation details: use module export list
I>One more usage of OOP classes is to hide implementation details, making internal data/functions inaccessible to class clients. Unfortunately, this functionality is not part of type class facilities. Instead, you should use the sole Haskell method of encapsulation, module export list:
I>module Stack (Stack, empty, push, pop, top, isEmpty) where
I>newtype Stack a = Stk [a]
I>empty = Stk []
I>push x (Stk xs) = Stk (x:xs)
I>pop (Stk (x:xs)) = Stk xs
I>top (Stk (x:xs)) = x
I>isEmpty (Stk xs) = null xs
I>Since the constructor for the data type Stack is hidden (the export list would say Stack(Stk) if it were exposed), outside of this module a stack can only be built from operations empty, push and pop, and examined with top and isEmpty.
Ну так это ты как раз и процитировал ту самую инкапсуляцию уровня древнего паскаля... И заметь там слово "Unfortunately"...
Re[6]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, NeoCode, Вы писали:
NC>Кстати, вот еще вопросы NC>1 в самом Хаскеле монады — это конструкция языка ("встроенная в компилятор") или что-то, построенное на основе других средств языка? Если последнее, то интересно как удается достичь соответствующего эффекта?
Ну как сказать... Формально говоря оно всё же построено на других средствах, но при этом очень интегрировано в язык (к примеру функция Main живёт в монаде IO, т.е. без неё никак). Плюс в язык добавлен специальный синтаксический сахар (который в прочем не обязателен для использования) специально для монад.
А вот про "соответствующий эффект" я что-то не понял. Не вижу там ничего особенного, с чем были бы проблемы в других языках. К примеру в начале этой темке есть пример реализации полноценной монады на C++ в пару строчек...
NC>2 какие вообще бывают монады? Хочется как можно более полный список.
А в императивных языках большая часть этих монад или просто не нужна (в хаскеле их существование является следствием сомнительного дизайна языка) или их задача уже решена средствами самого императивного языка (исключения например и т.п.), соответственно более эффективно. Остаётся небольшая часть и я уже перечислил в предыдущем сообщение самые распространённые.
Re[101]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Ikemefula, Вы писали:
I>Ты вероятно под ООП понимаешь исключительно вариант "инкапсуляция-наследование-полиморфизм как в С++". Между тем это всего лишь симуловское ООП, которое реализовано в С++. Скажем, Алан Кей, изобретатель ООП, c таким вариантом не согласен.
Ну понятно что во время исполнения можно любой вариант реализовать. Но для языков типа C++ и Хаскеля (при всей их разнице) это очевидно не вариант.
Re[91]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Ну, между модулями, в отличие от статических классов, могут быть нетривиальные отношения. Соответствующие агрегации, например. Так что их есть смысл на диаграмме показывать.
Ага, на диаграмме пакетов — в точности соответствует...
K>Просто потому, что вы высасываете проблемы из пальца. На самом деле нет никаких причин не использовать на диаграммах какие-то "неклассические" элементы.
Хы, ну если рисовать на бумажке, то возможно. А если пользоваться всяческими мощными инструментами, то обязанность жить в рамках стандарта UML появляется сама собой...
K>А как вы рисуете, какие методы одного класса работают с методами другого класса?
Вообще то это как раз всё указывается связями, включая даже имена переменных на них. Но я тут про другое говорил. Про связь кода со своими данными (инкапсуляцию) — это в ООП и UML задаётся как бы автоматически, просто по построению.
K>В чем разница-то? В конце концов, вы можете принять соглашение, что функции определенные на данном АлгТД — должны в его прямоугольнике перечислятся (в диаграмме для реального хаскельного кода с которой все и началось именно так и сделано) для этого в UML ничего добавлять не надо, в конце концов, бывают ООЯ, в которых методы класса выглядят как свободные функции. Более того, ФВП можно изображать на диаграмме как классы, если нужно показать их роль "фабрик" например.
Ну да, если сделать вот так, то подобная диаграммка будет уже не плохо описывать архитектуру приложения. Правда при этом будет очень сложно объяснить незнакомому с ООП (представим себе такого фаната ФП) человеку, что же собственно мы обозначаем этими прямоугольниками... )))
Кстати, я смотрю вы уже вроде как осознали, что единицей инкапсуляции всё же АлгТД являются, а не что-то ещё...
K>Да, конечно. Непонятно, правда, что они тогда делают в обсуждаемом модуле, ну да ладно. А методы "внутри" класса имеют доступ ко всем внутренностям класса — вот ужас-то!
Ну если делать грубо говоря по модулю на тип, то действительно получится аналог стандартных вещей из ООП мира. Но я бы не сказал, что это приемлемое требование.
K>Да, конечно, можно закрывать только часть конструкторов и полей, не обязательно все или ничего.
Ну т.е. аналог struct S { int a; private: int b}; легко делается? )
K>А нафига мне какой-то фубар без свойств вместо мапа со свойствами? K>Для того и нужен, чтоб себя не ограничивать в преобразовании кода и в рассуждении о нем.
А с чего вы взяли, что у map'а должно быть такое свойство (требование чистой функции на входе)?
Я вообще не в первый раз замечаю за вами такую позицию, когда вы называете вашу (ну или "хаскельную") точку зрения общепринятым стандартом. Ну ладно, в случае понятия "чистоты" ещё можно дать "хаскельному" взгляду некие особые права, т.к. хаскель первопроходец в этой фиче. Но уж в случае map'ов, которые сейчас есть наверное в каждом языке и естественно нигде кроме хаскеля не в курсе ни про какую чистоту, навязывать своё видение как стандарт мягко говоря смешно.
_>>Я вполне могу себе представить сценарии, в которых будет удобно применить map с не чистой функцией. K>Ценой отказа от других сценариев.
С чего это будет отказ от каких-то сценариев? Чистые функции мы же не перестаём принимать...
K>Я говорил не про это, а про то, что у A есть еще одно поле — ссылка на изменяемый класс, который для нескольких экземпляров A в списке может указывать на один и тот же экземпляр этого самого мутабельного класса.
Это же будет буквально тоже самое, что и в моём предыдущем примере. "А" тогда будет просто лишним контейнером. Показать кодом или сами осознаете? )
Я так понимаю, что вы уже видите что ошиблись со своим утверждением насчёт нарушения того равенства? Но всё же признавать не хотите... Наверное в итоге доберётесь до игр с указателями, лишь бы не признать свою неправоту. )
K>Ничего не понимаю. Что на внешность изначального кода влияет? Какой изначальный код вообще имеется в виду?
Ну у вас было два примера кода для той задачки. Мутабельный и иммутабельный. Причём внешне они заметно отличались. А внутренне думаю ещё больше. Вы показали пример оптимизации циклов на маленьком кусочке с иммутабельными данными. А меня вот терзают сомнения на тему "а возможны ли эти же оптимизации для мутабельного аналога этого кода".
K>Хранить, конечно, незачем. Так она и не будет хранится: версия не нужна, а значит нет ссылки на версию — версия не хранится.
Угу, только мы должны в начале произвести работу по выяснению "нужна эта версия ещё или уже нет". В то время как в нормальном мутабельном коде никакого подобного бреда нет, т.к. мы заранее точно знаем ответ — никакие старые версии не нужны.
K>Замечательно. В форумном диспуте можно, конечно, хоть плац ломом подметать, хоть персистентные структуры на счетчиках ссылок строить, но в реализациях языков, где эти структуры действительно широко используются без всяких страданий вроде "что же я делаю!? зачем мне старые версии?" есть быстрый сборщик мусора. По простой причине: для иммутабельных структур данных и типичных нагрузок связанных с работой с ними оптимальное управление памятью — точный, перемещающий сборщик с поколениями. Не стек, не счетчики ссылок, не пулы, не регионы. Построить их вокруг такого базиса мешают головная боль и тормоза.
Хыхы, это вы уже похоже от незнания такое пишете... Вообще то скажем пул будет быстрее любого сборщика мусора. И использование его более чем простое и удобное. Просто он подходит далеко не для каждой задачи. Так что по сути сборщик мусора — это не скорость или удобство, а средство нормально программировать "не умея работать с памятью самим".
Re[93]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Так надо не намекать, а просто воспроизвести мой пример. При этом можно использовать любые "инструменты эффективной работы с памятью". Можете хоть счетчики ссылок использовать, хоть стек, хоть фрактальные пулы сепулькариев в подпространстве. Только вопроизведите мой пример на иммутабельных данных, с заметной нагрузкой на память, по всем условиям задачи, а не некий нерелевантный код, не использующий иммутабельные данные вообще — как в прошлый раз.
Да пожалуйста. Напишем вот такой простейший контейнер:
immutable struct IArray(int L, T){
immutable auto opSlice() {return data[0..size];}
immutable auto opBinary(string op)(in T v) if(op=="~") {auto d=cast(T*)(data); d[size]=v; return immutable IArray!(L, T)(data, size+1);}
static auto Make() {return immutable IArray!(L, T)(cast(immutable T*)(GC.malloc(L*T.sizeof, GC.BlkAttr.NO_MOVE)), 0);}
private T* data;
private int size;
invariant() {assert(data!=null, "Неинициализированный объект!"); assert(size<L, "Надо бы побольше пул!");}
};
Это у нас иммутабельные структуры, живущие на стеке и оптимизированные с помощью персистентной структуры, живущей на пуле.
И используем этот контейнер таким образом:
T Primes(int M, T=immutable IArray!(M, int))(T primes=T.Make()~2~3, int v=5)
{
if(v>=M) return primes;
return Primes!M(primes[].find!q{b%a==0||a^^2>b}(v).front()^^2>v?primes~v:primes, v+1);
}
Primes!(2^^24)[].length.writeln;
K>Вот тогда и посмотрим: K>1) как все это будет выглядеть. K>2) с какой скоростью будет работать.
Ну насчёт внешности я уже давно понял, что у нас вкусы не совпадают... А вот измерение скорости уже не позволяет особого субъективизма. Так вот, этот пример работает у меня где-то на 10% быстрее самого оптимального мутабельного варианта. Т.е. в разы быстрее вашего мутабельного и наверное на порядок быстрее вашего иммутабельного...
P.S. Как я уже говорил, правильное применение иммутабельных данных не должно приводить к каким-то компромисам. Оно должно как минимум не влиять на быстродействие, а как максимум приводить к ускорению...
Re[92]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>А с чего вы взяли, что у map'а должно быть такое свойство (требование чистой функции на входе)? _>уж в случае map'ов, которые сейчас есть наверное в каждом языке и естественно нигде кроме хаскеля не в курсе ни про какую чистоту, навязывать своё видение как стандарт мягко говоря смешно.
В куче языков просто нет возможности проверить чистоту, поэтому там в документации пишут "используйте тут чистые ф-ии, а не то пеняйте на себя". Потому что по-хорошему map не должен гарантировать какой-то определенный порядок вычислений, а запуск эффектфул ф-й в неопределенном порядке — весьма ногопрострельноопасное дело. Дальше уже вопрос культуры: плюсовики привыкли иметь дело с опасными инструментами и сами думать обо всем, а в некоторых других языках предпочитают ограничить себя безопасными действиями, и тем избавиться от неприятных раздумий.
Re[92]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Ага, на диаграмме пакетов — в точности соответствует...
Это точно? <merge> пакета Foo в Bar даст Bar.Foo, а я про другое.
_>Хы, ну если рисовать на бумажке, то возможно. А если пользоваться всяческими мощными инструментами, то обязанность жить в рамках стандарта UML появляется сама собой...
Очевидно, что обсуждаемая диаграмма не была нарисована на бумажке.
K>>А как вы рисуете, какие методы одного класса работают с методами другого класса? _>Вообще то это как раз всё указывается связями, включая даже имена переменных на них.
Ну а я о чем? Это и ответ на ваш вопрос.
_>Но я тут про другое говорил. Про связь кода со своими данными (инкапсуляцию)
Инкапсуляция — это не "связь кода со своими данными"
_>Ну да, если сделать вот так, то подобная диаграммка будет уже не плохо описывать архитектуру приложения. Правда при этом будет очень сложно объяснить незнакомому с ООП (представим себе такого фаната ФП) человеку, что же собственно мы обозначаем этими прямоугольниками... )))
Да чего там объяснять? В том и дело, что эти прямоугольники и связи обозначают достаточно общие вещи.
_>Кстати, я смотрю вы уже вроде как осознали, что единицей инкапсуляции всё же АлгТД являются, а не что-то ещё...
С чего вы взяли?
_>Ну если делать грубо говоря по модулю на тип,
Ну или некоторое число связанных друг с другом АлгТД (взаимно рекурсивных, например)
_>то действительно получится аналог стандартных вещей из ООП мира. Но я бы не сказал, что это приемлемое требование.
Почему?
K>>Да, конечно, можно закрывать только часть конструкторов и полей, не обязательно все или ничего.
_>Ну т.е. аналог struct S { int a; private: int b}; легко делается? )
module S (S(a)) where
data S = S { a::Int, b::Int }
_>А с чего вы взяли, что у map'а должно быть такое свойство (требование чистой функции на входе)?
С того, что это удобно. Можно код оптимизировать, например.
_>Я вообще не в первый раз замечаю за вами такую позицию, когда вы называете вашу (ну или "хаскельную") точку зрения общепринятым стандартом. Ну ладно, в случае понятия "чистоты" ещё можно дать "хаскельному" взгляду некие особые права, т.к. хаскель первопроходец в этой фиче. Но уж в случае map'ов, которые сейчас есть наверное в каждом языке и естественно нигде кроме хаскеля не в курсе ни про какую чистоту, навязывать своё видение как стандарт мягко говоря смешно.
Да причем тут хаскель или моя позиция? Это свойство функторов. F(f o g) = F(f) o F(g)
_>С чего это будет отказ от каких-то сценариев? Чистые функции мы же не перестаём принимать...
Чистые функции не самоцель, а средство для сохранения таких вот свойств. Отказаться придется от всех сценариев, где мы можем полагаться на такие свойства map при рассуждении о коде, рефакторинге, оптимизации.
K>>Я говорил не про это, а про то, что у A есть еще одно поле — ссылка на изменяемый класс, который для нескольких экземпляров A в списке может указывать на один и тот же экземпляр этого самого мутабельного класса.
_>Это же будет буквально тоже самое, что и в моём предыдущем примере. "А" тогда будет просто лишним контейнером. Показать кодом или сами осознаете? )
Показывайте кодом. Речь идет о тривиальной трансформации моего примера на F# просто переносом ссылки на "счетчик" из замыкания функций в каждый элемент списка. Вы, кстати, невнятно ответили на вопрос будет ли работать аналог моего F# кода для "чистых"" функций Ди, работающих с мутабельными данными.
K>>Ничего не понимаю. Что на внешность изначального кода влияет? Какой изначальный код вообще имеется в виду?
_>Ну у вас было два примера кода для той задачки.
Для той задачи у меня был один пример кода — иммутабельный по условию задачи. Второй пример кода — был приближенным аналогом вашего, который к задаче никакого отношения не имеет.
_>Мутабельный и иммутабельный. Причём внешне они заметно отличались.
Разумеется они отличались. Это разные алгоритмы с разными свойствами обрабатывающие разные структуры данных, реализованных с помощью разных библиотек.
_>А внутренне думаю ещё больше. Вы показали пример оптимизации циклов на маленьком кусочке с иммутабельными данными. А меня вот терзают сомнения на тему "а возможны ли эти же оптимизации для мутабельного аналога этого кода".
Это часть кода из второго примера, с мутабельными данными. Причем та часть кода которая всю работу и выполняет. И та часть которую вы оптимизировали вручную заменив конвейер на готовую функцию.
_>Угу, только мы должны в начале произвести работу по выяснению "нужна эта версия ещё или уже нет".
Наоборот, работу проводить надо если версии для эфемерных структур. Для персистентных все просто работает.
_>Хыхы, это вы уже похоже от незнания такое пишете... Вообще то скажем пул будет быстрее любого сборщика мусора.
Пул рассчитан на сценарий, в котором время жизни объектов варьируется мало. Они массово становятся ненужны и мы освобождаем весь пул. В типичном сценарии работы с иммутабельными данными у нас много короткоживущих объектов и какое-то число долгоживущих некоторым образом распределенных среди короткоживущих при размещении в куче. Т.е. при применении пулов у вас в каждом пуле до самого конца будет пара процентов живых объектов и ни один из пулов вы освободить не можете. ГЦ — это, грубо говоря, пул, из которого долгоживущие объекты эвакуируют, чтоб его можно было освободить. При использовании пулов эту эвакуацию придется добавлять в алгоритм (в обсуждаемом примере это несложно: долгоживущие объекты — это простые числа, но при более-менее нетривиальном использовании персистентных структур — это уже не так просто). Все проекты автоматического разделения объектов на долгоживущие и короткоживущие стат.анализом и размещения их по этому признаку в разных пулах (вроде MLKit) пока успехом не увенчались.
_>Просто он подходит далеко не для каждой задачи.
Вот именно. Для работы с иммутабельными структурами он не подходит.
_>Так что по сути сборщик мусора — это не скорость или удобство, а средство нормально программировать "не умея работать с памятью самим".
Это просто миф про зеленый виноград, который распространяют любители языков в которых нормального ГЦ нет и не будет. ГЦ это именно средство оптимизации для типичных сценариев выделения памяти в высокоуровневых языках, а так же само средство достижения этой самой высокоуровневости.
'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
Re[94]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Да пожалуйста. Напишем вот такой простейший контейнер: _>
_>immutable struct IArray(int L, T){
_> immutable auto opSlice() {return data[0..size];}
_> immutable auto opBinary(string op)(in T v) if(op=="~") {auto d=cast(T*)(data); d[size]=v; return immutable IArray!(L, T)(data, size+1);}
_> static auto Make() {return immutable IArray!(L, T)(cast(immutable T*)(GC.malloc(L*T.sizeof, GC.BlkAttr.NO_MOVE)), 0);}
_> private T* data;
_> private int size;
_> invariant() {assert(data!=null, "Неинициализированный объект!"); assert(size<L, "Надо бы побольше пул!");}
_>};
_>
_>Это у нас иммутабельные структуры, живущие на стеке и оптимизированные с помощью персистентной структуры, живущей на пуле. _>И используем этот контейнер таким образом: _>
не имеет. Там строится длинный конвейер (у вас этого нет) между стадиями которого данные передаются в иммутабельных структурах данных (у вас этого нет) и, следовательно размещает много объектов в куче. Без оптимизации 276Гб, с оптимизацией 16Гб — это демонстрирует возможности компилятора уменьшать аллокации для такого кода. И делает это за 8 сек. — это показатель эффективности рантайма.
_>P.S. Как я уже говорил, правильное применение иммутабельных данных не должно приводить к каким-то компромисам. Оно должно как минимум не влиять на быстродействие, а как максимум приводить к ускорению...
Да, я уже понял, что "правильное использование иммутабельных данных" по вашему мнению сводится к их неприменению.
'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
Re[93]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, D. Mon, Вы писали:
DM>В куче языков просто нет возможности проверить чистоту, поэтому там в документации пишут "используйте тут чистые ф-ии, а не то пеняйте на себя". Потому что по-хорошему map не должен гарантировать какой-то определенный порядок вычислений, а запуск эффектфул ф-й в неопределенном порядке — весьма ногопрострельноопасное дело. Дальше уже вопрос культуры: плюсовики привыкли иметь дело с опасными инструментами и сами думать обо всем, а в некоторых других языках предпочитают ограничить себя безопасными действиями, и тем избавиться от неприятных раздумий.
Ну скажем так: явно существует ненулевое множество полезных нечистых функций, применение которых map'ом полностью безопасно. Хотя в принципе в императивных языках (см. ниже) я вполне готов был бы допустить такое ограничение на map. )))
А вообще в императивных языках это всё выглядит надуманными проблемами — если мы передаём в map не готовую библиотечную функцию, а какую-то лямбду (чаще всего), то обычно гораздо удобнее использовать явный цикл (тем более, если есть foreach).
Re[93]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Очевидно, что обсуждаемая диаграмма не была нарисована на бумажке.
Ну так на ней и нарисованы по сути некие виртуальные объекты в стиле ООП, как вы и предлагали ниже...
_>>Вообще то это как раз всё указывается связями, включая даже имена переменных на них. K>Ну а я о чем? Это и ответ на ваш вопрос.
А там речь про другое была. Нет в uml стрелочки "инкапсулированы"... )
K>Да чего там объяснять? В том и дело, что эти прямоугольники и связи обозначают достаточно общие вещи.
Безусловно. И эти общие вещи напрямую отображаются в код ООП программы. А в случае Хаскеля нам потребуется специальный перевод.
Собственно если мы говорим про диаграмму классов, то хаскель тут не одинок — тому же ассемблеру будет ещё хуже на ней. Но зато на ассемблере мы можем использовать две остальные важнейшие диаграммы, а в хаскеле, из-за его декларативности, они ни к чему (ну это мы уже обсуждали).
_>>Кстати, я смотрю вы уже вроде как осознали, что единицей инкапсуляции всё же АлгТД являются, а не что-то ещё... K>С чего вы взяли?
Потому как предлагаете рисовать виртуальные объекты именно на базе АлгТД, а не на базе модулей. ) Ну в общем то это и очевидно (мысль о массиве модулей сразу всё проясняет), хотя вы почему-то долго упирались. )))
K>С того, что это удобно. Можно код оптимизировать, например.
Так я не понял зачем запрещать то? ) Т.е. вот есть универсальный map (работает с любыми функциями и ничего не гарантирует не чистым). Посылаем в него чистые функции — получаем нужную вам картину. Посылаем не чистые — значит знаем что делаем и просчитываем эффекты.
K>Да причем тут хаскель или моя позиция? Это свойство функторов. F(f o g) = F(f) o F(g)
Во-первых это только в вашем мире функция map и понятие функторов жёстко связаны. )))
А во-вторых это правило будет сохраняться в D даже и для не чистых функций.
K>Чистые функции не самоцель, а средство для сохранения таких вот свойств. Отказаться придется от всех сценариев, где мы можем полагаться на такие свойства map при рассуждении о коде, рефакторинге, оптимизации.
Ещё раз: это будут свойства не map'а, а того, что мы подаём ей в качестве аргументов. Реализация map'a и принимающая только чистые функции и принимающая любые абсолютно одинаковая.
K>Показывайте кодом. Речь идет о тривиальной трансформации моего примера на F# просто переносом ссылки на "счетчик" из замыкания функций в каждый элемент списка. Вы, кстати, невнятно ответили на вопрос будет ли работать аналог моего F# кода для "чистых"" функций Ди, работающих с мутабельными данными.
Не помню что за F# код (я кстати не особо в курсе этого языка, хотя говорят там почти точная копия ocaml'а). Но в любом случае в D свой map, который работает на базе диапазонов, так что в нём даже с не чистыми функциями всё отлично. )))
K>Это часть кода из второго примера, с мутабельными данными. Причем та часть кода которая всю работу и выполняет. И та часть которую вы оптимизировали вручную заменив конвейер на готовую функцию.
Нет, в вашей "части кода" присутствует "Vector Int" в роли primes. А в том мутабельном варианте было нечто жуткое вида "data Buffer m a = Buffer { len :: !Int, buf :: !(Vector.Unboxed.Mutable.MVector m a) }". Так вот меня терзают сомнения, справится ли компилятор с оптимизацией для такой штуки... )
K>Пул рассчитан на сценарий, в котором время жизни объектов варьируется мало. Они массово становятся ненужны и мы освобождаем весь пул. В типичном сценарии работы с иммутабельными данными у нас много короткоживущих объектов и какое-то число долгоживущих некоторым образом распределенных среди короткоживущих при размещении в куче. Т.е. при применении пулов у вас в каждом пуле до самого конца будет пара процентов живых объектов и ни один из пулов вы освободить не можете. ГЦ — это, грубо говоря, пул, из которого долгоживущие объекты эвакуируют, чтоб его можно было освободить. При использовании пулов эту эвакуацию придется добавлять в алгоритм (в обсуждаемом примере это несложно: долгоживущие объекты — это простые числа, но при более-менее нетривиальном использовании персистентных структур — это уже не так просто). Все проекты автоматического разделения объектов на долгоживущие и короткоживущие стат.анализом и размещения их по этому признаку в разных пулах (вроде MLKit) пока успехом не увенчались.
Не не не. Это вы говорите про использование чего-то вроде автоматического сборщика мусора, основанного на пулах, т.е. опять же каком-то универсальном решение. А я говорю о непосредственном использование пулов в конкретных задачах. Т.е. когда мы заранее точно знаем (а не пытаемся отслеживать во время работы) время жизни набора объектов (например в рамках выполнения какой-то задачи). В таком случае мы спокойно размещаем все объекты (по этой задаче) на нашем пуле и спокойно уничтожаем его при завершение задачи.
K>Вот именно. Для работы с иммутабельными структурами он не подходит.
Ну конечно. ))) А код в моём соседнем сообщение оказывается из иной реальности. )))
K>Это просто миф про зеленый виноград, который распространяют любители языков в которых нормального ГЦ нет и не будет. ГЦ это именно средство оптимизации для типичных сценариев выделения памяти в высокоуровневых языках, а так же само средство достижения этой самой высокоуровневости.
Пока что по всем тестам ваше "средство оптимизации" болтается среди отстающих по быстродействию... )))
Re[102]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
I>>Ты вероятно под ООП понимаешь исключительно вариант "инкапсуляция-наследование-полиморфизм как в С++". Между тем это всего лишь симуловское ООП, которое реализовано в С++. Скажем, Алан Кей, изобретатель ООП, c таким вариантом не согласен.
_>Ну понятно что во время исполнения можно любой вариант реализовать. Но для языков типа C++ и Хаскеля (при всей их разнице) это очевидно не вариант.
Вообще говоря нормальный вариант. Я тут рядом дал ссылку от Gaperton, посмотри, убедись.
Re[95]: Есть ли вещи, которые вы прницпиально не понимаете...
не имеет. Там строится длинный конвейер (у вас этого нет) между стадиями которого данные передаются в иммутабельных структурах данных (у вас этого нет) и, следовательно размещает много объектов в куче. Без оптимизации 276Гб, с оптимизацией 16Гб — это демонстрирует возможности компилятора уменьшать аллокации для такого кода. И делает это за 8 сек. — это показатель эффективности рантайма.
Ээээ вы в своём уме? ))) Напомню нашу дискуссию:
K: В D иммутабельные структуры использовать нельзя, т.к. сборщик мусора не оптимизирован под это.
A: В языках типа D есть много других эффективных способов работы с памятью, помимо сборщика мусора.
K: Все другие способы тормозные и неудобные.
A: Стек и пул будут побыстрее любого сборщика мусора и вполне удобны, в том числе и для иммутабельных данных.
K: Ну покажите реализацию того нашего примера с иммутабельными данными на базе стека, пулов или чего угодно.
A: Вот код. В нём иммутабельнные данные на стеке и пуле. Работает быстрее всех наших предыдущих примеров.
K: Этот код не подходит, т.к. не размещает много объектов в куче.
K>Да, я уже понял, что "правильное использование иммутабельных данных" по вашему мнению сводится к их неприменению.
Ну покажите мне в моём последнем примере хоть одну не иммутабельную структуру данных.
Re[103]: Есть ли вещи, которые вы прницпиально не понимаете...
Кстати, самое забавное, что прототипное ООП применяется обычно в динамических языках. А в них можно намутить классическое ООП на уровне библиотеки, как ты и говорил. А вот в "серьёзных" языках это всё не варианты. )))