Здравствуйте, AndrewVK, Вы писали:
EP>>Допустим два типа слишком слабо связанны, и общий тип представляет процентов 5% от их возможностей — думаю в таком случае лучше Variant. AVK>Ну значит хотя бы при полном совпадении не должен генерироваться variant. EP>>В крайнем случае можно дать возможность получить из Varaint какой-то общий тип, с помощью отдельного явного вызова. AVK>Согласен.
Вот да. if без else вычислять в Maybe, а if с else вычислять в Either. Тогда, всем пользователям C# будет привычно и удобно, не то что when.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
G>>2. Если выражение if содержит else, то оно вычисляется "нормально". Иначе, оно вычисляется в void. EP>А почему void, а не Maybe?
Эта ветка, кстати, отличная демонстрация того, что надо очертить некий Nemerle Base Level и долбить его детально и тщательно. Это большая работа, но ее надо делать обязательно, а не надеятся что каждый сам себе напишет свой if.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Sinclair, Вы писали:
S>А зачем вы обязательно хотите одинаковости в вычислениях if-с-else и if-без-else? S>На мой взгляд, это два разных оператора, которые вполне имеют право возвращать разные результаты.
Я-то как раз никакой одинаковости и не "хочу". Это действительно два разных оператора, и то, что в сабже у них и синтаксис разный — это даже плюс, имхо.
S>Можно вообще считать else шорткатом для оператора ??. S>То есть результат выражения if (x == 5) 42 имеет тип Nullable<int>. S>Результат выражения if (x == 5) 42 else 41 имеет тот же тип, что и someNullableInt ?? 41, т.е. просто int.
Соглашусь, только если не Nullable<int>, а честный maybe<int>. Nullable, в этом случае, такой же паллиатив как и variant[int, void]
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, gbear, Вы писали: G>>Так-то да. Но, там речь даже не о типо-maybe шла... А о неком variant[T], который не монада, а есть "упращение" от variant[T, U] при U = T. В это предлагается вычислять if-с-else. S>Упрощением от variant<T, U> для T=U должен быть просто T. S>Потому что "int или int" — это просто int.
В "два" как раз есть случай помянутого выше упрощения для альтернативного парсера a | b (т.е. if/else по сути):
a: A, b: B --> (a | b): variant<A, B>
a: A, b: Unused --> (a | b): optional<A>
a: A, b: B, c: Unused --> (a | b | c): optional<variant<A, B> >
a: Unused, b: B --> (a | b): optional<B>
a: Unused, b: Unused --> (a | b): Unused
a: A, b: A --> (a | b): A
тут a, b — это выражения (парсеры), а A, B — их типы (типы их атрибутов), optional — это Maybe, Unused — void.
Здравствуйте, AndrewVK, Вы писали:
AVK>С другой стороны, в шарпе никакого общего типа не выводится, требуется точное совпадение. И этого, что характерно, в 99% случаев вполне достаточно. А оставшийся 1% легко фиксится явным кастингом одной из веток.
Здравствуйте, AndrewVK, Вы писали:
EP>>А почему void, а не Maybe? AVK>Кстати да.
Думаю если if без else это Maybe, то по идее if+else это Variant (в таком случае есть некоторая симметрия: Maybe<T> это грубо говоря Variant<T, void>).
А сейчас видимо для if+else просто общий тип выводится?
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Я думаю намного более неудобно, если в ветках получаются слабосвязанные A и B, и выводится какой-то крайне слабый общий тип вроде IPrintable (как это делается сейчас) — по сути много полезной информации о типах бесследно теряется.
По сути, она почти никогда не нужна.
А в тех редких случаях когда нужна того решения что вы предлагаете не достаточно.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
WH>>>>Не нужна информация о том, в какой ветке, какой тип. И из какой ветки мы получили управление. EP>>>Почему не нужна? WH>>По тому, что мы уже обработали информацию о ветвлении внутри самого if. EP>При вызове виртуального метода общего интерфейса (к которому привелись результаты веток) — точно также обрабатывается информация о ветвлении. EP>Не вижу здесь принципиальной разницы с Variant.
Я даже больше скажу, теоретически вызов метода содержащегося внутри Variant может иметь абсолютно такой же синтаксис как и вызов виртуального метода:
Variant<Triangle, Rectangle> v = ...;
v.draw();
А вот такой синтаксис можно получить в C++ прям сейчас:
Variant<Triangle, Rectangle> v = ...;
use_concrete_type(v)
{
v.draw();
};
Причём для всех вызовов внутри фигурных скобок не будет никакого dispatch, так как там у v один из конкретных типов — Triangle или Rectangle.
Dispatch будет только однократный, перед всеми вызовами (в отличии от виртуальных методов, где в общем случае для каждого вызова происходит индерекция).
Здравствуйте, VladD2, Вы писали:
VD>Я, вот, немерл уже 7 лет использую и реальных косяков особо не заметил. VD> Историю с when можно счесть за косяк, ну там в описании массивов есть непоследовательность, выбор скобок для дженериков слабостью парсера объясняется.
Опять сам себе противоречишь.
И мне пофигу, чем там оно объясняется. Косяки есть? Есть? Не фиксятся годами? Не фиксятся.
VD>Можно ли ругать авторов за этот выбор?
Я не ругаю авторов, я пытаюсь намекнуть почему у Немерле такое маленькое комьюнити.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Опять сам себе противоречишь.
Я себе не противоречу. 3 (три) это никак не целая куча.
Запомнить их труда не представляет. Исправление таких "косяков" положения дел не изменит.
AVK>И мне пофигу, чем там оно объясняется. Косяки есть? Есть? Не фиксятся годами? Не фиксятся.
До тебя никто не говорил, что это косяк. В шарик 100500 подобных косяков. Но ты их выучил и принял как данное. А тут ты в бутылку полез. Да еще и про целую куче косяков речь завел.
AVK>Я не ругаю авторов, я пытаюсь намекнуть почему у Немерле такое маленькое комьюнити.
Думаю, ты ошибаешься с оценкой значимости этих проблем.
Если бы when был реальной проблемой, о нем бы уже 10 тем завели.
Повторюсь, что возможно для успешности проекта действительно разумно было бы сделать чистый (или не очень) суперсет шарпа. Дизайн немерла логичен и оправдан, просто ты хочешь видеть в нем суперсет шаопа, а это не так. И это дизайнерский выбор авторов. Как идиалист я с ним согласен. Как циник и скептик считаю, что при всей правильности дизайна он вредит популярности. Привычное люди ценят больше чем удобное. Да и оценить удобство можно тоько освоив язык, а именно этого и не случается.
По сему менять что–то в немерле считаю не целесообразным. Нужно допиливать нитру и делать на ее основе 2 или три языка поддерживающие главные концепции, но имеющие разный синтаксис.
Кроме того компилятор немерла требует переписывания чисто с технической точки зрения. Его писали без привела на работу в качестве движка IDE, писали с целью по быстрее получить работающий код (срезая углы), писали используя менее мощьные технологии.
Нитра и есть технологическая основа для расширяемых языков. Она снимет ограничения немерла, избавит от багов, повысит уровень кода, автоматизирует создание поодержки IDE и т.п.
На ее основе можно будет создать язык с несколькими синтаксисами и одним ядром.
Залудим и немерл, и шарп++. И пусть история рассудит какой синтаесис выбрать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
G>>В вашем случае, важна именно полноценная монада maybe. Паллиатив вида variant[T, void] — не даст вам возможности поймать "go wrong". G>>И важен тут, не столько Nothing (который, вполне может быть заменен и на void), G>>сколько Just. Т.е. если за-предикатное выражение вычисляется в T, то "нормальный" результат вычисления if-без-else будет Just[T], а не T.
EP>Да, и я не вижу в этом проблемы.
А как вы собираетесь это потом использовать?! Повторюсь, смысл вычисления в maybe — возможность понять что "что-то пошло не так". Использование неявного приведения Just[T] к T (которое, кстати, вполне возможно) лишает нас этой возможности. Если нет неявного приведения, то "просто так" использовать результат вычисления if-без-else в тех же местах, где мы можем использовать результат вычисления if-с-else не получится.
Вот и вся проблема.
G>>Если теперь сравнить это с if-с-else, то видно следующее. Чтобы "в качестве результата можно использовать один тип результата" — в вашем случае — его придется вычислять в Just[T], а не в T. Что, вообще говоря, смысла лишено.
EP>Почему же? Для обобщённого кода как раз и не лишено — вне зависимости от того, значения каких типов возвращают ветки if-else, всё выражение вычислится в Variant.
Ну причем тут variant-то?! Речь идет о том, что нет смысла вычислять if-с-else в maybe.
EP>Для обычного, не обобщённого кода, да немного неудобно, но это обходится всего одним унарным оператором/унарной функцией, причём без потери производительности (Variant<T> всегда содержит внутри T, безусловно). EP>Тут главное не забывать, что if-с-else в общем случае возвращает Variant<T, U>, а не просто Variant<T>, который является всего лишь частным случаем.
Уф. Если уж на то пошло, то обобщенный код — песня отдельная. Там можно и нормальный match использовать.
G>>Даже если "обернуть" все это в variant — это мало что изменит . Типы A и Some[A] — это таки разные типы. G>>Можно потытаться "стереть границы" введя функторы, например... и вот тогда про "два синтаксиса" вообще никто вспоминать уж точно не будет
EP>В случае когда у нас Variant<T>, полноценный функторный fmap не нужен, точнее можно без него. Достаточно T unbox(Variant<T> x);, или короткого оператора.
По-ходу, как-то я не ясно выразился. Я вам про одно — вычисление if-без-else в maybe, а вы мне про другое — вычисление if-c-else в variant
Ладно, поговорим за variant...
А зачем вам какой-то явный unbox для variant? Вполне можно обойтись и неявным приведением к. Но, дело-то не в этом.
Смотрите... вот получили мы какой-то variant[string, int, ?DateTime], условно. Пусть даже у него есть какой-то unbox. Нормально обработать его можно только через PM. Вы же, надеюсь, не собираетесь прогонять его через очередной if? Об этом и речь.
G>>Вычисление if-с-else в variant, само по себе (без попытки добиться "симметрии") может иметь смысл. Но только в том случае, когда PM и использование variant уйдут на ступень выше, так сказать.
EP>То есть основная проблема заключается в каких-то пробелах в Nemerle с Varaint'ом как таковым, а не в самой идеи if-else -> Variant?
Nemerle тут вообще не причем Недостаточно вычислить if в variant[A, B, C]. Нужно еще как-то "узнать", что конкретно этот variant сейчас в себе несет... A, B или C. Даже в варианте с явным unbox, его тип вам заранее не известен. И его нужно будет либо дополнительно проверять... либо двойную диспетчеризацию городить... либо я ещё не знаю что Но просто так (без PM) использовать не получится. Хоть в Nemerle, хоть еще где.
Здравствуйте, VladD2, Вы писали:
VD>По сему менять что–то в немерле считаю не целесообразным. Нужно допиливать нитру и делать на ее основе 2 или три языка поддерживающие главные концепции, но имеющие разный синтаксис.
Вас на один язык не хватает. Про то что нужно компилятор переписывать я с самого начала твоего увлечения Немерле слышу. А тут ещ 2 языка...
Вобщем, вангую еще раз — через пять лет активно что то писать на Немерле будет не больше пары десятков человек.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Sinclair, Вы писали:
S>Упрощение variant[T, T] должно давать T для любого T. В том числе и для void. Так что (if(x > 0) foo(x)) будет иметь тип void, что вполне совпадает с интуитивными ожиданиями.
В этом разговоре как-то смешиваются типы-объединения с типами-суммами. Да T V T — это то же самое, что и T. Но T + T — не то же самое, что T.
Упрощенно говоря T + T = Left * T V Right * T, мы всегда можем отличить значение производимое injl от injr по "тегу".
Суммы, они же алгебраические типы и "варианты" — вещь распространенная, полезная на практике. Объединения, с другой стороны, штука довольно сомнительная и редко встречающаяся.
Что из себя представляет boost variant я вообще не смогу навскидку сказать, к примеру, он описывается как "set of types", и "static visitor" различает элементы по типу. Т.е. это никакой не вариант (не тип-сумма). Но с другой стороны это и не объединение, потому что мы не можем просто применять к значению этого типа функции, определенные на всех его элементах без приседаний со всякими визиторами. Т.е. он, похоже, совмещает в себе недостатки сумм и объединений (не могу сказать, что это неожиданный итог, раз уж речь идет о какой-то C++-поделке).
Действительно, с помощью объединений можно типизировать if таким образом:
Γ |- t1 : T1, T1 = Bool, Γ |- t2 : T2, Г |- t3 : T3, T2 V T3 = T
----------------------------------------------------------------
Γ |- if t1 then t2 else t3 : T
а if без else как T2 V Unit, но это полностью бессмысленно, потому что на объединении безопасны только операции, которые определены на всех элементах объединения, а на Unit (который тут void называют) ничего интересного не определить. Т.е. никакой практической разницы с возвращением просто Unit нет.
Сумму же можно элиминировать паттерн-матчингом (благодаря тегам) и обработать каждый элемент суммы теми операциями, которые на этом элементе определены.
G>>А что делать в "обобщенном коде", когда любой variant[T, U], при таком подходе, способен внезапно стать таким типо-maybe? S>А в чём тут проблема, напомните?
S>Я вижу у maybe только одно отличие от абстрактного варианта — это наличие доп. синтаксиса с hasValue.
Не понял, что за синтаксис. Maybe T — это сумма типа T и единицы.
То, что тут проходит под названием "типа Maybe" — это Alternative.
Если обобщеный "if без else" типизировать как Alternative f => Bool -> a -> f a то он будет работать с любым "типа Maybe", хоть с Maybe, хоть со списком, хоть с суммой типа с чем-то являющимся моноидом, т.е. в том числе и для суммы типа с единицей — Either () a.
Полиморфизм тут облегчает дизайнерское решение с выборами конкретного "типа Maybe" просто устраняя необходимость выбора: все что работает как Maybe будет работать с таким "if без else"
'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
Здравствуйте, AndrewVK, Вы писали:
EP>>Думаю если if без else это Maybe, то по идее if+else это Variant AVK>Думаю все таки Variant должен быть только если общий тип равен object.
Допустим два типа слишком слабо связанны, и общий тип представляет процентов 5% от их возможностей — думаю в таком случае лучше Variant.
В крайнем случае можно дать возможность получить из Varaint какой-то общий тип, с помощью отдельного явного вызова.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Допустим два типа слишком слабо связанны, и общий тип представляет процентов 5% от их возможностей — думаю в таком случае лучше Variant.
Ну значит хотя бы при полном совпадении не должен генерироваться variant.
EP>В крайнем случае можно дать возможность получить из Varaint какой-то общий тип, с помощью отдельного явного вызова.
Согласен.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
EP>>Допустим два типа слишком слабо связанны, и общий тип представляет процентов 5% от их возможностей — думаю в таком случае лучше Variant. AVK>Ну значит хотя бы при полном совпадении не должен генерироваться variant.
По-хорошему да, не хотелось бы делать Variant из одного типа, но это не должно ломать обобщённый код.
Например некая generic функция принимает два типа параметра, и делает if+else внутри. В общем случае типы результатов веток разные, а в некоторых частных — одинаковые. Код этой функции должен работать без изменений и там и там.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, gbear, Вы писали:
G>>2. Если выражение if содержит else, то оно вычисляется "нормально". Иначе, оно вычисляется в void.
EP>А почему void, а не Maybe?
Воот Совсем другой разговор.
Вообще говоря — принципиальной разницы во что вычилять if-без-else, в void или в option[T] — нет. Важно то, что if-с-else вычислит тоже самое выражение — гарантированно — в другой тип. А это — имхо, гооораздо... скажем так, станьше, чем "два синтаксиса для if"
Ну вот я и спрашиваю: зачем два _разных_ — по своей сути разных — выражения сводить к одному синтаксису?!
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Думаю если if без else это Maybe, то по идее if+else это Variant (в таком случае есть некоторая симметрия: Maybe<T> это грубо говоря Variant<T, void>). EP>А сейчас видимо для if+else просто общий тип выводится?
Для того, чтобы вычисление if+else в Variant имело смысл, его синтаксис должен позволять избегать вложенности. Иначе на выходе гипотетического:
Получим variant[string, variant[int, variant[decimal, option[T]]]]. Что несколько не удобно, имхо. Если идти по такому пути, то надо иметь возможноть получать какой-нибудь variant[string, int, decimal, option[T]]
Здравствуйте, gbear, Вы писали:
G>Вообще говоря — принципиальной разницы во что вычилять if-без-else, в void или в option[T] — нет. Важно то, что if-с-else вычислит тоже самое выражение — гарантированно — в другой тип. А это — имхо, гооораздо... скажем так, станьше, чем "два синтаксиса для if"
if+else может возвращать Variant<A, B>, а if-без-else — Variant<A> или Variant<A, void> (в зависимости от того как реализован Variant), что по сути является Maybe.
Получается достаточно симметрично.
G>Ну вот я и спрашиваю: зачем два _разных_ — по своей сути разных — выражения сводить к одному синтаксису?!
Здравствуйте, gbear, Вы писали:
G>Получим variant[string, variant[int, variant[decimal, option[T]]]]. Что несколько не удобно, имхо. Если идти по такому пути, то надо иметь возможноть получать какой-нибудь variant[string, int, decimal, option[T]]
Вижу несколько вариантов:
1. Автоматически делать flatten, но только при условии что это сломает обобщённый код.
2. Дать возможность делать явный flatten.
3. Сделать выражение не просто if else, а составное if else if else if else, то есть что-то типа специального keyword'а elseif (но думаю что его вводить не обязательно, а просто задавать составной синтаксис).
В этом случае те кому нужен плоский Variant — будут писать составное выражение, а если иерархический — то просто добавят скобки.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>if+else может возвращать Variant<A, B>, а if-без-else — Variant<A> или Variant<A, void> (в зависимости от того как реализован Variant), что по сути является Maybe. EP>Получается достаточно симметрично.
Скорее всего, я как-то Вас не так понял... но, имхо, не получится в этом случие "достаточно симметрично". Начиная с того, что variant[A] — будет вызывать только раздражение, а variant[A, void] — не является (хоть по сути, хоть еще как) Maybe.
void — в сабже — вполне себе нормальный тип. Вводя вычисление if-без-else в option[T] (так, как я понял, выглядит Maybe а сабже) — а это, "по сути", variant[Some[T], Nothing], а не variant[Some[T], void] — мы получаем возможность явно различать — было ли произведено вычисление выражения в if, или нет.
Вычисляя же его же в variant[T, void] вы такую возможность теряете (ведь T, вполне, может быть и void). И мне не понятно, чего полезного — по вашему — дает такое вычисление?
Даже если принять такое вычисление, то variant[A, B] и variant[A, void] — это таки разные типы (конечно, если B не void). Это мало что меняет.
Так же будут "проблемы" восприятия всяческих variant[A, A]. Даже если "упрощать" их до variant[A] — ничего, кроме дополнительного раздражения вывод таких типов вызвать не может.
Здравствуйте, gbear, Вы писали:
G>Скорее всего, я как-то Вас не так понял... но, имхо, не получится в этом случие "достаточно симметрично". Начиная с того, что variant[A] — будет вызывать только раздражение, а variant[A, void] — не является (хоть по сути, хоть еще как) Maybe. G>void — в сабже — вполне себе нормальный тип. Вводя вычисление if-без-else в option[T] (так, как я понял, выглядит Maybe а сабже) — а это, "по сути", variant[Some[T], Nothing], а не variant[Some[T], void] — мы получаем возможность явно различать — было ли произведено вычисление выражения в if, или нет. G>Вычисляя же его же в variant[T, void] вы такую возможность теряете (ведь T, вполне, может быть и void). И мне не понятно, чего полезного — по вашему — дает такое вычисление?
ИМХО, variant[Some[T], Nothing], или variant[T, void] это всё детали.
Основной поинт в том, что и для if-без-else, и для if+else, и даже для if+else+if+else+... в качестве результата можно использовать один тип результата Variant<...>
G>Так же будут "проблемы" восприятия всяческих variant[A, A]. Даже если "упрощать" их до variant[A] — ничего, кроме дополнительного раздражения вывод таких типов вызвать не может.
Для обобщённого кода это удобно.
Для обычного — возможно да, автоматическое упрощение до A было бы удобней. Но это упрощение можно получить явным вызовом одного унарного оператора или функции, причём если считать что variant<A> всегда содержит значение типа A (то есть не Nothing), то такой вызов не будет давать никаких накладных расходов.
Я думаю намного более неудобно, если в ветках получаются слабосвязанные A и B, и выводится какой-то крайне слабый общий тип вроде IPrintable (как это делается сейчас) — по сути много полезной информации о типах бесследно теряется.
Здравствуйте, WolfHound, Вы писали:
EP>>Я думаю намного более неудобно, если в ветках получаются слабосвязанные A и B, и выводится какой-то крайне слабый общий тип вроде IPrintable (как это делается сейчас) — по сути много полезной информации о типах бесследно теряется. WH>По сути, она почти никогда не нужна.
Если по сути не нужна, тогда действительно не понятно зачем when — был бы void во случаях (только из-за dangling else?)
WH>А в тех редких случаях когда нужна того решения что вы предлагаете не достаточно.
Здравствуйте, WolfHound, Вы писали:
WH>По сути, она почти никогда не нужна.
С другой стороны, в шарпе никакого общего типа не выводится, требуется точное совпадение. И этого, что характерно, в 99% случаев вполне достаточно. А оставшийся 1% легко фиксится явным кастингом одной из веток.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Если по сути не нужна, тогда действительно не понятно зачем when — был бы void во случаях (только из-за dangling else?)
Я не смог прочитать вопрос.
WH>>А в тех редких случаях когда нужна того решения что вы предлагаете не достаточно. EP>Почему?
По тому, что идёт перевод в некий предопределённый вариантный тип. https://github.com/rsdn/nemerle/blob/master/snippets/peg-parser/Nemerle.Peg.Macros/Optimizer/Optimizer.OptimizeRule.n
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
EP>>Если по сути не нужна, тогда действительно не понятно зачем when — был бы void во случаях (только из-за dangling else?) WH>Я не смог прочитать вопрос.
Как я понял, ты говоришь что информация о типах там по сути не нужна. Вопрос: почему бы просто тогда не возвращать void всегда, даже для if+else?
WH>>>А в тех редких случаях когда нужна того решения что вы предлагаете не достаточно. EP>>Почему? WH>По тому, что идёт перевод в некий предопределённый вариантный тип.
Почему в неопределённый-то? Он полностью зависит от того, что возвращают ветки if
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Как я понял, ты говоришь что информация о типах там по сути не нужна.
Не нужна информация о том, в какой ветке, какой тип. И из какой ветки мы получили управление.
EP>Вопрос: почему бы просто тогда не возвращать void всегда, даже для if+else?
По тому, что нужно значение.
WH>>По тому, что идёт перевод в некий предопределённый вариантный тип. EP>Почему в неопределённый-то? Он полностью зависит от того, что возвращают ветки if
Второе сообщение подряд, в котором я половину понять не могу.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, AndrewVK, Вы писали:
AVK>С другой стороны, в шарпе никакого общего типа не выводится, требуется точное совпадение. И этого, что характерно, в 99% случаев вполне достаточно. А оставшийся 1% легко фиксится явным кастингом одной из веток.
Rule.Not, Rule.And, Rule.Capture,... разные типы. Но все они потомки Rule.
В C# нет подобных языковых средств. Поэтому там никто так не пишет.
А в немерле есть. И в немерле нужно приведение к общему типу.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
EP>>Как я понял, ты говоришь что информация о типах там по сути не нужна. WH>Не нужна информация о том, в какой ветке, какой тип. И из какой ветки мы получили управление.
Почему не нужна?
EP>>Вопрос: почему бы просто тогда не возвращать void всегда, даже для if+else? WH>По тому, что нужно значение.
И какой тип у этого значения?
WH>Второе сообщение подряд, в котором я половину понять не могу.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
WH>>Не нужна информация о том, в какой ветке, какой тип. И из какой ветки мы получили управление. EP>Почему не нужна?
По тому, что мы уже обработали информацию о ветвлении внутри самого if.
WH>>По тому, что нужно значение. EP>И какой тип у этого значения?
Тот, к которому приводятся типы обеих веток.
EP>Что в этом плохого?
То, что это не нужно.
EP>Почему это хуже, чем вывод общей базы,
Хотя бы по тому что для того чтобы получить данные из этого варианта тебе нужен ещё один точно такой же if.
EP>которая может быть крайне "слабым" типом вроде IPrintable, а то и вовсе Object.
В немерле Object не выводится. Там специальная закладка на этот счёт есть.
Ибо это почти всегда ошибка. А когда оно нужно, нужно одну из веток явно привести к Object.
Ради интереса можешь попробовать найти хоть один язык, который ведёт себя, так как вы тут хотите.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
WH>>>Не нужна информация о том, в какой ветке, какой тип. И из какой ветки мы получили управление. EP>>Почему не нужна? WH>По тому, что мы уже обработали информацию о ветвлении внутри самого if.
При вызове виртуального метода общего интерфейса (к которому привелись результаты веток) — точно также обрабатывается информация о ветвлении.
Не вижу здесь принципиальной разницы с Variant.
WH>>>По тому, что нужно значение. EP>>И какой тип у этого значения? WH>Тот, к которому приводятся типы обеих веток.
И получится в общем случае что-то очень близкое к Object'у
EP>>Что в этом плохого? WH>То, что это не нужно.
Аргумент
EP>>Почему это хуже, чем вывод общей базы, WH>Хотя бы по тому что для того чтобы получить данные из этого варианта тебе нужен ещё один точно такой же if.
Во-первых, как уже говорил выше — можно дать возможность получить из Variant'а значение общего типа (то есть текущее поведение).
Во-вторых, при вызове виртуального метода — также происходит индерекция (пусть и другого рода) как и при if.
EP>>которая может быть крайне "слабым" типом вроде IPrintable, а то и вовсе Object. WH>В немерле Object не выводится. Там специальная закладка на этот счёт есть.
А с IPrintable (или подобным крайне "слабым") как быть?
То есть будут где-то иерархии пересекаться недалеко от Object'а — толку-то?
Здравствуйте, WolfHound, Вы писали:
WH>В C# нет подобных языковых средств.
Так и речь тут не про РМ, а про if. А то что if в немерле это сахар для РМ, это уже подробности реализации, которые в контексте разговора не очень интересны.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Эта ветка, кстати, отличная демонстрация того, что надо очертить некий Nemerle Base Level и долбить его детально и тщательно. Это большая работа, но ее надо делать обязательно, а не надеятся что каждый сам себе напишет свой if.
Он много лет как есть. Просто ты хочешь чтобы он был >= C#,а это не так. С when — это скорее недороботка в парсере тех времен. Но есть куда более концептуальные различия устранение которых может сделать язык хуже или другим.
Например, я на раз запомнил (прочтя в первой же статье), что нужно писать when. Ноя не раз нарывался на вот такую особенность:
when (true)
42;
0
Как императивщик, я переводил этот код на Шара следующим образом:
if (true)
return 42;
return 0;
Но это не верно, так как в Немерде выражение в конце функции не помещается в невидимый return, а вся функция рассматривается как дерево ветвления выражений. При этом незультат может быть получен только с "листьев". Таким образом, when посто теряет значение. Об этом говорит и warning, но человек из мира С# не понимает, что происходит.
Что уж говорить о мелких синтаксических фичах вроде mutable и описаниях массивов?
Тут уж нужно просто подумать и решить не проще ли сделать на потребу толпе полный супер–сет C#?
Собственно это была одна из
предпосылок для проекта Найтра. На ней создать два языка на одной базе будет парой пустяков.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Он много лет как есть. VD>С when — это скорее недороботка
Сам себе и противоречишь.
VD>Но есть куда более концептуальные различия устранение которых может сделать язык хуже или другим.
Концептуальные различия тут никто и не обсуждает. Но у вас и без них куча мелочей, которые мешают.
VD>Что уж говорить о мелких синтаксических фичах вроде mutable и описаниях массивов?
mutable как раз проблем не вызывает. А when — вызывает.
... << RSDN@Home 1.0.0 alpha 5 rev. 0 on Windows 8 6.2.9200.0>>
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>ИМХО, variant[Some[T], Nothing], или variant[T, void] это всё детали. EP>Основной поинт в том, что и для if-без-else, и для if+else, и даже для if+else+if+else+... в качестве результата можно использовать один тип результата Variant<...>
При всем моем уважении, если это и детали, то это весьма важные детали.
Ход рассуждений примерно такой:
1. if-без-else — выражение;
2. Как любое выражение, оно может быть вычислено;
3. Результат этого вычисления может быть сохранен.
Но, с другой стороны, предикат может быть вычислен в false. И тут возможны варианты:
1. Условно, F#-way. Т.н. неявный else. Else есть всегда. Когда его мы его не наблюдаем — он таки есть и вычисляется в void;
2. Условно, erlang-way. Если предикат вычислился в false, результатом вычисления будет некое if-clause исключение.
3. То, что предлагаете вы. if-без-else вычисляется в монаду maybe.
Смысл всего этого, вообще говоря, избежать неоднозначности в вычислениях.
В F#-way неоднозначности просто нет. Альтернативная ветвь вычислений есть всегда. Просто иногда она не выражена явно.
В erlang-way, цепь вычислений прерывается специальным исключением.
В вашем случае, цепь потенциально неоднозначных вычислений "замыкается" на монаду maybe. И она дает возможность понять, что что-то "go wrong".
В вашем случае, важна именно полноценная монада maybe. Паллиатив вида variant[T, void] — не даст вам возможности поймать "go wrong".
И важен тут, не столько Nothing (который, вполне может быть заменен и на void), сколько Just. Т.е. если за-предикатное выражение вычисляется в T, то "нормальный" результат вычисления if-без-else будет Just[T], а не T.
Если теперь сравнить это с if-с-else, то видно следующее. Чтобы "в качестве результата можно использовать один тип результата" — в вашем случае — его придется вычислять в Just[T], а не в T. Что, вообще говоря, смысла лишено.
Даже если "обернуть" все это в variant — это мало что изменит . Типы A и Some[A] — это таки разные типы.
Можно потытаться "стереть границы" введя функторы, например... и вот тогда про "два синтаксиса" вообще никто вспоминать уж точно не будет
G>>Так же будут "проблемы" восприятия всяческих variant[A, A]. Даже если "упрощать" их до variant[A] — ничего, кроме дополнительного раздражения вывод таких типов вызвать не может.
EP>Для обобщённого кода это удобно. EP>Для обычного — возможно да, автоматическое упрощение до A было бы удобней. Но это упрощение можно получить явным вызовом одного унарного оператора или функции, причём если считать что variant<A> всегда содержит значение типа A (то есть не Nothing), то такой вызов не будет давать никаких накладных расходов. EP>Я думаю намного более неудобно, если в ветках получаются слабосвязанные A и B, и выводится какой-то крайне слабый общий тип вроде IPrintable (как это делается сейчас) — по сути много полезной информации о типах бесследно теряется.
Попытаюсь объяснить еще раз...
Смотрите, я выше высказался за разрешение неоднозначности при вычислении if-без-else. Действительно её можно снять вычисляя его в maybe. Проблема только в том, что это никакого отношения к вычислению if-с-else не имеет
Добиться "одинаковости" можно лишь вычислением и if-с-else тоже в maybe. Что весьма странно, согласитесь. Ведь никаких неоднозначностей при его вычмслении у нас нет. И никакие variant[A, B] и т.п. не дадут вам взаимозаменяемости, если хотите, между результатами вычислений if-с-else и if-без-else.
Вычисление if-с-else в variant, само по себе (без попытки добиться "симметрии") может иметь смысл. Но только в том случае, когда PM и использование variant уйдут на ступень выше, так сказать.
Какой смысл в данный момент получать из if (который, на самом деле PM) variant? Для его обработки все равно придется использовать PM. Если же мы можем выделить общий интерфейс (т.е. при дальнейшей обработки обойтись без PM), то и возвращать из if лучше именно этот интерфейс. Что, как я понимаю, сейчас и делается.
Имхо, вычисление if-с-else в variant было бы более ценным, если бы была возможность, например, использовать механизмы PM при описании сигнатуры ф-ций. И, соответсвенно, при разрешении их вызовов. Тогда бы, можно было организовать диспетчеризацию не прибегая к явному использованию PM.
Здравствуйте, gbear, Вы писали:
G>В вашем случае, важна именно полноценная монада maybe. Паллиатив вида variant[T, void] — не даст вам возможности поймать "go wrong".
Почему же не даст? void тут играет роль Nothing.
G>И важен тут, не столько Nothing (который, вполне может быть заменен и на void),
Да, именно.
G>сколько Just. Т.е. если за-предикатное выражение вычисляется в T, то "нормальный" результат вычисления if-без-else будет Just[T], а не T.
Да, и я не вижу в этом проблемы.
G>Если теперь сравнить это с if-с-else, то видно следующее. Чтобы "в качестве результата можно использовать один тип результата" — в вашем случае — его придется вычислять в Just[T], а не в T. Что, вообще говоря, смысла лишено.
Почему же? Для обобщённого кода как раз и не лишено — вне зависимости от того, значения каких типов возвращают ветки if-else, всё выражение вычислится в Variant.
Для обычного, не обобщённого кода, да немного неудобно, но это обходится всего одним унарным оператором/унарной функцией, причём без потери производительности (Variant<T> всегда содержит внутри T, безусловно).
Тут главное не забывать, что if-с-else в общем случае возвращает Variant<T, U>, а не просто Variant<T>, который является всего лишь частным случаем.
G>Даже если "обернуть" все это в variant — это мало что изменит . Типы A и Some[A] — это таки разные типы. G>Можно потытаться "стереть границы" введя функторы, например... и вот тогда про "два синтаксиса" вообще никто вспоминать уж точно не будет
В случае когда у нас Variant<T>, полноценный функторный fmap не нужен, точнее можно без него. Достаточно T unbox(Variant<T> x);, или короткого оператора.
G>Смотрите, я выше высказался за разрешение неоднозначности при вычислении if-без-else. Действительно её можно снять вычисляя его в maybe. Проблема только в том, что это никакого отношения к вычислению if-с-else не имеет
Maybe действительно не имеет отношения, а вот Varaint — вполне.
G>Добиться "одинаковости" можно лишь вычислением и if-с-else тоже в maybe. Что весьма странно, согласитесь. Ведь никаких неоднозначностей при его вычмслении у нас нет. И никакие variant[A, B] и т.п. не дадут вам взаимозаменяемости, если хотите, между результатами вычислений if-с-else и if-без-else.
Почему же не дадут?
G>Вычисление if-с-else в variant, само по себе (без попытки добиться "симметрии") может иметь смысл. Но только в том случае, когда PM и использование variant уйдут на ступень выше, так сказать.
То есть основная проблема заключается в каких-то пробелах в Nemerle с Varaint'ом как таковым, а не в самой идеи if-else -> Variant?
G>Какой смысл в данный момент получать из if (который, на самом деле PM) variant? Для его обработки все равно придется использовать PM. Если же мы можем выделить общий интерфейс (т.е. при дальнейшей обработки обойтись без PM), то и возвращать из if лучше именно этот интерфейс. Что, как я понимаю, сейчас и делается.
Дело в том, что non-member функции также, грубо говоря, относятся к интерфейсу. Объектам необязательно иметь одинаковые-функции члены и общих предков, чтобы их можно было использовать с одинаковым синтаксисом.
Например на C++ можно вот так:
Variant<Widget, Gadget> x, y;
// ...
apply(x, y, [](auto &x, auto &y)
{
draw(x);
draw(y);
collide(x, y);
});
Здесь нет PM, и нет общего предка, хотя оба объекта имеют одинаковый интерфейс с точки зрения этой полиморфной лямбды.
G>Имхо, вычисление if-с-else в variant было бы более ценным, если бы была возможность, например, использовать механизмы PM при описании сигнатуры ф-ций. И, соответсвенно, при разрешении их вызовов. Тогда бы, можно было организовать диспетчеризацию не прибегая к явному использованию PM.
Это похоже по описанию на перегрузку функций в C++, там как раз и возможно разруливать ветви variant через перегрузку (а можно и через один шаблон функции, если интерфейс одинаков, как в примере выше).
Раз так, то до ее окончания потрудись не делать заявлений. А то не понравилось тебе одно решение с when–ом и на этом основании делается вывод что есть "куча мелочей, которые мешают".
Я, вот, немерл уже 7 лет использую и реальных косяков особо не заметил. Историю с when можно счесть за косяк, ну там в описании массивов есть непоследовательность, выбор скобок для дженериков слабостью парсера объясняется. Но в остальном все решения имеют под собой конкретные обоснования. Да даже скобки для дженириков не прихоть авторов, а выбор в пользу меньшей неоднозначности. Без Нитры ее очень не просто было бы обойти. Нитра не ограничена LL(k), а немерловый парсер ограничен. Можно ли ругать авторов за этот выбор?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>2 или три языка поддерживающие главные концепции, но имеющие разный синтаксис. VD>Залудим и немерл, и шарп++. И пусть история рассудит какой синтаесис выбрать.
Взгляд со стороны. Мне кажется, что многих людей (вероятно, не привыкших к написанию дсл) отпугнёт такое разнообразие. Может создаться впечатление, что вы распыляетесь и не можете сами определиться.
Или вот представим, что человек знает шарп, но услышал про ваш язык и заинтересовался. Изначально интерес может быть праздным — это нормально. Желательно на этом этапе увлечь. А у человека возникнет выбор — или брать "более знакомый" "шарп++" или немерле. Причём вы пишете на немерле, вот и думай, что выбирать или знакомый синтаксис или "реально используемый" диалект языка.
Если что, то это я по себе сужу. Разумеется, могу быть не обьективным. Тем не менее, на мой взгляд, лучше всё-таки сделать один немерле2.
Здравствуйте, DarkEld3r, Вы писали:
DE>Причём вы пишете на немерле, вот и думай, что выбирать или знакомый синтаксис или "реально используемый" диалект языка.
Тандему VB/C# это не очень мешает. Хотя по сути это один язык с двумя сильно разными синтаксисами.
Здравствуйте, gbear, Вы писали:
G>А как вы собираетесь это потом использовать?! Повторюсь, смысл вычисления в maybe — возможность понять что "что-то пошло не так". Использование неявного приведения Just[T] к T (которое, кстати, вполне возможно) лишает нас этой возможности. Если нет неявного приведения,
Да, в этом случае его быть не должно.
G>то "просто так" использовать результат вычисления if-без-else в тех же местах, где мы можем использовать результат вычисления if-с-else не получится. G>Вот и вся проблема.
И там и там Variant. В том что набор типов Variant'а будет отличаться при комментировании ветки else — не вижу проблемы
EP>>Почему же? Для обобщённого кода как раз и не лишено — вне зависимости от того, значения каких типов возвращают ветки if-else, всё выражение вычислится в Variant. G>Ну причем тут variant-то?! Речь идет о том, что нет смысла вычислять if-с-else в maybe.
Variant — своего рода обобщение Maybe. Вычислять if-с-else в maybe — действительно не имеет смысла.
EP>>В случае когда у нас Variant<T>, полноценный функторный fmap не нужен, точнее можно без него. Достаточно T unbox(Variant<T> x);, или короткого оператора. G>По-ходу, как-то я не ясно выразился. Я вам про одно — вычисление if-без-else в maybe, а вы мне про другое — вычисление if-c-else в variant G>Ладно, поговорим за variant... G>А зачем вам какой-то явный unbox для variant? Вполне можно обойтись и неявным приведением к.
А где именно оно будет происходить? if-else будет сразу иметь этот тип? Или при присвоении к значению типа T?
G>Смотрите... вот получили мы какой-то variant[string, int, ?DateTime], условно. Пусть даже у него есть какой-то unbox.
А для него не будет unbox'а — unbox это только для variant[T], то есть для тех случаев, когда обе ветки if-else имеют одинаковый тип результата, и variant внутри имеет значение только одного определённого типа.
G>Нормально обработать его можно только через PM. Вы же, надеюсь, не собираетесь прогонять его через очередной if? Об этом и речь.
Можно и через if, а можно и спрятать этот if/условный переход в библиотеку, так как это сделано в boost::variant.
Обычная работа с variant'ом. В чём проблема-то?
G>>>Вычисление if-с-else в variant, само по себе (без попытки добиться "симметрии") может иметь смысл. Но только в том случае, когда PM и использование variant уйдут на ступень выше, так сказать. EP>>То есть основная проблема заключается в каких-то пробелах в Nemerle с Varaint'ом как таковым, а не в самой идеи if-else -> Variant? G>Nemerle тут вообще не причем Недостаточно вычислить if в variant[A, B, C]. Нужно еще как-то "узнать", что конкретно этот variant сейчас в себе несет... A, B или C. И его нужно будет либо дополнительно проверять...
Точно также как и в случае с виртуальным вызовом метода результата
G>Но просто так (без PM) использовать не получится. Хоть в Nemerle, хоть еще где.
Во-первых, в Nemerle же есть PM — в чём проблема?
Во-вторых — я же приводил уже примеры использования variant'а в C++ — нужен конкретный код?
AVK>Вас на один язык не хватает. Про то что нужно компилятор переписывать я с самого начала твоего увлечения Немерле слышу. А тут ещ 2 языка...
Это почти ничего не будет стоить. Основные усилия идут на Нитру.
AVK>Вобщем, вангую еще раз — через пять лет активно что то писать на Немерле будет не больше пары десятков человек.
Ну, уже лучше чем прошлое вангование про "сдохнет".
Но ты сам себе противоречишь. То говоришь о проблеме переучивания, то говоришь, что делать суперсет шарпа нецелесообразно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, DarkEld3r, Вы писали:
DE>Может создаться впечатление, что вы распыляетесь и не можете сами определиться.
А мы и не можем. За немерловый синаксис голосует душа и опыт. За шарповский — расчет.
DE>Или вот представим, что человек знает шарп, но услышал про ваш язык и заинтересовался. Изначально интерес может быть праздным — это нормально. Желательно на этом этапе увлечь. А у человека возникнет выбор — или брать "более знакомый" "шарп++" или немерле. Причём вы пишете на немерле, вот и думай, что выбирать или знакомый синтаксис или "реально используемый" диалект языка.
Ну, дык если его подвигнеь на знакомство знакомость базы — это и будет то что нужно нам.
Тут надо понимать, что мы не будем делать два разных мира. Это будут два разных синтаксиса для одной модели (т.е., по сути, одного язака). Оба синтаксиса можно будет использовать в рамках одного проекта. Более того можно будет и обычный шарп в такой проект включать.
DE>Если что, то это я по себе сужу. Разумеется, могу быть не обьективным. Тем не менее, на мой взгляд, лучше всё-таки сделать один немерле2.
Пока что расклад таков — ты думаешь, оцениваешь, но не используешь/изучаешь Немерл. Стало быть, если знакомость базы тебя сподвигнет присоедениться к комьюнити (без маркетингового обмана), то это и будет разрывам замкнутого круга.
А далее ты сможешь освоить сначала "++", а потом и более горманичный синтаксис. Ну, а не освоишь — тоже не беда. Ты станешь частью комьюнити и сможешь приносить ему пользу создавая новые DSL–и или внося вклад в развитие проекта.
Ну, а если в итоге окажется, что синтаксис немерла окажется не востребованным, значит это грусный парадокс с которым придется смириться.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>И там и там Variant. В том что набор типов Variant'а будет отличаться при комментировании ветки else — не вижу проблемы
Еще раз. Variant — _не_ maybe. Даже близко не maybe. Даже не аналог. Конкретная реализация maybe вполне _может_ использовать variant в качестве "кишок" — это да. Но, только и всего.
EP>Variant — своего рода обобщение Maybe. Вычислять if-с-else в maybe — действительно не имеет смысла.
Уф. Сдается мне, у вас какое-то своеобразное представление о maybe. Давайте на пальцах...
Представим себе, что у нас есть у нас ф-ция f(int) -> maybe[int]. В какой, по вашему мнению, тип должен быть вычислен if(x > 0) f(x)?
Мне, вот, очевидно — в тот же самый maybe[int]. Надеюсь, что и вам тоже. И если это так, то объясните мне, как вы собираетесь провернуть такой "фокус" на "читых" variant'ах?
G>>А зачем вам какой-то явный unbox для variant? Вполне можно обойтись и неявным приведением к. EP>А где именно оно будет происходить? if-else будет сразу иметь этот тип? Или при присвоении к значению типа T?
Почему только при присвоении?! Везде, где "по смыслу" должно быть T.
EP>А для него не будет unbox'а — unbox это только для variant[T], то есть для тех случаев, когда обе ветки if-else имеют одинаковый тип результата, и variant внутри имеет значение только одного определённого типа.
Вот уж в этом-то случае, явный unbox смысла не имеет вообще.
G>>Нормально обработать его можно только через PM. Вы же, надеюсь, не собираетесь прогонять его через очередной if? Об этом и речь. EP>Можно и через if, а можно и спрятать этот if/условный переход в библиотеку, так как это сделано в boost::variant. EP>Обычная работа с variant'ом. В чём проблема-то?
Уж вот я не знаю как для вас. А для меня то, что я увидел по вашей сслыке на boost — является всем чем угодно, но не "обычной работой с variant'ом".
В нашем случае использовать if не получится... конечно, если вы не предлагаете "нафантазировать" для этого случая еще один — специальный — "if". Ну, который не будет вычисляться в variant .
А вариант с двойной диспетчеризацией — еще хуже. Надеюсь, вы сами это понимаете.
G>>Nemerle тут вообще не причем Недостаточно вычислить if в variant[A, B, C]. Нужно еще как-то "узнать", что конкретно этот variant сейчас в себе несет... A, B или C. И его нужно будет либо дополнительно проверять... EP>Точно также как и в случае с виртуальным вызовом метода результата
?! Вы вообще о чем? Как определить этот "метод"? Смысл использования variant'а — по вашей же логике — "впихнуть невпихуемое". Если мы подразумеваем общий интерфейс, то зачем variant-то?!
G>>Но просто так (без PM) использовать не получится. Хоть в Nemerle, хоть еще где. EP>Во-первых, в Nemerle же есть PM — в чём проблема?
Проблему можно выразить так: Если результат if можно обработать только через PM, то зачем нам использовать if? Почему сразу не использовать PM?!
Напоминаю, в сабже if — это "сахар" для PM.
EP>Во-вторых — я же приводил уже примеры использования variant'а в C++ — нужен конкретный код?
Не нужен. Яж вам говорю... чудес не бывает
Одно дело, какой-нибудь условный:
match (if (x > 0) getA() else getB())
{
| y is A => handleA(y.PropertyOfA)
| y is B => handleB(y.MethodOfB())
}
А другое — предлагаемые вами "танцы на граблях" (с)
Здравствуйте, VladD2, Вы писали:
VD>Пока что расклад таков — ты думаешь, оцениваешь, но не используешь/изучаешь Немерл. Стало быть, если знакомость базы тебя сподвигнет присоедениться к комьюнити (без маркетингового обмана), то это и будет разрывам замкнутого круга.
Не использую/изучаю по несколько другим причинам — мне "нужен" (действительно нужен или это "предрассудки" — не важно) нативный язык. Поэтому изучаю Pаст.
По моему, освоить непривычный синтаксис не особо сложно. Скажем, после сишного опыта с лиспом разобраться вполне удалось. Было бы желание. Более того, непривычность может подталкивать писать "правильно". В этом плане мне нравится Nemerle.Imperative — при желании можно "как раньше", но по умолчанию предполагается использовать функциональный стиль.
Изучал Racket когда-то из интереса. Подкупало то, что у них есть типизированный диалект. Казалось бы, всё здорово, но постоянно вылезали грабли, что разные возможности в typed racket не поддерживаются. В итоге забил именно поэтому.
Конечно, может просто проецирую свой негативный опыт и у вас таких проблем не возникнет...
Здравствуйте, kekekeks, Вы писали:
K>Тандему VB/C# это не очень мешает. Хотя по сути это один язык с двумя сильно разными синтаксисами.
Возможно, я не прав, но никогда VB серьёзно не воспринимал. Tак понимаю, что некая база пользователей языка была, ну и её попытались сохранить перетащив язык на дотнет. Мне кажется, что это весьма консервативные люди, которые так и продолжат писать "на ВБ".
Здравствуйте, gbear, Вы писали:
G>Представим себе, что у нас есть у нас ф-ция f(int) -> maybe[int]. В какой, по вашему мнению, тип должен быть вычислен if(x > 0) f(x)? G>Мне, вот, очевидно — в тот же самый maybe[int]. Надеюсь, что и вам тоже. И если это так, то объясните мне, как вы собираетесь провернуть такой "фокус" на "читых" variant'ах?
Явно или автоматически: Variant<Variant<T, void>, void> -> Variant<T, void>
G>>>А зачем вам какой-то явный unbox для variant? Вполне можно обойтись и неявным приведением к. EP>>А где именно оно будет происходить? if-else будет сразу иметь этот тип? Или при присвоении к значению типа T? G>Почему только при присвоении?! Везде, где "по смыслу" должно быть T.
Вот тут:
auto foo()
{
return (if(c) T{1} else T{2});
}
какой должен быть тип результата у foo?
EP>>А для него не будет unbox'а — unbox это только для variant[T], то есть для тех случаев, когда обе ветки if-else имеют одинаковый тип результата, и variant внутри имеет значение только одного определённого типа. G>Вот уж в этом-то случае, явный unbox смысла не имеет вообще.
Ещё раз (третий?), речь об обобщённом коде:
template<typename T, typename U>
void bar()
{
auto res = if(c) T{} else U{};
// use res as variant
}
Если делать здесь неявное преобразование variant[X] в X (в случае когда T == U), то этот код поломается, либо ему придётся вставлять дополнительные проверки.
G>В нашем случае использовать if не получится...
Почему?
G>конечно, если вы не предлагаете "нафантазировать" для этого случая еще один — специальный — "if". Ну, который не будет вычисляться в variant .
Зачем?
G>А вариант с двойной диспетчеризацией — еще хуже. Надеюсь, вы сами это понимаете.
Откуда двойная диспетчеризация-то?
G>>>Nemerle тут вообще не причем Недостаточно вычислить if в variant[A, B, C]. Нужно еще как-то "узнать", что конкретно этот variant сейчас в себе несет... A, B или C. И его нужно будет либо дополнительно проверять... EP>>Точно также как и в случае с виртуальным вызовом метода результата G>?! Вы вообще о чем? Как определить этот "метод"? Смысл использования variant'а — по вашей же логике — "впихнуть невпихуемое". Если мы подразумеваем общий интерфейс, то зачем variant-то?!
Я уже приводил пример, придётся скопировать:
Variant<Widget, Gadget> x, y;
// ...
apply(x, y, [](auto &x, auto &y)
{
draw(x);
draw(y);
collide(x, y);
});
Здесь нет общего предка у Widget и Gadget (да и вообще у них нет ни одного метода), а интерфейс использования в данном случае у них одинаковый
G>>>Но просто так (без PM) использовать не получится. Хоть в Nemerle, хоть еще где. EP>>Во-первых, в Nemerle же есть PM — в чём проблема? G>Проблему можно выразить так: Если результат if можно обработать только через PM, то зачем нам использовать if? Почему сразу не использовать PM?!
Во-первых, не только через PM.
Во-вторых, претензия не понятна — в коде удобен if, получили из него variant, передали куда-то дальше, там обработали хоть PM'ом хоть ещё чем. В чём проблема-то?
G>Напоминаю, в сабже if — это "сахар" для PM.
И что? Это мешает построить if возвращающий variant?
EP>>Во-вторых — я же приводил уже примеры использования variant'а в C++ — нужен конкретный код? G>Не нужен. Яж вам говорю... чудес не бывает G>Одно дело, какой-нибудь условный:
G>
G>match (if (x > 0) getA() else getB())
G>{
G> | y is A => handleA(y.PropertyOfA)
G> | y is B => handleB(y.MethodOfB())
G>}
G>
G>А другое — предлагаемые вами "танцы на граблях" (с)
Чем код выше, принципиально отличается от следующего:
MATCH( IF(x>0) { return getA(); } ELSE { return getB(); } )
CASE(y IS A) { handleA(y.PropertyOfA); }
CASE(y IS B) { handleB(y.MethodOfB()); }
;
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Явно или автоматически: Variant<Variant<T, void>, void> -> Variant<T, void>
А явно это как? Особенно интересно, как это "явно" будет выглядеть в обобщенном коде.
А если автоматически, то получается тип Variant<Variant<T, void>, void> существовать не может? Я вас правильно понимаю?
G>>Почему только при присвоении?! Везде, где "по смыслу" должно быть T.
EP>Вот тут: EP>
Очевидно — variant.
EP>Ещё раз (третий?), речь об обобщённом коде: EP>
EP>template<typename T, typename U>
EP>void bar()
EP>{
EP> auto res = if(c) T{} else U{};
EP> // use res as variant
EP>}
EP>
EP>Если делать здесь неявное преобразование variant[X] в X (в случае когда T == U), то этот код поломается, либо ему придётся вставлять дополнительные проверки.
Зачем здесь-то делать? Хотите работать с res как c variant'ом — ктож вам запретит Но если у вас есть, например, какая-нибудь ф-ция от X, то, согласитесь, удобней "уметь" в неё передавать variant[X] "as is", а не дергать какие-то unbox'ы.
G>>В нашем случае использовать if не получится... EP>Почему?
?! Очевидно, потому что if вернет нам новый variant. С которым "нужно что-то делать"
G>>конечно, если вы не предлагаете "нафантазировать" для этого случая еще один — специальный — "if". Ну, который не будет вычисляться в variant .
EP>Зачем?
?! Вы серьезно не понимаете? ?! Очевидно, потому что "нормальный" if вернет нам новый variant. С которым "нужно что-то делать"
G>>А вариант с двойной диспетчеризацией — еще хуже. Надеюсь, вы сами это понимаете.
EP>Откуда двойная диспетчеризация-то?
boost::variant< int, std::string > v;
...
class times_two_visitor
: public boost::static_visitor<>
{
public:
void operator()(int & i) const
{
i *= 2;
}
void operator()(std::string & str) const
{
str += str;
}
};
...
boost::apply_visitor( times_two_visitor(), v );
Это пример из boost, в который вы мне тыкали, как в пример. И таки да... это называется, двойная диспетчеризация.
G>>?! Вы вообще о чем? Как определить этот "метод"? Смысл использования variant'а — по вашей же логике — "впихнуть невпихуемое". Если мы подразумеваем общий интерфейс, то зачем variant-то?! EP>Я уже приводил пример, придётся скопировать: EP>
EP>Здесь нет общего предка у Widget и Gadget (да и вообще у них нет ни одного метода), а интерфейс использования в данном случае у них одинаковый
Я где-то говорил за "общего предка"?! Процитирую себя
Если мы подразумеваем общий интерфейс, то зачем variant-то?!
Что мешает-то на лету "собрать" нужный тип (в вашем примере, это агрегат Widget и Gadget), реализующий _нужный_ интерфейс?!
G>>Проблему можно выразить так: Если результат if можно обработать только через PM, то зачем нам использовать if? Почему сразу не использовать PM?! EP>Во-первых, не только через PM.
Возвращаемся к нашим баранам. Таки через visitor'ы предлагаете? Повторюсь — обработка через if даст вам на выходе опять variant. Т.е. получаем "один раз variant — всегда variant". Оно действительно надо?
EP>Во-вторых, претензия не понятна — в коде удобен if, получили из него variant, передали куда-то дальше, там обработали хоть PM'ом хоть ещё чем. В чём проблема-то?
"Хоть PM'ом" — означает "только PM'ом".
G>>Напоминаю, в сабже if — это "сахар" для PM. EP>И что? Это мешает построить if возвращающий variant?
Да ничего не мешает. Вопрос не в этом. Вопрос в том, чего вы этим хотите добиться? Пресловутой "симметрии" с if-без-else, как вроде бы выяснили, добиться таким образом не получится. Так в чем смысл-то?
EP>>>Во-вторых — я же приводил уже примеры использования variant'а в C++ — нужен конкретный код? G>>Не нужен. Яж вам говорю... чудес не бывает G>>Одно дело, какой-нибудь условный:
G>>
G>>match (if (x > 0) getA() else getB())
G>>{
G>> | y is A => handleA(y.PropertyOfA)
G>> | y is B => handleB(y.MethodOfB())
G>>}
G>>
G>>А другое — предлагаемые вами "танцы на граблях" (с)
EP>Чем код выше, принципиально отличается от следующего: EP>
EP>MATCH( IF(x>0) { return getA(); } ELSE { return getB(); } )
EP> CASE(y IS A) { handleA(y.PropertyOfA); }
EP> CASE(y IS B) { handleB(y.MethodOfB()); }
EP>;
EP>
EP>
Принципиально — ничем. Только какое он имеет отношение к?
Здравствуйте, gbear, Вы писали:
EP>>Явно или автоматически: Variant<Variant<T, void>, void> -> Variant<T, void> G>А явно это как? Особенно интересно, как это "явно" будет выглядеть в обобщенном коде.
Например simplify(v) или какой-нибудь унарный оператор ^^ v
G>А если автоматически, то получается тип Variant<Variant<T, void>, void> существовать не может? Я вас правильно понимаю?
Хм, наверное да. Точнее существовать может, но до первого использования.
G>>>Почему только при присвоении?! Везде, где "по смыслу" должно быть T. EP>>Вот тут: EP>>
EP>>какой должен быть тип результата у foo? G>Очевидно — variant.
Variant[T]? А как же:
G>>>Почему только при присвоении?! Везде, где "по смыслу" должно быть T.
Что имелось в виду? Протягивать Variant[T] до T? А если пользователь просто захочет позвать метод T?
G>Хотите работать с res как c variant'ом — ктож вам запретит Но если у вас есть, например, какая-нибудь ф-ция от X, то, согласитесь, удобней "уметь" в неё передавать variant[X] "as is", а не дергать какие-то unbox'ы.
Можно и так — автоматически/неявно.
G>>>В нашем случае использовать if не получится... EP>>Почему? G>?! Очевидно, потому что if вернет нам новый variant. С которым "нужно что-то делать"
Когда из if возвращается указатель на какую-нибудь абстрактную базу, при работе с ней придётся точно также выяснять — что же там внутри, и какой именно метод вызывать. Выбор-то никуда не исчезает
G>>>конечно, если вы не предлагаете "нафантазировать" для этого случая еще один — специальный — "if". Ну, который не будет вычисляться в variant . EP>>Зачем? G>?! Вы серьезно не понимаете? ?! Очевидно, потому что "нормальный" if вернет нам новый variant. С которым "нужно что-то делать"
А сейчас возвращается общая база, с которой тоже "нужно что-то делать".
G>Это пример из boost, в который вы мне тыкали, как в пример. И таки да... это называется, двойная диспетчеризация.
Там одинарная диспетчеризация, а не двойная
G>>>?! Вы вообще о чем? Как определить этот "метод"? Смысл использования variant'а — по вашей же логике — "впихнуть невпихуемое". Если мы подразумеваем общий интерфейс, то зачем variant-то?! EP>>Я уже приводил пример, придётся скопировать: EP>>
EP>>...
EP>>
EP>>Здесь нет общего предка у Widget и Gadget (да и вообще у них нет ни одного метода), а интерфейс использования в данном случае у них одинаковый G>Я где-то говорил за "общего предка"?! Процитирую себя
Если мы подразумеваем общий интерфейс, то зачем variant-то?!
Тогда покажите как этот пример (где у разных типов общий интерфейс) переделать без variant'а и без общего предка.
G>Что мешает-то на лету "собрать" нужный тип (в вашем примере, это агрегат Widget и Gadget), реализующий _нужный_ интерфейс?!
Пример сборки нужного типа в студию. Мне кажется это будет сложнее чем variant.
G>>>Проблему можно выразить так: Если результат if можно обработать только через PM, то зачем нам использовать if? Почему сразу не использовать PM?! EP>>Во-первых, не только через PM. G>Возвращаемся к нашим баранам. Таки через visitor'ы предлагаете?
Один из вариантов. Вот только visitor может иметь один полиморфный метод, а может много перегрузок — а-ля PM, а может и то и то.
G>Повторюсь — обработка через if даст вам на выходе опять variant. Т.е. получаем "один раз variant — всегда variant". Оно действительно надо?
С общим предком тоже самое. Если же обработать всё что относится к этой ветке, внутри неё, и наружу никогда не выдавать полиморфный тип (будь то variant или абстрактная база), то мы очень быстро получим комбинаторный взрыв.
G>>>Напоминаю, в сабже if — это "сахар" для PM. EP>>И что? Это мешает построить if возвращающий variant? G>Да ничего не мешает. Вопрос не в этом. Вопрос в том, чего вы этим хотите добиться? Пресловутой "симметрии" с if-без-else, как вроде бы выяснили, добиться таким образом не получится. Так в чем смысл-то?
Как это не получится, если выяснили что как раз получается
EP>>Чем код выше, принципиально отличается от следующего: EP>>
EP>>MATCH( IF(x>0) { return getA(); } ELSE { return getB(); } )
EP>> CASE(y IS A) { handleA(y.PropertyOfA); }
EP>> CASE(y IS B) { handleB(y.MethodOfB()); }
EP>>;
EP>>
EP>> G>Принципиально — ничем. Только какое он имеет отношение к?
Тем что такой синтаксис можно получить на базе boost::variant
Здравствуйте, Evgeny.Panasyuk, Вы писали:
G>>А явно это как? Особенно интересно, как это "явно" будет выглядеть в обобщенном коде. EP>Например simplify(v) или какой-нибудь унарный оператор ^^ v
Да я не про это. Кто будет "дергать за веревочку"? И главное — как он будет определять нужно ли "дергать"?
Есть foo(x) -> void и bar(x) -> variant[int, void].
if(x > 0) bar(x) else foo(x)
Нужно "дергать"?
if(x > 0) bar(x)
А теперь?
if(x > 0)
if(x > 10) x else foo(x)
А если так?
G>>А если автоматически, то получается тип Variant<Variant<T, void>, void> существовать не может? Я вас правильно понимаю? EP>Хм, наверное да. Точнее существовать может, но до первого использования.
Плохо это. Потеряем возможность поймать "go wrong". Variant<Variant<T, void>, void> вполне может означать — что все ок... ну просто, например, вложеный if-c-else вычислился в void.
Привели к Variant<T, void> — и если "внутрях у неё" void — будем думать, что до вложенного if дело вообще не дошло.
Вывод, нужен таки maybe, а не суррогаты
G>>>>В нашем случае использовать if не получится... EP>>>Почему? G>>?! Очевидно, потому что if вернет нам новый variant. С которым "нужно что-то делать" EP>Когда из if возвращается указатель на какую-нибудь абстрактную базу, при работе с ней придётся точно также выяснять — что же там внутри, и какой именно метод вызывать. Выбор-то никуда не исчезает
def a = if(x > 0) "a"else 1; // "абстракная база" в виде какого-нибудь IConvertable def b = if(a is string) stringToX(a) else intToX(a); // Вполне конкретный X
В вашем случае: b — это в лучшем случае variant[X]. Что дальше? Можно и по сложней примеры нарисовать — например, когда "база" таки нужна. Но, в вашем варианте получим очередной variant[T, U] .
G>>Это пример из boost, в который вы мне тыкали, как в пример. И таки да... это называется, двойная диспетчеризация.
EP>Там одинарная диспетчеризация, а не двойная
Понимаете, то, что в данном случае диспетчеризация — скорее всего... этожплюсы, в конце концов — статическая (не виртуальная, если хотите) — ничего не меняет.
G>>Что мешает-то на лету "собрать" нужный тип (в вашем примере, это агрегат Widget и Gadget), реализующий _нужный_ интерфейс?! EP>Пример сборки нужного типа в студию. Мне кажется это будет сложнее чем variant.
А в чем проблема? Я такой тип вам и в C# соберу "налету" — только в run-time. В чем проблемы то?! Нам же нужен только агригат (контейней, если хотите), который делегирует вызовы конкретного интерфейса, конкретному экземпляру.
В Nemerle — такой же фокус можно провернуть в compile-time. А сложность тут вообще не причем... этот код пишется ровно один раз.
EP>Тем что такой синтаксис можно получить на базе boost::variant
Синтаксис-то тут причем?! Скажите лучше во что это вычислится? В variant?
Здравствуйте, gbear, Вы писали:
G>>>А явно это как? Особенно интересно, как это "явно" будет выглядеть в обобщенном коде. EP>>Например simplify(v) или какой-нибудь унарный оператор ^^ v G>Да я не про это. Кто будет "дергать за веревочку"?
Программист вестимо, а кто ещё если это "явное" упрощение?
G>И главное — как он будет определять нужно ли "дергать"?
Самому или с помощью компилятора. Например сейчас же компилятор ругается на вывод Object'а?
G>Есть foo(x) -> void и bar(x) -> variant[int, void]. G>
G>if(x > 0) bar(x) else foo(x)
G>
G>Нужно "дергать"?
Если неявного упрощения нет — то да, можно.
G>
G>if(x > 0) bar(x)
G>
G>А теперь?
И здесь можно.
G>
G>if(x > 0)
G> if(x > 10) x else foo(x)
G>
G>А если так?
И здесь.
G>>>А если автоматически, то получается тип Variant<Variant<T, void>, void> существовать не может? Я вас правильно понимаю? EP>>Хм, наверное да. Точнее существовать может, но до первого использования. G>Плохо это. Потеряем возможность поймать "go wrong". Variant<Variant<T, void>, void> вполне может означать — что все ок... ну просто, например, вложеный if-c-else вычислился в void.
При неявном упрощении, поймать "go wrong" можно во внутренней вложенности.
G>Вывод, нужен таки maybe, а не суррогаты
Вывод: необходимо определится нужно ли неявное упрощение или нет. Как и всегда, у explicit и implicit есть свои плюс и минусы.
При этом у нас всегда есть возможность вернуться к текущему поведению Nemerle позвав специальную функцию/оператор.
G>
G>def a = if(x > 0) "a"else 1; // "абстракная база" в виде какого-нибудь IConvertable
G>def b = if(a is string) stringToX(a) else intToX(a); // Вполне конкретный X
G>
G>В вашем случае: b — это в лучшем случае variant[X]. Что дальше?
При отсутствии неявного упрощения — simplifiy(b)
G>Можно и по сложней примеры нарисовать — например, когда "база" таки нужна. Но, в вашем варианте получим очередной variant[T, U] .
А в этом случае deduce_base(b)
Я уже неоднократно говорил, что вернуться от variant'а к текущему поведению всегда возможно через спец вызов, а вот наоборот уже нет.
G>Понимаете, то, что в данном случае диспетчеризация — скорее всего... этожплюсы, в конце концов — статическая (не виртуальная, если хотите) — ничего не меняет.
Там, по сути, внутри union + tag, по тэгу делается один switch, в ветках case делается cast к текущему типу и вызов visitor'а.
Если хотите называть простой вызов конкретной функции, у объекта конкретного типа, где не делается никакой выбор, даже compile time (хотя бы что-то типа category dispatch как в std::distance) dispatch'ем — то тогда вам придётся и все методы типа std::vector<T>::push_back называть single dispatch'ем.
Да, бывает compile time диспетчеризация, как в std::distance, перегрузках, специализациях и т.п. Но тут-то ничего этого нет.
G>>>Что мешает-то на лету "собрать" нужный тип (в вашем примере, это агрегат Widget и Gadget), реализующий _нужный_ интерфейс?! EP>>Пример сборки нужного типа в студию. Мне кажется это будет сложнее чем variant. G>А в чем проблема? Я такой тип вам и в C# соберу "налету" — только в run-time. В чем проблемы то?! Нам же нужен только агригат (контейней, если хотите), который делегирует вызовы конкретного интерфейса, конкретному экземпляру.
Проблема в том, что вместо использования уже готовых возможностей:
Variant<Widget, Gadget> x, y;
// ...
apply(x, y, [](auto &x, auto &y)
{
draw(x);
draw(y);
collide(x, y);
});
вы собрались собирать какой-то тип, непонятно как, и непонятно зачем.
В коде выше apply выбирает выводит конкретные типы для x и y однократно, на основе их тэгов, и внутри лямбды нет никаких проверок тэгов. В том варианте который предлагаете вы, как я понял — на каждом вызове типа draw будет что-то типа virtual call.
G>В Nemerle — такой же фокус можно провернуть в compile-time. А сложность тут вообще не причем... этот код пишется ровно один раз.
Ок, допустим один раз. Что будет сгенерировано в качестве агрегата для Variant<Widget, Gadget> из примера выше? Какой интерфейс у этого агрегата?
EP>>Тем что такой синтаксис можно получить на базе boost::variant G>Синтаксис-то тут причем?! Скажите лучше во что это вычислится? В variant?
Да, в чём проблема-то? В общую базу это нормально, а в variant — нет? Почему?
В следующем примере MATCH вычисляется в variant: LIVE DEMO
#include <iterator>
#include <vector>
#include"match.hpp"struct widget {};
struct gadget {};
void draw(widget) { println("drawing widget"); }
void draw(gadget) { println("drawing gadget"); }
int main()
{
using namespace std;
bool flags[] = {true, false, true, true, false};
vector<variant<widget, gadget>> xs;
for(auto f : flags)
if(f)
xs.push_back(widget{});
else
xs.push_back(gadget{});
for(auto x : xs)
MATCH(x)
CASE(x IS _) { draw(x); }
;
println();
vector<variant<int, double>> ys;
for(auto x : xs)
ys.push_back
(
MATCH(x)
CASE(x IS widget) { println("x IS widget"); return 1; }
CASE(x IS gadget) { println("x IS gadget"); return 0.1; }
);
println();
for(auto y : ys)
MATCH(y)
CASE(y IS int) { println("y IS int"); }
CASE(y IS double) { println("y IS double"); }
;
}
Вывод:
drawing widget
drawing gadget
drawing widget
drawing widget
drawing gadget
x IS widget
x IS gadget
x IS widget
x IS widget
x IS gadget
y IS int
y IS double
y IS int
y IS int
y IS double
Здравствуйте, gbear, Вы писали: G>Смотрите, я выше высказался за разрешение неоднозначности при вычислении if-без-else. Действительно её можно снять вычисляя его в maybe. Проблема только в том, что это никакого отношения к вычислению if-с-else не имеет G>Добиться "одинаковости" можно лишь вычислением и if-с-else тоже в maybe. Что весьма странно, согласитесь. Ведь никаких неоднозначностей при его вычмслении у нас нет. И никакие variant[A, B] и т.п. не дадут вам взаимозаменяемости, если хотите, между результатами вычислений if-с-else и if-без-else.
А зачем вы обязательно хотите одинаковости в вычислениях if-с-else и if-без-else?
На мой взгляд, это два разных оператора, которые вполне имеют право возвращать разные результаты.
Можно вообще считать else шорткатом для оператора ??.
То есть результат выражения if (x == 5) 42 имеет тип Nullable<int>.
Результат выражения if (x == 5) 42 else 41 имеет тот же тип, что и someNullableInt ?? 41, т.е. просто int.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, gbear, Вы писали:
G>Почему только при присвоении?! Везде, где "по смыслу" должно быть T.
Везде, где по смыслу должно быть T, должен быть лифтинг.
Монада — она на то и монада.
Вот был у нас код (простите за шарп-синтаксис — других не умею) типа
int Test()
{
var a = 6; // intvar b = 7; // intvar c = a * b; // int(42);return c;
}
Мы его улучшили:
int Test(bool b)
{
var a = if(b) 6; // int?var b = 7; // intvar c = a * b; // int? - лифтинг!return c; // compile-time error
}
Теперь у программиста есть на выбор несколько способов починить функцию:
а)
int Test(bool b)
{
var a = if(b) 6 else 0; // intvar b = 7; // intvar c = a * b; // int return c; // tadamm!
}
b)
int? Test(bool b)
{
var a = if(b) 6; // int?var b = 7; // intvar c = a * b; // int? - лифтинг!return c; // tadamm!
}
c)
int Test(bool b)
{
var a = if(b) 6; // int?var b = 7; // intvar c = a * b; // int? - лифтинг!return c ?? 0; // disambiguation
}
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
G>>Почему только при присвоении?! Везде, где "по смыслу" должно быть T. S>Везде, где по смыслу должно быть T, должен быть лифтинг. S>Монада — она на то и монада.
Если у нас массив int? и мы вычисляем сумму всех элементов "обычным for", от первого до последнего элемента, то сколько будет всего проверок на null если у нас уже первый элемент окажется null?
Если N проверок — то здесь нет ничего монадического, это только аппликативный функтор. При полноценном использовании монады — была бы только одна проверка.
S>Вот был у нас код (простите за шарп-синтаксис — других не умею) типа S> ... S>
S>int Test(bool b)
S>{
S> var a = if(b) 6; // int?
S> var b = 7; // int
S> var c = a * b; // int? - лифтинг!
S> return c ?? 0; // disambiguation
S>}
S>
Здравствуйте, Sinclair, Вы писали:
S>А зачем вы обязательно хотите одинаковости в вычислениях if-с-else и if-без-else? S>На мой взгляд, это два разных оператора, которые вполне имеют право возвращать разные результаты.
Если и там и там использовать variant — то получается достаточно симметрично, и каких-то проблем я не вижу (при необходимости, текущее поведение получается явным вызовом одного унарного оператора). Причём if-без-else может иметь отдельный синтаксис а-ля when, для избежания dangling else.
S>Можно вообще считать else шорткатом для оператора ??. S>То есть результат выражения if (x == 5) 42 имеет тип Nullable<int>. S>Результат выражения if (x == 5) 42 else 41 имеет тот же тип, что и someNullableInt ?? 41, т.е. просто int.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Если и там и там использовать variant — то получается достаточно симметрично, и каких-то проблем я не вижу (при необходимости, текущее поведение получается явным вызовом одного унарного оператора).
Основная проблема — это накопление вложенных variant<variant<x, y>, x, y>.
Чтобы этого не происходило, нужно уметь сворачивать варианты, чтобы попытка получить variant<variant<x, y>, z> автоматически приводилась к variant<x, y, z>.
Тогда, как частный случай, мы бы получили в результатеif(BooleanFunc()) NullableIntFunc(); не variant<variant<int, void>, void>, а просто variant<int, void>, он же option<int>.
EP>Причём if-без-else может иметь отдельный синтаксис а-ля when, для избежания dangling else.
Синтаксис — штука интересная. Имея, скажем, оператор ??, можно вполне обойтись и без else, и без тернарного оператора. Если не настаивать на симметрии:
Здравствуйте, Evgeny.Panasyuk, Вы писали:
G>>Да я не про это. Кто будет "дергать за веревочку"? EP>Программист вестимо, а кто ещё если это "явное" упрощение? G>>И главное — как он будет определять нужно ли "дергать"? EP>Самому или с помощью компилятора. Например сейчас же компилятор ругается на вывод Object'а?
Вот, при вычислении мы получили некий variant[int, void]. Вы, только честно, способны сказать _что_ мы получили? Ваше типо-maybe? Или, может быть, это "нормальный" variant?
Вы предлагаете интерпритировать _любой_ variant[T, void] как maybe чтоли?! А если T — внезапно — окажется void? if(x > 0) foo(x) — это тогда "go wrong" — без вариантов
А что делать в "обобщенном коде", когда любой variant[T, U], при таком подходе, способен внезапно стать таким типо-maybe?
Еще раз... maybe — вещь вполне конкретная. Обладает вполне определенной семантикой. variant — такой семантической нагрузки не несет. И "усилием воли" её на него "повесить" не выйдет.
G>>Есть foo(x) -> void и bar(x) -> variant[int, void]. G>>
G>>if(x > 0) bar(x) else foo(x)
G>>
G>>Нужно "дергать"?
EP>Если неявного упрощения нет — то да, можно.
Кхм... я дико извиняюсь. Но я таки спрашивал вас не за "можно", а за "нужно".
Здравствуйте, Sinclair, Вы писали:
EP>>Если и там и там использовать variant — то получается достаточно симметрично, и каких-то проблем я не вижу (при необходимости, текущее поведение получается явным вызовом одного унарного оператора). S>Основная проблема — это накопление вложенных variant<variant<x, y>, x, y>. S>Чтобы этого не происходило, нужно уметь сворачивать варианты, чтобы попытка получить variant<variant<x, y>, z> автоматически приводилась к variant<x, y, z>. S>Тогда, как частный случай, мы бы получили в результатеif(BooleanFunc()) NullableIntFunc(); не variant<variant<int, void>, void>, а просто variant<int, void>, он же option<int>.
Опять же, это можно делать одним явным вызовов унарного оператора, который по-максимуму упростит тип.
Основной вопрос в том, сколько неявности можно себе позволить, не сломав при этом обобщённый код (в который могут прийти и типы приводящие к variant'у который можно упростить и те для которых его упростить нельзя).
S>>>Результат выражения if (x == 5) 42 else 41 имеет тот же тип, что и someNullableInt ?? 41, т.е. просто int. EP>>А что для if(cond) 11 else 0.11? S>Либо выводить общий тип (и ругаться на несовместимость приведений), либо variant<int, float>
Опять таки, возникает вопрос совместимости с обобщённым кодом: if(x == 5) T() else U() — тут T и U могут быть одинаковые или разные.
Здравствуйте, Sinclair, Вы писали:
G>>Почему только при присвоении?! Везде, где "по смыслу" должно быть T. S>Везде, где по смыслу должно быть T, должен быть лифтинг. S>Монада — она на то и монада.
Так-то да. Но, там речь даже не о типо-maybe шла... А о неком variant[T], который не монада, а есть "упращение" от variant[T, U] при U = T. В это предлагается вычислять if-с-else.
---
С уважением, Константин Сиваков
Re[16]: Nemerle через 5 лет - выстрелит или скончается?
Здравствуйте, gbear, Вы писали:
G>Вот, при вычислении мы получили некий variant[int, void]. Вы, только честно, способны сказать _что_ мы получили? Ваше типо-maybe? Или, может быть, это "нормальный" variant?
А чем он не нормальный?
G>Вы предлагаете интерпритировать _любой_ variant[T, void] как maybe чтоли?! А если T — внезапно — окажется void? if(x > 0) foo(x) — это тогда "go wrong" — без вариантов
Почему go wrong? После упрощения будет просто void.
Если же хочется большой verbose многоэтажности, то можно делать: variant[just[T], nothing], для нескольких веток — variant[just[T], just[U], nothing] (или как вариант — variant[variant[T, U], nothing]), если есть else — то убрать nothing.
G>А что делать в "обобщенном коде", когда любой variant[T, U], при таком подходе, способен внезапно стать таким типо-maybe? G>Еще раз... maybe — вещь вполне конкретная. Обладает вполне определенной семантикой. variant — такой семантической нагрузки не несет. И "усилием воли" её на него "повесить" не выйдет.
Допустим что у maybe есть какая-то специальная maybe-семантика, которая с variant'ом не вяжется ну никак. Ок, просто не будем называть Variant[T, void] maybe — делов-то
G>>>Есть foo(x) -> void и bar(x) -> variant[int, void]. G>>>
G>>>if(x > 0) bar(x) else foo(x)
G>>>
G>>>Нужно "дергать"? EP>>Если неявного упрощения нет — то да, можно. G>Кхм... я дико извиняюсь. Но я таки спрашивал вас не за "можно", а за "нужно".
Нужно или нет зависит от конкретной ситуации. Вполне возможно что в обобщённом коде понадобится не упрощённая версия:
auto first = bar(1);
// ...auto v = if(x > 0) bar(2) else foo(x);
match(v) case(v is typeof(bar(1))) { first = v; } // ...
В то же время можно представить ситуацию, когда нужна именно упрощённая версия — например return simplify(if(x > 0) bar(2) else foo(x));.
Именно поэтому "можно", а не "нужно".
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>А почему void, а не Maybe?
Тогда уж if без else должен возвращать Alternative f => f a, а уж никак не Maybe.
Но в nemerle нет higher-kinded polymorphism, поэтому сделать так нельзя.
Версия с void вполне имеет право на существование, у нее другие юзкейсы, в том же хаскеле (теперь) есть
guard :: Alternative f => Bool -> f ()
when :: Applicative f => Bool -> f () -> f ()
unless :: Applicative f => Bool -> f () -> f ()
Правда, в хаскеле when более общая функция, чем guard, про обсуждаемую "версию с void" такого не скажешь.
(до AMP вместо Alternative был MonadPlus, а вместо Applicative — Monad, в контексте разговора это не интересно, Monad для такой функции не требуется, а MonadPlus исключительно продукт недоделанности хаскельной библиотеки, который в других языках повторять не нужно)
'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
Здравствуйте, Evgeny.Panasyuk, Вы писали: EP>Опять же, это можно делать одним явным вызовов унарного оператора, который по-максимуму упростит тип.
Этот унарный оператор нужно будет постоянно втыкать к месту и не к месту.
Зачем он нужен? У нас есть реальная потребность оперировать вложенными вариантами?
EP>Основной вопрос в том, сколько неявности можно себе позволить, не сломав при этом обобщённый код (в который могут прийти и типы приводящие к variant'у который можно упростить и те для которых его упростить нельзя).
Я пока затрудняюсь представить себе такой сценарий, в котором обобщённый код сломается из-за автоупрощения. Вы можете представить себе такой код?
EP>Опять таки, возникает вопрос совместимости с обобщённым кодом: if(x == 5) T() else U() — тут T и U могут быть одинаковые или разные.
Ну да. Если T и U одинаковые, то в одном из вариантов языка мы имеем T благодаря автоупрощению, в другом — Object, если нет ограничений на T и U.
Ну или C, если в декларации параметров T и U указано where T:C, U:C.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, gbear, Вы писали: G>Так-то да. Но, там речь даже не о типо-maybe шла... А о неком variant[T], который не монада, а есть "упращение" от variant[T, U] при U = T. В это предлагается вычислять if-с-else.
Упрощением от variant<T, U> для T=U должен быть просто T.
Потому что "int или int" — это просто int.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>А почему void, а не Maybe?
Потому что Maybe тут никому не нужен. Еще потому что внутри when может быть return/break/continue/возврат из именованного блока. Ну, и с точки зрения производительности выделение памяти под Maybe не комильфо. Кому надо заманадить, может воспользоваться комьютейшон экспрешонами, где все можно определить так как хочется.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, gbear, Вы писали: G>Вот, при вычислении мы получили некий variant[int, void]. Вы, только честно, способны сказать _что_ мы получили? Ваше типо-maybe? Или, может быть, это "нормальный" variant? G>Вы предлагаете интерпритировать _любой_ variant[T, void] как maybe чтоли?! А если T — внезапно — окажется void? if(x > 0) foo(x) — это тогда "go wrong" — без вариантов
Упрощение variant[T, T] должно давать T для любого T. В том числе и для void. Так что (if(x > 0) foo(x)) будет иметь тип void, что вполне совпадает с интуитивными ожиданиями.
G>А что делать в "обобщенном коде", когда любой variant[T, U], при таком подходе, способен внезапно стать таким типо-maybe?
А в чём тут проблема, напомните?
G>Еще раз... maybe — вещь вполне конкретная. Обладает вполне определенной семантикой. variant — такой семантической нагрузки не несет. И "усилием воли" её на него "повесить" не выйдет.
Я вижу у maybe только одно отличие от абстрактного варианта — это наличие доп. синтаксиса с hasValue.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.