Здравствуйте, samius, Вы писали:
S>Здравствуйте, jazzer, Вы писали:
J>>Здравствуйте, samius, Вы писали:
J>>А сама статья есть где-нибудь? А то по ссылке только то, что она цитирует... S>у меня там же ссылка на PDF видна
Спасибо! (у тебя ссылка слегка битая, поправь).
Ну там как раз и говорится:
Haskell type classes and various extensions can be emulated in C++ by C++ templates/traits and some amount of compile-time metaprogramming. ...C++ can easily handle other possible extensions to Haskell type classes.
Здравствуйте, jazzer, Вы писали:
J>Ну там как раз и говорится: J>
J>Haskell type classes and various extensions can be emulated in C++ by C++ templates/traits and some amount of compile-time metaprogramming. ...C++ can easily handle other possible extensions to Haskell type classes.
Что тут удивительного? C++ "эмулирует" средства, добавляемые в язык с параметрическим полиморфизмом для того, чтоб получить ad-hoc полиморфизм. Т.е. полиморфизм, который в С++ основной. Правильнее было бы, наверное, сказать, что это хаскель "эмулирует" C++ средства.
Удивительно было бы, если бы C++ не "эмулировал" то, что в нем и так есть, а — наоборот — то, чего в нем нет — т.е. параметрический полиморфизм.
'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
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, samius, Вы писали:
S>>у меня там же ссылка на PDF видна
J>Спасибо! (у тебя ссылка слегка битая, поправь).
Не выходит. вставляю
Здравствуйте, WolfHound, Вы писали:
WH>Ошибка будет не при компиляции Add<float>, а при компиляции sum.
Вы будете смеяться, но неполная имплементация инстанса в хаскеле — не ошибка, а ворнинг:
Prelude> data D = D
Prelude> class C a where c :: a
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 4, types: 7, coercions: 2}
-- тут сгенерировалась полиморфная функция, принимающая словарь.
c
c = \ @ a tpl -> tpl `cast` ...
-- пишем пустой инстанс:
Prelude> instance C D
-- компилятор выдает ворнинг
<interactive>:13:10: Warning:
No explicit implementation for
‘c’
In the instance declaration for ‘C D’
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 5, types: 4, coercions: 3}
-- После чего генерирует функцию-заглушку, кидающую эксепшн
$cc
$cc = noMethodBindingError "<interactive>:13:10-12|c"#
-- и делает словарь с ней (когда функция класса одна - словарь это просто обернутая в newtype функция)
$fCD
$fCD = $cc `cast` ...
В главном, впрочем, вы правы.
'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>Здравствуйте, jazzer, Вы писали:
J>>Ну там как раз и говорится: J>>
J>>Haskell type classes and various extensions can be emulated in C++ by C++ templates/traits and some amount of compile-time metaprogramming. ...C++ can easily handle other possible extensions to Haskell type classes.
K>Что тут удивительного? C++ "эмулирует" средства, добавляемые в язык с параметрическим полиморфизмом для того, чтоб получить ad-hoc полиморфизм. Т.е. полиморфизм, который в С++ основной. Правильнее было бы, наверное, сказать, что это хаскель "эмулирует" C++ средства. K>Удивительно было бы, если бы C++ не "эмулировал" то, что в нем и так есть, а — наоборот — то, чего в нем нет — т.е. параметрический полиморфизм.
А чем тебе traits не параметрический полиморфизм? Ты меня поправь, если я понимаю неправильно, но параметрический полиморфизм от ad-hoc отличается лишь тем, что в ПП реализация одна, а все типовые зависимости вынесены в тайпклассы, в то время как в ad hoc их можно применить непосредственно (специализацией/перегрузкой).
Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции.
Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.
Здравствуйте, jazzer, Вы писали:
J>А чем тебе traits не параметрический полиморфизм? Ты меня поправь, если я понимаю неправильно, но параметрический полиморфизм от ad-hoc отличается лишь тем, что в ПП реализация одна, а все типовые зависимости вынесены в тайпклассы, в то время как в ad hoc их можно применить непосредственно (специализацией/перегрузкой). J>Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции. J>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.
traits и type classes это как раз ad-hoc (https://www.haskell.org/haskellwiki/Polymorphism#Ad-hoc_polymorphism).
ПП работает для любого типа. Он unconstrained. Потому, любая функция, которой нужно ограничение (traits, type classes), не может быть ПП.
K>Вы ее, например, попробуйте на псевдохаскеле написать и сразу все ясно станет.
Если ты намекаешь на то, что Add должна быть в сигнатуре, то, во-первых, я тебе ее и на С++ напишу:
template<class a>
typename enable_if< CheckTypeClass<Add, a>, a >::type
sum(vector<a> v);
где CheckTypeClass проверяет, что Add<a> определен и делает то, что надо.
А во-вторых, именно этим и занимаются концепты в явном виде:
template<Add a>
a sum(vector<a> v);
использование Add вместо обобщенного class как раз и означает проверку концепта (т.е. тайпкласса).
Вариант реализации CheckTypeClass (просто проверка сигнатур plus и zero):
template<class a>
struct CheckTypeClass<Add, a>
: and_< is_same< decltype(Add<a>::plus), a(a, a) >
, is_same< decltype(Add<a>::zero), a() >
>
{};
Соответственно, возвращаясь к примеру Wolfhound-а, если воткнуть проверку
static_assert(CheckTypeClass<Add,float>::value,"Add<float> is not correct");
сразу после объявления неполного Add<float> без zero, то мы мгновенно получим сообщение об ошибке:
main.cpp: In instantiation of 'struct CheckTypeClass<Add, float>':
main.cpp:34:40: required from here
main.cpp:27:136: error: 'zero' is not a member of 'Add<float>'
Здравствуйте, jazzer, Вы писали:
K>>Что тут удивительного? C++ "эмулирует" средства, добавляемые в язык с параметрическим полиморфизмом для того, чтоб получить ad-hoc полиморфизм. Т.е. полиморфизм, который в С++ основной. Правильнее было бы, наверное, сказать, что это хаскель "эмулирует" C++ средства. K>>Удивительно было бы, если бы C++ не "эмулировал" то, что в нем и так есть, а — наоборот — то, чего в нем нет — т.е. параметрический полиморфизм.
J>Ты меня поправь, если я понимаю неправильно, но параметрический полиморфизм от ad-hoc отличается лишь тем, что в ПП реализация одна, а все типовые зависимости вынесены в тайпклассы, в то время как в ad hoc их можно применить непосредственно (специализацией/перегрузкой).
Ну все правильно. Параметрический полиморфизм — это когда один код для любых типов. Соответственно полиморфный код, типизируется, компилируется раздельно, непробиваемая абстракция, есть полиморфная рекурсия и т.д. Но нам нужно и код для каждого типа отдельный писать.
Когда код отдельный для каждого типа — это ad-hoc полиморфизм.
Не понятно только, почему "лишь"? Куда уж больше отличая-то между полиморфизмами?
Prelude Data.Monoid> let msum xs = foldr (<>) mempty xs
-- получается один код для всех типов:
==================== Simplified expression ====================
returnIO
(: ((\ @ b $dMonoid xs -> foldr (<> $dMonoid) (mempty $dMonoid) xs)
`cast` ...)
([]))
msum :: Monoid b => [b] -> b
-- это была параметрически полиморфная часть.
Prelude Data.Monoid> data D = D
-- а теперь пишем код специфический для данного типа.
Prelude Data.Monoid> instance Monoid D where D `mappend` D = D; mempty = D
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 15, types: 13, coercions: 0}
-- ad-hoc для типа D
$cmappend
$cmappend = \ ds ds1 -> case ds of _ { D -> ds1 }
Rec {
-- собираем в словарь
$fMonoidD
$fMonoidD = D:Monoid D $cmappend $cmconcat
-- тут у нас дефолтная реализация
$cmconcat
$cmconcat = $dmmconcat $fMonoidD
end Rec }
J>Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции.
Ну и где эта единственная реализация? Будет по реализации для каждого типа.
Можно конечно и параметрический полиморфизм в C++ эмулировать, боксируя все, и делая "параметрическую" функцию, работающую со ссылками.
Но смысл "эмулировать" ad-hoc полиморфизм в C++ — где он и так всегда работает — от меня все еще ускользает.
J>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.
Ну так в хаскеле-то она единственная без кавычек. Понятно, что — при наличии разверток — при оптимизации может появится несколько специализированных функций — но в хаскеле это деталь реализации, она семантически ненаблюдаема. В C++ весь темплейтный код на наблюдаемости такой "генеративной" реализации и основан.
'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
Здравствуйте, jazzer, Вы писали:
J>Если ты намекаешь на то, что Add должна быть в сигнатуре, то, во-первых, я тебе ее и на С++ напишу: J> <...> J>сразу после объявления неполного Add<float> без zero, то мы мгновенно получим сообщение об ошибке: J>
J>main.cpp: In instantiation of 'struct CheckTypeClass<Add, float>':
J>main.cpp:34:40: required from here
J>main.cpp:27:136: error: 'zero' is not a member of 'Add<float>'
J>
J>до объявления sum, всё, как хотел Wolfhound.
Ну, концепты — это опциональная "проверяемая компилятором документация", без которой и так все работает. А тайпкласс — именно то, что и работает.
у вашей функции sum один параметр vector<a> v. У моей хаскельной версии на самом деле три параметра.
'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>Здравствуйте, jazzer, Вы писали:
J>>А чем тебе traits не параметрический полиморфизм? Ты меня поправь, если я понимаю неправильно, но параметрический полиморфизм от ad-hoc отличается лишь тем, что в ПП реализация одна, а все типовые зависимости вынесены в тайпклассы, в то время как в ad hoc их можно применить непосредственно (специализацией/перегрузкой). J>>Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции. J>>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно. S>traits и type classes это как раз ad-hoc (https://www.haskell.org/haskellwiki/Polymorphism#Ad-hoc_polymorphism). S>ПП работает для любого типа. Он unconstrained. Потому, любая функция, которой нужно ограничение (traits, type classes), не может быть ПП.
Во-первых, Что значит — работает? Типы ведь очень разные бывают. Мне как-то сложно представить функцию, которая будет работать абсолютно для всех типов, кроме identity (и то только для копируемых/перемещаемых типов) и констант типа примера ниже.
Во-вторых, обычные сиплюсовые шаблоны в каком-то смысле "работают" для всех типов изначально, до того, как были изобретены traits:
template<class a> byte bestArchiverEver(a) { return byte(1); }
для любого типа. unconstrained.
Или что там в статье приводят в качестве пример, map?
map :: (a -> b) -> [a] -> [b]
Ну так и в С++ он будет такой же:
template<class a, class b> list<b> map( b(a), list<a> );
Так что мне не очень понятно, когда Klapaucius говорит, что в С++ нет ПП.
Ну и в-третьих, traits, type classes — это не (только) ограничение, а, если можно так выразиться, средство натянуть сову на глобус.
Т.е. рассказать, как функции надо работать с типом, который без этого этой функции не подходил бы.
Как в примере со встроенным массивом, у которого нет методов begin/end — но их можно предоставить извне, и тогда функция std::accumulate будет работать со всем подряд. Но (судя по статье, которую ты привел) это Ad-hoc_polymorphism и есть.
Здравствуйте, Klapaucius, Вы писали:
K>Не понятно только, почему "лишь"? Куда уж больше отличая-то между полиморфизмами?
Потому что пока у нас одна функция — это параметрический полиморфизм.
Как только добавили к ней перегрузку/специализацию для какого-нть типа — все, ad hoc.
По мне так деталь абсолютно несущественная, чтоб еще и специальным названием заморачиваться и следить за правильной терминологией
J>>Но traits как раз и играют роль тайпклассов, позволяя сделать одну единственную реализацию ПП-функции. K>Ну и где эта единственная реализация? Будет по реализации для каждого типа.
В коде, естественно. Или ты про бинарник говришь? Имхо, это вообще никакого отношения к делу не имеет — как там что на бинарном уровне генерится.
K>Можно конечно и параметрический полиморфизм в C++ эмулировать, боксируя все, и делая "параметрическую" функцию, работающую со ссылками. K>Но смысл "эмулировать" ad-hoc полиморфизм в C++ — где он и так всегда работает — от меня все еще ускользает.
Ну так и параметрический работает "из коробки", не?
J>>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно.
K>Ну так в хаскеле-то она единственная без кавычек.
Где ж без кавычек? Вон у sum единственная реализация, только вот вся реальная работа вынесена в Add.
Так что если рассматривать sum как исключительно ее код — все хорошо.
А если принять во внимание Add и различия в его реализации — то сразу получится много всего.
Так что это вопрос чисто терминологический — насколько глубоко спускаться, определяя единственность реализации.
Мне вот лично кода вполне достаточно. Если тело функции определено (в тексте программы) один раз — значит, у нее единственная реализация. Что она там зовет из себя — ее личное, десятое, дело.
K>Понятно, что — при наличии разверток — при оптимизации может появится несколько специализированных функций — но в хаскеле это деталь реализации, она семантически ненаблюдаема. В C++ весь темплейтный код на наблюдаемости такой "генеративной" реализации и основан.
где? Вроде только сигнатуры проверяются же, кого колышет бинарный код?
Здравствуйте, Klapaucius, Вы писали:
K>Ну, концепты — это опциональная "проверяемая компилятором документация", без которой и так все работает. А тайпкласс — именно то, что и работает.
traits в С++ — это тоже "именно то, что и работает".
И моя Add в С++ — тоже "именно то, что и работает".
Плюс она таки в сигнатуре, как и у тебя Add в Haskell.
K>у вашей функции sum один параметр vector<a> v. У моей хаскельной версии на самом деле три параметра.
Можно раскрыть мысль про три параметра? Я вот смотрю глазами и вижу только один — [a].
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, samius, Вы писали:
J>>>Что в Хаскеле, что в С++ "единственная" реализация ПП-функции просто будет звать типозависимые дела из тайпклассов и traits, соответственно. S>>traits и type classes это как раз ad-hoc (https://www.haskell.org/haskellwiki/Polymorphism#Ad-hoc_polymorphism). S>>ПП работает для любого типа. Он unconstrained. Потому, любая функция, которой нужно ограничение (traits, type classes), не может быть ПП.
J>Во-первых, Что значит — работает? Типы ведь очень разные бывают. Мне как-то сложно представить функцию, которая будет работать абсолютно для всех типов, кроме identity (и то только для копируемых/перемещаемых типов) и констант типа примера ниже.
работает — значит handle values. Может и не значит, но я употребил "работает" примерно в этом контексте.
можно еще представить функцию length, которая возвращает длину списка для списка с любым типом элемента списка. А если напрячься, то еще и : (cons)...
J>Во-вторых, обычные сиплюсовые шаблоны в каком-то смысле "работают" для всех типов изначально, до того, как были изобретены traits:
Некоторые шаблоны работали для всех, а какие-то за счет перегрузок. Т.е. еще не traits, но уже ad-hoc J>
J>template<class a> byte bestArchiverEver(a) { return byte(1); }
J>
J>для любого типа. unconstrained.
а мне кажется что a должен уметь передаваться по значению.
J>Или что там в статье приводят в качестве пример, map?
map :: (a -> b) -> [a] -> [b]
J>Ну так и в С++ он будет такой же:
template<class a, class b> list<b> map( b(a), list<a> );
J>Так что мне не очень понятно, когда Klapaucius говорит, что в С++ нет ПП.
На самом деле у этого map есть неявный констрейнт на тип b, который накладывает list, в том числе при добавлении элемента.
J>Ну и в-третьих, traits, type classes — это не (только) ограничение, а, если можно так выразиться, средство натянуть сову на глобус. J>Т.е. рассказать, как функции надо работать с типом, который без этого этой функции не подходил бы. J>Как в примере со встроенным массивом, у которого нет методов begin/end — но их можно предоставить извне, и тогда функция std::accumulate будет работать со всем подряд. Но (судя по статье, которую ты привел) это Ad-hoc_polymorphism и есть.
Согласен. Ограничение (constraint) как средство. И даже как цель.
Здравствуйте, jazzer, Вы писали:
J>Потому что пока у нас одна функция — это параметрический полиморфизм. J>Как только добавили к ней перегрузку/специализацию для какого-нть типа — все, ad hoc.
Очевидно, что кроме этих двух негодных путей есть и третий — передача в функции для всех типов необходимого минимума специализированного кода (и варьирования соотношения между специализированным и обобщенным в зависимости от того, что нам нужнее — быстрая компиляция или быстрое исполнение). Этот третий путь называется тайпклассы, о них вся эта ветка.
J>По мне так деталь абсолютно несущественная, чтоб еще и специальным названием заморачиваться и следить за правильной терминологией
Ну это типичный прием в таких разговорах. Регулярно на каком то этапе оказывается, что между чем угодно и чем угодно разницы нет, а значит если в C++ что-то есть, то есть и что угодно другое.
Если для вас нет разницы между параметрическим полиморфизмом и ad-hoc, то о чем вы вообще спорите? Какой-то полиморфизм в C++ есть — значит все в порядке. Какой — не важно.
J>В коде, естественно.
Который отдельно от другого кода не типизируется, а типизироваться будут так раз по функции на каждый тип, в отличие от хаскеля, где полиморфную функцию можно типизировать.
J>Или ты про бинарник говришь?
Раздельная компиляция — это только ожидаемое следствие раздельной типизации. Впрочем, у плюсовых шаблонов есть генеративные родственники, которые тоже отдельно не компилируются, зато типизируются: параметризованные модули в sml и inline-функции в F#.
J>Имхо, это вообще никакого отношения к делу не имеет — как там что на бинарном уровне генерится.
И типизируемость и раздельная компиляция как раз имеют отношение к делу. Нет никакой пользы от модели "параметрический полиморфизм", если мы к ней прибавляем: "но ничего из того, что вы ожидаете от параметрического полиморфизма не работает".
J>Ну так и параметрический работает "из коробки", не?
Нет, не работает. Его нет.
JK>>Ну так в хаскеле-то она единственная без кавычек. J>Где ж без кавычек? Вон у sum единственная реализация, только вот вся реальная работа вынесена в Add.
Вовсе нет. Тот код, который обходит список, весь параметрически полиморфный. Чем это не "реальная работа", по сравнению со сложением?
J>Так что если рассматривать sum как исключительно ее код — все хорошо. J>А если принять во внимание Add и различия в его реализации — то сразу получится много всего.
Ну так все хорошо, потому что в хаскеле мы можем рассматривать sum и Add раздельно. В C++ с другой стороны sum без Add — это нетипизированный и некомпилируемый код.
J>Так что это вопрос чисто терминологический — насколько глубоко спускаться, определяя единственность реализации.
Дело тут не в глубине спуска.
J>Мне вот лично кода вполне достаточно. Если тело функции определено (в тексте программы) один раз — значит, у нее единственная реализация. Что она там зовет из себя — ее личное, десятое, дело.
Ну а мне кода, который ни типизировать, ни скомпилировать, да и понять как следует нельзя — недостаточно.
J>где? Вроде только сигнатуры проверяются же, кого колышет бинарный код?
Сигнатуры не проверяются, пока вы собственноручно проверку сигнатур не напишете. Что характерно, в таких спорах я еще не разу не видел такого, чтоб проверку сразу написали, хотя все "упражнение" в общем то в ней и заключается. От опциональной типизации пользы примерно столько же, сколько от отсутствующей. Вся полезность от тайпчека как легковесной верификации — от обязательности.
'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
Здравствуйте, jazzer, Вы писали:
K>>Ну, концепты — это опциональная "проверяемая компилятором документация", без которой и так все работает. А тайпкласс — именно то, что и работает.
J>traits в С++ — это тоже "именно то, что и работает". J>И моя Add в С++ — тоже "именно то, что и работает". J>Плюс она таки в сигнатуре, как и у тебя Add в Haskell.
Нет, это не то, что работает. Это ненужные приседания. В темплейтной функции суммирования можно + писать без всяких передач словарей. Раздельной типизации и компиляции, которые мы и оплачиваем этими "приседаниями" в хаскеле — в плюсах все равно нет.
K>>у вашей функции sum один параметр vector<a> v. У моей хаскельной версии на самом деле три параметра.
J>Можно раскрыть мысль про три параметра? Я вот смотрю глазами и вижу только один — [a].
Не знаю, куда вы смотрите. Посмотрите в код в моих сообщениях, там все параметры отлично видны.
Могу повторить код с типами, которые я совершенно напрасно отфильтровал в прошлый раз:
Prelude Data.Monoid> let msum xs = foldr (<>) mempty xs
==================== Simplified expression ====================
returnIO -- на эти навороты можно внимания не обращать
@ [()] -- это из-за объявления в интерпретаторе
(: @ () -- рассахаренная функция - лямбда ниже
((\ (@ b) ($dMonoid :: Monoid b) (xs :: [b]) ->
foldr @ b @ b (<> @ b $dMonoid) (mempty @ b $dMonoid) xs)
`cast` ...)
([] @ ()))
-- сюда я явно параметр дописал, сейчас - думаю - будет понятнее.
msum :: forall a. Monoid b => [b] -> b
Prelude Data.Monoid> instance Monoid D where D `mappend` D = D; mempty = D
==================== Tidy Core ====================
Result size of Tidy Core = {terms: 15, types: 13, coercions: 0}
$cmappend :: D -> D -> D
$cmappend = \ (ds :: D) (ds1 :: D) -> case ds of _ { D -> ds1 }
Rec {
$fMonoidD :: Monoid D
$fMonoidD = D:Monoid @ D D $cmappend $cmconcat
$cmconcat :: [D] -> D
$cmconcat = $dmmconcat @ D $fMonoidD
end Rec }
'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