Здравствуйте, WolfHound, Вы писали:
VD>>1. ПЕГ — это формат грамматики. Алгоритм парсинга ПЕГ-а называется Пакрат. WH>Пакрат это один из алгоритмов разбора ПЕГ. Можно и тупым рекурсивным спуском без мемоизации. На некоторых грамматиках оно будет даже быстрее. Но на других улетит в экспоненту. WH>Например это
Это понятно. Мне можешь не объяснять. Я для нашего иксперта это писал, так как он просто формализм с алгоритмом путает.
WH>В то же время нитра пакрат с возможностью переключится на Эрли, но не ПЕГ. Ибо приоритетного выбора там нет.
Это да. Можно сказать, что это твое главное нау-хау в алгоритме. Об этом вот и стоило бы рассказать. Ну, и о компановке приоритетного парсинга (забыл, как там автора его завали?), расширяемости и т.п. стоило бы рассказать. Это могло многих бы привлечь. Уже не раз встречался с мнением вроде того что АВК высказывал — мол Нитра это АНТЛР с перловыми структурами на выходе. Просто сказать, что он не прав — считай ничего не сказать. Все равно при своем мнении останется.
VD>>2. Пакрат работает без откатов на любой грамматике описываемой ПЕГ-ом. WH>Ты не прав. Пакрат работает с откатами, но без повторного разбора.
Это не откаты, а параллельный разбор. Тем более, что у тебя Пакратовский алгоритм изменен. И вот параллельного забора то почти и нет, так как все промежуточные результаты мемоизируются, т.е. ничего не стоят.
WH>Те если правило разбирается с того же места, то оно будет взято из мемоизации. WH>Но если во время разбора правила произошёл облом, то будет именно откат и попытка парсить другое правило.
Главное, что одна и та же работа не делается дважды. А вот vdimas явно думает, что там экспонента на откатах может вылезти, в то время как там чисто линейное время.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, vdimas, Вы писали:
F>>Но по голому факту: мэйнстрим фронты используют ужаснейший recursice descent.
V>Это просто декомпозиция грамматик.
V>Очевиднейший пример такой декомпозиции: лексер+парсер. V>Лексер всегда работает по восходящей грамматике, а парсер может работать по любой — восходящей, нисходящей или гибридной (есть и такие алгоритмы, угу).
V>recursice descent в этом смысле — это декомпозиция грамматики на несколько НЕЗАВИСИМЫХ областей.
Слушай, не нельзя такой бред нести на публике. "recursice descent" — это алгоритм. Лексер работает по регулярной грамматике, которая тупо переписывается в ДКА. Никаких рекурсий в лексере нет! Там тупое чтение по автомату.
Где ты такую кашу в мозг заложил то?
V>Простой пример: нет смысла парсить вычисления формул в синтаксисе объявлении класса или заголовков методов в C#. А вот внутри тел методов — есть, но зато внутри тел методов нельзя объявлять другие классы, поэтому эта часть грамматики внутри тел методов НЕ нужна. Вот тебе и декомпозиция.
Как этой банальностью можно оправдать заявление о том, что "recursice descent" — это не алгоритм, а способ декомпозиции?
Что ты несешь?
Что касается парсинга классов внутри методов, то в С++ именно так и происходит, а выражения встречаются в разных инициализаторах полей и конструкторах, которые тоже на топлевеле.
И вообще зачем парсеру какая-то декомпозиия, когда правила явно ссылаются друг на друга? Если правило не сослалось на другое, то оно его и не использует.
"recursice descent" — это просто самый быстрый и простой для ручной реализации алгоритм парсинга для однозначных грамматик. Проблес с ним масса. Но кода ты пишешь парсер руками — это самый логичный выбор.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, vdimas, Вы писали:
V>А вот это уже тело ф-ий удобней разбирать восходящими грамматиками — особенно что касается вложенных вызовов или математических выражений или совместной работе одного и другого. Что и происходит в реальности. Там тот же самый *LR. Потому что нисходящий разбор страдает склонностями к зацикливаниям на неоднозначных участках кода by design или к тяжеловесным откатам — и это тоже на всех углах обсосано, не буду повторяться.
Точно так же отлично парсятся рекурсивным спуском. Еще есть такие вещи как приоритетный парсинг он и леворекурсивным бывает. Мы его как раз используем. И никаких ЛР-ов там нет, так как ЛР без автоматов парсить не просто. А руками автоматы писать — мазохизм.
F>>Предполагаю что c# рослин тоже.
V>C# рассматривать нелепо: V>- грамматика сильно однозначная;
Все грамматики современных языков одназначны за мелкими исплючениями которые легко обходятся. Для С++ нужно всего лишь использовать таблицу имен в качестве контекста (проще прямо в лексере).
V>- объем перемалываемых исходников смешон; для сравнения, в современной С++ программе из каждого CPP-фала может быть подключено до половины Буста (утрирую, но оно близко к такому положению дел).
Это просто бред. Объем исходников определяется разменами проектов. Например, у какого нибудь решарпера это гигабайты кода.
Тормоза при компиляции плюсов определяются возней с инклюдами (которые кэшируются) и раскрытием шаблонов.
V>Наоборот, если речь о неоднозначных участках кода.
Речь об ошибках в коде и восстановлении после них.
V>При рекурсивном спуске проще генерить ошибку только о простых ситуациях, типа, тут идёт объявление класса, а после имени класса написано, скажем, const — и генерим ошибку "это не ожидается здесь". ))
Очередной бред. Леворекурсивные парсеры падают в месте ошибки. Это их главная особенность. По этому их и восстанавливать проще и ошибки для них проще выдавать. Хотя это вопрос решаемый. Ошибки можно по лесу вычислять. Мы в итоге так и стали делать, так как это дает качественные сообщения не только в месте первого падения, но и далее.
V>LR-разбор в состоянии подсказать множество верных ожидаемых символов. В реальности там выбор из 1-2-х вариантов максимум, потому что в С++ неоднозначность происходит в выборе м/у идентификатором типа или переменой, вот и вся неоднозначность, которой "болеет" С++.
ЛР падает на свертках. И эти почти наверняка не места реальных ошибок. В тоже время ЛЛ падет на входе в правило и это почти наверняка реальное место ошибки.
Ты почитай какими извращениями занимаются те кто восстановление для GLR-ов пишут. Это жесть!
V>Т.е. типа такого: V>
V>id1 id2(id3);
V>
V>В этом месте id1 — всегда идентификатор типа, а вот чем является id2 зависит от id3.
Пипец ты жжешь! Какие типы в ЛР-парсере? Вот в ЛЛ можно тупо таблицу имен протащить и действительно во время парсинга резолвнуть имя узнав является ли оно типом (если речь о С++). А как это сделать в ЛР, который обломался на свертке некого правила?
V>Если id3 — это значение (переменная/константа/перечисление), то id2 — это идентификатор переменной, а всё выражение означает объявление переменной типа id1 и вызов соотв. конструктора этого типа. А если id3 — это имя типа, то всё выражение означает объявление сигнатуры некоей ф-ии id2.
Какая чушь?! Никто не типизирует выражения С++ во время парсинга. Только лукапят имена по таблице имен. И тип выражения определяется по тому является ли id1 типом. Хотя здесь выражение и так объявление переменной. Вот в случае id1* id2(id3) действительно было бы не ясно.
V>С другой стороны, подобную неоднозначность НЕ обязательно сваливать на парсер и её не сваливают — для такой конструкции заводится отдельный "недоопределённый" нетерминал и его ресолвят до однозначного через таблицу идентификаторов ручками. Т.е. на скорости работы именно парсера по алгоритму *LR это не сказывается (если убрать ресолвинг). Это конкретные заморочки конкретно С++, которые верны для ЛЮБОГО алгоритма парсинга.
Причем тут скорость? А... не я пас. Это такая жесть, что просто нет слов.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, WolfHound, Вы писали:
WH>В чём проблема компилятору языка быть написанным на .НЕТ?
Не поверишь
Люди хотят ничего не ставить и чтобы работало.
Например был вопрос как использовать NuGet в Маке.
Ну так я и предложил запусти через Mono и нет проблем.
Тут собеседник упёрся дескать что-то ставить, а он ничего вообще не хочет ставить.
Я-то понимаю, что аргумент бестолковый, но если есть один такой, значит будут и ещё.
Здравствуйте, vdimas, Вы писали: V>А почему бы не сделать обновление автоматизированным (типа как делают ORM), где прикладной код писать в колбэках навроде onClanChanged? V>Тогда новым коллегам вообще не придется возиться с транспортным слоем или возиться по-минимуму.
Да, это возможно. Я упоминал о расширяемом кодогенераторе, и чтобы не быть совсем голословным, приведу пример:
Скрытый текст
public class DeltaGenerator : ICsGenerator
{
string FieldUpdate(RecordForm rec, RecordField field) =>
$@"if (update.{field.csName}.HasValue) this.{field.csName} = update.{field.csName}.Value;";
public void Generate(CsModel model, Module mod)
{
foreach (var rec in mod.Records)
{
var deltaClass = rec.StringAttribute("delta.class", null);
if (deltaClass != null)
{
var deltaFile = rec.StringAttribute("delta.file", mod.csFileName);
var @class = model.File(deltaFile).NamespaceClass(deltaClass);
@class.Partial = true;
@class.Method($@"
public void Update({rec.csTypeName} update)
{{
{rec.Fields.Join(f => FieldUpdate(rec, f))}
}}
");
}
}
}
}
Этот скрипт расширения (который я только что набросал за пару минут) генерирует C# код, подобный тому, что я приводил, и который вызвал критику. Объявить здесь же свойства, события, что угодно, или вызвать коллбэки — очевидно, не проблема. Используется следующий образом:
[csharp delta.class="Game.Unit" delta.file="Unit.Generated.cs"]
record UnitUpdate
{
...
}
Partial класс Unit будет сгенерирован в нэймспэйсе Game в файле Unit.Generated.cs.
Однако еще раз скажу, почему этого нет. Чтобы решить задачу в общем случае, нужно в первую очередь стандартизировать способы обновления коллекций. (А их много разных, так как коллекции бывают упорядоченными и неупорядоченными списками, упорядоченными и неупорядоченными словарями, в какие-то элементы только добавляются, из каких-то и удаляются, какие-то всегда обновляются целиком, для каких-то обновление элемента это в свою очередь дельта). Эта работа еще не проделана (а возможно никогда и не будет), и приоритет у нее низкий. Потому что таких функций обновлений в одном проекте всего несколько мест строк на двести-триста в сумме, а писать код реакции (как правило, тривиальный) под условием HasValue, пусть и не так изящно как в коллбэках, но не менее удобно. V>Еще идея. V>Если уж у вас собственный DSL и собственный маппинг (кодогенерация) на типы, то можно ввести такую штуку V>(условный синтаксис): V>
V>type ClanId = Optional<int> where NotSet = 0;
V>type Coord = Optional<uint> where NotSet = ~0; // 0xFFFFFFFF
V>
Сейчас можно так:
define ClanId ?int;
(?int это шорт алиас для Optional<int>; странноватый и не факт что удачный ? перед типом а не после, потому что изначально это был модификатор поля рекорда, а не тип, а потом с развитием системы типов оставили как есть).
Что касается аспектов DSL (про генерируемый C#-код — ниже):
ИМХО, ClanId — должен быть int, а не Optional<int>. Скажем сервисная функция GetClanInfo(ClanId clan_id) не должна принимать NotSet, а GetPlayerInfo может вернуть NotSet в поле ClanId, и я бы предпочел, чтобы в обоих случаях это было отражено в сигнатуре.
Одна из возможных мотиваций для констант после where — экономия трафика — тоже не проходит, так как если не использовать variable encoding, то для NonSet получим 4 байта вместо 1 байта (а часто — бита), что в среднем даст худший результат, а с variable encoding мы стараемся быть осторожнее, так как в случае частого использования и большого числа мелких пакетов нагрузка на cpu может дать больше вреда, чем минимальная экономия трафика — пользы (впрочем, если до этого дойдет, нужно конечно замерять, задумываться об этом имеет смысл лишь для очень частых событий). V>Для C# генерить примерно такой маппинг:
Скрытый текст
V>
V>struct ClanId {
V> public int Value;
V> public bool HasValue { get { return Value != 0; }}
V>}
V>struct Coord {
V> public int Value;
V> public bool HasValue { get { return Value != ~0; }}
V>}
V>void Update(ClanId clanId) {
V> if(clanId.HasValue) ...
V>}
V>
V>Тогда размер структуры в памяти будет минимален и уже имеющийся код можно будет оставить без изменений.
Здесь мы тоже не можем по типу отличить обязательно наличествующее значение от опционального. Небольшая экономия памяти не кажется достаточной компенсацией.
Ну и не работает обобщенный код для Nullable и ??, что может быть или не быть неудобством в конкретных случаях.
Но честно говоря, я не понимаю, зачем нужно искать замену Optional в случае его использования по самому что ни на есть прямому назначению.
Однако, в любом случае спасибо за предложения. (Без обид, но думать о том, чтобы все бросить и переписать на ASN.1, ANTLR, BLToolkit и т.п. мне давно уже поздно, а о том, как люди воспринимают текущее решение, какие недостатки видят и какие альтернативы предпочитают — это полезно, и хоть я и отстаиваю свою точку зрения, но все идеи и предметную критику записываю. Нужно ли что-то менять в системе апдейт-рекордов — вопрос для нас еще открытый).
Здравствуйте, VladD2, Вы писали:
V>>Т.е. если в Nemerle опишу некий свой variant MyOptional, это будет value-тип? VD>Value-option не обязан быть вариантом. В немерле он сделан просто структурой.
Обязан.
Структура — это произведение типов, алгебраик — сумма.
Вот так было лучше (из твоей статьи):
Тип «option» – это вариантный тип данных, который объявлен в стандартной библиотеке Nemerle следующим образом:
variant option[T]
{
| None
| Some { val : T; }
}
Иначе у тебя получится аналог Nullable<> из System, но в C#/VB хоть компилятор "знает этот тип в лицо", как грится, и по-особенному с ним работает. С другим таким же MyNullable<> аналогичные фокусы не прокатывают.
А у тебя компилятор знает твой переписанный Optional в лицо? ))
public Value : T
{
get
{
if (HasValue) _value
else assert(false, "try use Value when it not set");
}
}
Кошмар.
В том-то и беда для ООП-языков, что тип вариант нормально не выразить, потому что единственная возможность, когда в ООП можно одновременно разветвиться и изъять "откуда-то" значение, это вот такая конструкция:
Но ведь именно в случае Optional объявление предварительно "пустого" extractedValue убивает всю идею на корню.
Не видишь разве сам?
Тут ведь вся идея в том, что MyType может быть даже таким типом, который принципиально не может быть "пустым", а Optional<MyType> — может.
Поэтому, в ООП стиле "окончательно правильно" можно только так:
А вот за GetValueOrDefault() — вообще руки оторвать надо. ))
Вот зачем ты сделал копию Nullable<> сугубо из мира ООП, если оно уже есть в системной библиотеке, т.е. от него всё-равно не убежишь?
Потому что ограничение на where T : struct? Дык, а ссылочные типы и так изкаробки обладают этой optional/nullable семантикой.
VD>В Немерле варианты сделаны на базе классов, но это одна из возможных реализаций.
Это отличная реализация, особенно в иммутабельной среде.
Реализаций может быть несколько, просто конкретно в дотнете если уж тип ссылочный — то это, блин, тип из кучи. ))
А не просто тип, который передают и хранят по ссылке.
Для языков навроде Немерле нужен чуть другой рантайм. Дотнетный не тогось — прибит гвоздями к ООП.
Вот, Гугл тут делает VM для веба, её текущие версии уже доступны, я уже посмотрел и вам советую...
Эта VM нейтральна к модели памяти.
V>>Я правильно понимаю, что средствами языка, т.е. через variant, его нельзя было описать как value-тип? VD>У языка как бы средств больше чем одно. option ни разу не обязан быть ваниантом.
Он обязан быть алгебраиком по-определению.
Всё правильно у тебя в статье было когда-то написано.
V>>Я-то уже 100 лет на Немерле не смотрел, но если я понимаю правильно, то ты лишь удружил через ЧТД. VD>Ты себе что-то на придумывал и пытаешься это обосновать любыми путями.
Да ладно тебе включать непонимающего.
Всё ты прекрасно понимаешь, просто со своими особенностями — да, ты по своей инициативе взял Немерле под своё крыло, это похвально, конечно, но ты к своему подопечному явно не объективен.
Вот даже взять синтаксис:
public GetValueOrDefault() : T { _value }
А чего вот так не сделали еще?
public GetValueOrDefault() => _value;
Или так:
public GetValueOrDefault() = _value;
И зачем идёт аннотация типа : T в исходном варианте?
V>>А как ты отличаешь value-тип от ссылочного? VD>Как кота от кошки. Смотрю value-тип — говорю — "value-тип".
Т.е. вне дотнета жизни нет? ))
Единственное отличие в том, что value-тип ХРАНИТСЯ по значению, а ссылочный по ссылке.
Передаваться оба могут как ссылочные и на этом есть куча оптимизирующих трюков именно в ФП.
Например, ссылочный тип мог быть создан по-значению в некоей структуре (в памяти этой структуры), а "все остальные" получают/размножают лишь ссылку на такое значение.
Понятно, что в дотнете в этом плане особо не разбежишься.
Причем, в дотнете есть т.н. "управляемая ссылка" (т.е. можно ссылаться на "середину" тела другого объекта из кучи или стека), но её тип почему-то не совместим с "обычным" ссылочным типом. Бред какой-то...
V>>Т.е. вопрос в следующем: а как эти значения этих типов передаются в кач-ве аргументов функций — по ссылке или по-значению? VD>Не имеет значения. Передать вэлью-значение по ссылке не особо проблематично. Это детали реализации.
Именно. Передать можно. Вопрос — как хранить. Потому что в случае value-типа надо делать копию значения.
По-определению ссылочного типа — это тип-индекс. Значения этого типа хранят индекс (ключ) другого значения. При копировании ссылочного типа копируется индекс, а не значение. Вот и вся семантика ссылочного типа.
А то, что ссылочный тип в дотнете создаётся исключительно на куче — это заморочки сугубо дотнета.
Всё никак не научишься спорить? ))
Ты можешь возражать своим аргументом с обоснованием, или ты НЕ можешь возражать.
Третьего не дано.
V>>1. Было сказано "в ФП языках". VD>Язык не имеет значения.
Назови язык как хочешь, но в нём нужна поддержка алгебраиков + паттерн-матчинга по ним (одно без другого не имеет смысла, ес-но), чтобы осилить именно идиому Optional. Потому что проблему реализации в ООП-style я уже показал. Единственное нормальное решение в ООП — через колбэк. Но, боюсь, индустрия как такому "нормальному решению" может оказаться не готовой, ы-ы-ы.
(и пофик что колбэк якобы функциональный тип в "чужеродном ООП" — замени его на интерфейс-колбэк, ничего не изменится в семантике)
V>>2. Variant не описан средствами VB, а "дан сверху", т.е. является встроенным для него. VD>Опять же не имеет значения.
Если речь о системе типов — имеет первейшее.
V>>Я тебе больше скажу. Генератор CORBA IDL или COM IDL генерит размеченные объединения как value-типы для С++. V>>Одно плохо — CORBA это тоже не про ФП-языки. VD>Ты себе придумал какой-то странный ценз "ФП-языки". Он только тебе интересен.
Я уже поправился — алегбраик + паттерн матчинг.
Просто сие пошло от функциональных языков изначально.
V>>В общем, в двух словах. Не столько для тебя, сколько для читателей. VD>Ну, да. Как всем же ясно что ты прав и они хотят у гуру поучиться.
Почему бы и нет?
V>>Основная фишка тут в том, что реализация объединения через условный value-тип будет занимать в памяти такой же размер, какой нужен для представления самого большого из вариантов + дискриминатор. VD>Ой-йо-йо. Великое горе! А любой объект в дотнете занимает 24 байта (под x64).
Зато ссылка на него занимает 4 байта в x86.
И вообще, это заморочки конкретно дотнета.
VD>И стало быть перерасход 1-2 интов не такая уж проблема.
Проблема при передаче параметров и хранении.
VD>А там еще локальность памяти подтягивается
Это в дотнете она подтягивается.
VD>затраты на занятие памяти в куче
именно что
VD>и в друг оказывается, что потеря этих байтов просто фигня по сравнению с выигрышем.
От сценариев зависит. Если это значение не надо многократно копировать, то да. Если надо — то упс.
VD>Вот так и с ValueOption оказывается, что потеря 1-4 байта (занимаемого bool-ом в зависимости от выравнивания) — это ни что по сравнению с затратами на ссылочные типы.
Это ничто по сравнению со структурой в 1 байт. Когда байт десятки, то это уже ой. А когда мы в вариант собираем структуры из 1-го байта и из десятков байт, то смысл хранить вариант по ссылке вполне может появиться.
Как сказал бы WH по этому поводу: "компилятор сам должен первое превращать во второе или второе в первое, глядя на использование".
Теоретически я где-то согласен, а фактически его заносит. ))
VD>>>Тот же Котлин, хотя и использует нулевую ссылку каксэ отсутствующее значение, интерпретирует, например, string и string? как разные типы. Это позволяет компилятору контролировать ошибки связанные с null–ами. V>>А тут не верно. VD>Только по-твоему.
Компилятор не контроллирует ошибки, связанные с null-ами, он просто контролирует РАЗНЫЕ типы.
Я тебе специально привел аналог из С++, но про С++ достоверно известно, что компилятор никак не контролирует конкретно NULL.
Зато контролирует типы. Поэтому, просто выносим non-nullable в ОТДЕЛЬНЫЙ тип. Вот и вся суть "фокуса".
V>>Озвученное тобой говорит о том, что система типов Котлина НЕ контроллирует значение ссылочного типа, поэтому просто добавила ДРУГОЙ тип для non-nullable-ссылок. VD>Не говори ерунды. Система типов нигде значения не контролирует, так как значения сущность времени исполнения.
В общем, бывают системы типов, которые контролируют именно значения как параметр типа.
ЗТ называются.
Спроси у WH, он любит сверкать изречениями аккурат по этому поводу.
VD>Зато она контролирует отсутствие ошибок с этими значениями.
В моём примере такой контроль запросто получается на прикладном уровне, а не на уровне системы типов.
V>>То, что в синтаксисе языка наоборот — т.е. как бэ nullable-тип видится как "другой" — это ж просто трюк такой, верно? VD>Какие на фиг трюки? Это система типов языка.
Обычная там система типов, выглядит как без ЗТ.
Ссылочный тип сам по себе — это и так (в случае строки) уже будет тип 'string?' — сие дано изкаробки для любого ссылочного типа.
А теперь вводим некий ДРУГОЙ тип — аналог показанного мною NotNull<string> и делаем такой фокус — в синтаксисе языка обозначаем его как просто string.
VD>Для этого языка это разные, хотя и связанные типы.
В моём сниппете T* и NotNull<T> — тоже разные типы.
Первый — nullable, второй — никогда.
VD>Система типов языка может отличаться от системы типов базовой платформы.
Может. Только тут не отличается йоту. По крайней мере в плане nullable.
Тут весь трюк заключается сугубо в синтаксисе, где некий встроенный тип NotNull<string> в синтаксисе (и только в синтаксисе) выражается через простое string.
V>>А так-то похожий трюк с введением ДРУГОГО типа я тоже в С++ тоже периодически делаю. VD>Да это где угодно можно. Вопрос лишь в удобстве использовании и загрузки компилятора ненужной работой.
Вот тут верно. Дело в удобстве.
С другой стороны, когда компилятор знает некоторые типы в лицо — это всегда грязный хак как раз в обход системы типов языка.
Потому что иначе такой хак был бы не нужен, ес-но.
VD>В том же Колине кроме разведения типов еще есть ряд фич упрощающих жизнь с этим. Иначе эта была бы адова боль, а не работа.
Я посмотрел "эти фичи".
Основное отличие в сценариях от моих — это в явном вызове конструктора NotNull<T>(someNullablePtr) у меня, потому что explicit было поставлено специально (думаю, понятно — почему).
Но это вещь одноразовая — только при вызове new объекта.
Но сам понимаешь, в С++ с его шаблонами эта вещь обыгрывается тоже однократно:
Последнее не сильно отличается от этого в плане "синтаксического оверхеда":
foo(new MyType(args));
Зато сильно отличается в плане безопасности — в теле foo() и далее по зависимостям не надо делать никаких проверок.
Более того, метод NotNull operator-> проходит через такое:
T * tmp = ptr_;
assume(tmp);
return tmp;
Это подсказка компилятору, что tmp не может быть NULL ни при каких обстоятельствах, что позволяет компилятору делать совсем уж агрессивные оптимизации.
Итого:
— проверку на NULL на каждый чих убрали, контроль возложили на компилятор;
— агрессивную оптимизацию включили.
— все выполнено ср-вами языка.
))
VD>Так у них можно проверить в if-е значение на null и компилятор сам подменит тип внутри true-секции этого if-f. Оберткой это не сделаешь. Или можно?
Оберткой надо будет создать новую "строгую" ссылку в секции true, согласен.
VD>Далее для null-абл значений есть лифтинг. Например, написав optionX?.Foo мы получим значение Foo поднятое в null-абл тип. И это можно проделывать сколько угодно долго.
Дык, это же не система типов, это же просто синтаксический сахар для такого (в терминах Шарпа):
option.HasValue ? new Nullable<ReturnTypeOfFoo>(Foo()) : null;
Думаю, в Немерле что-то типа такого тоже должно быть?
Согласен, в разработке где-то можно будет на такое опереться, сделав автоматом длинный вызов, не расписывая ветвления вручную.
VD>Еще они проводят анализ библиотек Явы и вычисляют функции точно не возвращающие нулабл-типы. Всего этого на обертках не добьешся.
На обертке компилятор выкинет повторный if в момент линковки в теле конструктора моего NotNull, если поданное значение будет достоверно не NULL.
VD>В итоге получается разный класс решений. У тебя это примочка вызывающая боль при использовании, а у них стройная система.
Нет никакой боли и не про боль речь, а про систему типов языка.
Моей задачей было лишь ПРОДЕМОНСТРИРОВАТЬ механику такого трюка в языке без ЗТ.
Хотя, вот это:
можно проверить в if-е значение на null и компилятор сам подменит тип внутри true-секции этого if
Это обычно признак языков с ЗТ. В таких языках т.н. "контекст типизации" меняется прямо по ходу вычислений в теле ф-ий, а не только в ветках паттерн-матчинга, как у обычных ФП-языков.
Одно но — в языке со строгой типизацией всё-равно невозможно использование пременной одного типа в виде переменной другого, но возможно после такой проверки произвести, скажем присвоение/инициализацию, которая до проверки вызовет ошибку компиляции.
В случае С++ тоже надо будет в положительной ветке создать переменную NotNull<>, скомпилируется второй if в теле конструктора, который будет выкинут затем оптимизатором.
Да, есть такое. Без вот этого трюка оптимизатора с выкидываением повторного if в С++ мой вариант был бы фактически не нужен и местами даже вреден с т.з. эффективности.
public Value : T
{
get {
match (this) {
| Some (x) => x
| None =>
throw System.ArgumentException ("option.Value")
}
}
}
Как говорится, найдите 10 отличий.
V>В том-то и беда для ООП-языков, что тип вариант нормально не выразить
Очередной раз несешь околесицу. С точки зрения использования разницы нет.
V>потому что единственная возможность, когда в ООП можно одновременно разветвиться и изъять "откуда-то" значение, это вот такая конструкция: V>
Столько слов не по делу. Сказал бы, что хочешь паттер-матчинг, так я тебе бы сразу ответил бы, что это не проблема. Во-первых в Немерле можно делать МП и не по объектам, а во вторых в Немерле есть возможность расширения МП. В частности вот эти вот строки: https://github.com/rsdn/nemerle/blob/master/lib/option.n#L215
Здравствуйте, fddima, Вы писали:
F>И да — имхо, только C++ позволяет более-менее это обернуть, сохранив типы (т.е. избавиться от трюкачества) и не растерять перфоманс.
Вообще-то помещение указателя в структуру дает то же эффектв дотнете. Таким образом сделан ImmutableArray<T> например. Это легкая обертка над массивом делающая его неизменяемым.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, vdimas, Вы писали:
V>А еще язык С (и С++) так хитро устроен, что способен приводить целочисленное значение 0 к типу указателя на любой тип. О как! )) Эдакий хак. В Джава и дотнете для аналогичного ввели специальное ключевое слово, чтобы не насиловать систему типов.
В С++ тоже ввели (nullptr), но 0 оставили для совместимости. Из-за этого нуля много проблем было.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, WolfHound, Вы писали:
_>>Во-вторых, если нам требуется только одно поле в структуре, то мы только его и будем запрашивать, так что никакого дополнительного трафика памяти не будет (внутренние тонкости работы кэша — это другой вопрос, но в регистры ничего кроме нужного нам поля копироваться не будет). WH>А вот тут ты не прав. WH>Процессор не может прочитать из памяти 4 байта. Он всегда читает всю линейку кэша.
Опять же не корректно сразу по трём причинам. )))
1. Процессор читает из памяти данные порциями соответствующими ширине шины к памяти (для обычных машин это будет всегда 64 бита), а не ширине линии кэша. Т.е. только 1/8 линии кэша современных процессоров Intel передаётся с периодичностью равной удвоенной (для ddr) частоте шины памяти (800-1200 МГЦ для современных компьютеров) по одному каналу.
2. Хотя при промахе в кэше действительно инициируется чтение из памяти всей линии (собственно иначе в кэше было бы меньше смысла), но для использования процессором нужных ему данных не требуется дожидаться загрузки всей линии.
3. Если говорить о процессах, типа обсуждаемых нами (линейная итерация по массиву нессылочных данных), то в таких случаях предсказатель работает практически гарантированно (у нас же конвейер с OOO), так что программисту использующему нессылочные данные можно особо не задумываться о вопросах кэша за исключением некоторых особых случаев (см. следующий абзац).
_>>Ну и в третьих, если говорить об эффективности работы кэша, то тут действительно есть нюансы. И выбор оптимальной схемы будет зависеть от соотношения между отношением размера структуры к линии кэша и отношением времени исполнения задачи цикла (не забываем, что в нормальном цикле нам надо не только запрашивать данные, но и что-то с ними делать) к времени загрузки данных из памяти в кэш. WH>Очень часто память доминирует.
Смотри, если мы возьмём цикл с телом вида "a[i].v=b[i].v+c[i].v;", то размер используемой структуры действительно становится критически важным. Потому что хотя для такого линейного цикла предсказатель и работает отлично, но при этом всё равно возникает "затор по памяти", т.к. время на исполнение других операций цикла (в данном случае это просто инструкция сложения) не позволяет успеть достать данные из памяти. Соответственно чем меньше размер данной структуры, тем реже будет происходить этот затор. Грубо говоря, если наша структура будет соответствовать одному int32, то потенциально заторная ситуация будет встречаться только в каждую 16-ую итерацию цикла. Соответственно в такой ситуации (она кстати периодически встречается, особенно в случае использования simd) подход SOA действительно является сильно ускоряющим код.
А вот если взять код вида "a[i].v=F(G(b[i].v), H(c[i].v);" (где F, G и H заняты вычислениями на регистрах), то ситуация может быть уже совсем другой — есть шансы, что работа предсказателя в сочетание с OOO может обеспечить вычисления без задержек при любом размере структуры. Хотя подобные вещи уже надо смотреть в конкретике и на практике с конкретными ЦПУ. )))
Кроме того, если не хочется доверять предсказателю процессора (хотя у Intel он не плох), то у нас же имеются _mm_prefetch или __builtin_prefetch (это уже специфично для gcc и актуально для случая работы без sse, т.е. на процессорах другой архитектуры), позволяющие руками инициировать запрос к памяти. Это может выглядеть приблизительно так:
for(int i=0; i<n; i++){
_mm_prefetch((char*)&b[i+1],_MM_HINT_T0);
_mm_prefetch((char*)&c[i+1],_MM_HINT_T0);
a[i].v=b[i].v+c[i].v;//желательно конечно код "потяжелее", а то иначе опять же лучше переходить к SOA
}
_>>Жёсткая связь между типом данных и типом размещения данных в любом случае не рабочая. WH>На практике она достаточно хороша.
На практике оно так же убого как и в теории. )))
_>>При условие сохранения удобства — само собой. Если же быстродействие будет требовать уменьшения удобства, то тут уже надо смотреть конкретную ситуацию — стоит ли оно того. ) WH>Как я уже говорил всё это безобразие может делать компилятор. WH>Нужно его только этому научить.
Я как бы только за. Более того, я можно сказать настоятельно запрашиваю себе такой инструмент) Вот пытался вас соблазнить на создание "C++ с крутыми макросами" (теоретически с их помощью такое можно было бы попробовать сделать), но вроде пока что-то не вышло. )))
_>>8М будет в случае Some optional. А в случае None optional будет 4M, т.к. мы будем запрашивать из памяти только флаги. WH>Не правильно. Мы не можем запросить из памяти 4 байта. Только всю линейку кэша целиком.
Ещё раз: запрос значения из памяти — это помещение его в регистр и оно никак не может происходить линейками кэша. ))) В случае "None optional" значения не будут копироваться из памяти/кэша в регистр (вообще не будет исполняться соответствующая ветка алгоритма), так что никакого запроса не будет. А то, что там кэш себе накопировал в себя в фоне — это его личное дело. ))) Вообще этот вопрос уже подробно разобран выше, а тут у тебя просто ещё терминологическая ошибка — что называть "загрузкой значения из памяти". )
_>>Я думаю, что всё же можно сохранить мощь современных языков, добавив к ним отдельно данную возможность. Причём возможно через МП (кстати, интересно хватило бы на это макросов Rust'а или Nemerle?)... WH>Про руст не знаю. Слишком давно на него смотрел.
Вряд ли на Немерле (или любом .net языке) будут решать задачи, для которых актуальны подобные проблемы. )
_>>Да, и небольшое замечание по предыдущей дискуссии в контексте этой ссылки: обрати внимание, что все эти игроделы (и автор данного языка и остальные комментирующие) сразу ставят крест даже на самой идеи использования языка с GC в своей отрасли... ))) WH>Это психологические проблемы.
Хех, вот смотри, ты вроде как не занимаешься написанием игр. Т.е. не относишься к их индустрии. Но при этом считаешь, что лучше их знаешь как им решать свои задачи. Тебе не кажется это несколько самонадеянным? ) Не допускаешь мысли, что всё же не "все вокруг неправы, один я знаю истину", а возможно наоборот? )
Здравствуйте, alex_public, Вы писали:
_>Хех, вот смотри, ты вроде как не занимаешься написанием игр. Т.е. не относишься к их индустрии. Но при этом считаешь, что лучше их знаешь как им решать свои задачи. Тебе не кажется это несколько самонадеянным? ) Не допускаешь мысли, что всё же не "все вокруг неправы, один я знаю истину", а возможно наоборот? )
Здравствуйте, alex_public, Вы писали:
_>2. Хотя при промахе в кэше действительно инициируется чтение из памяти всей линии (собственно иначе в кэше было бы меньше смысла),
Вот это важно. А всё остальное несущественные детали.
Соответственно если у нас структура 32 байта, а запрашиваем мы 4 то через шину памяти будут прокачаны 28 лишних байт.
И ничего ты с этим не сделаешь.
_>но для использования процессором нужных ему данных не требуется дожидаться загрузки всей линии.
В цикле это не важно.
_>А вот если взять код вида "a[i].v=F(G(b[i].v), H(c[i].v);" (где F, G и H заняты вычислениями на регистрах), то ситуация может быть уже совсем другой — есть шансы, что работа предсказателя в сочетание с OOO может обеспечить вычисления без задержек при любом размере структуры. Хотя подобные вещи уже надо смотреть в конкретике и на практике с конкретными ЦПУ. )))
Проблема в том, что очень часто вычисления недостаточно тяжёлые для того чтобы они были узким местом.
Например, разработчики factorio.com говорят, что их главная проблема с производительностью симуляции именно скорость работы памяти.
Я сам однажды разогнал алгоритм в 100 раз уменьшив размер памяти до размера кэша процессора. Но при этом именно вычислений приходилось делать больше.
Так что память тормозит и весьма сильно.
_>Я как бы только за. Более того, я можно сказать настоятельно запрашиваю себе такой инструмент) Вот пытался вас соблазнить на создание "C++ с крутыми макросами" (теоретически с их помощью такое можно было бы попробовать сделать), но вроде пока что-то не вышло. )))
Нативный язык сделаем. С++ я точно делать не буду.
_>Ещё раз: запрос значения из памяти — это помещение его в регистр и оно никак не может происходить линейками кэша. ))) В случае "None optional" значения не будут копироваться из памяти/кэша в регистр (вообще не будет исполняться соответствующая ветка алгоритма), так что никакого запроса не будет. А то, что там кэш себе накопировал в себя в фоне — это его личное дело. ))) Вообще этот вопрос уже подробно разобран выше, а тут у тебя просто ещё терминологическая ошибка — что называть "загрузкой значения из памяти". )
Это всё лирика имени капитана очевидность.
Важно то что процессор всё равно запросит эти данные из памяти.
_>Хех, вот смотри, ты вроде как не занимаешься написанием игр. Т.е. не относишься к их индустрии. Но при этом считаешь, что лучше их знаешь как им решать свои задачи. Тебе не кажется это несколько самонадеянным? ) Не допускаешь мысли, что всё же не "все вокруг неправы, один я знаю истину", а возможно наоборот? )
Я знаю, как они работают. Нет там ничего сверхъестественного.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, VladD2, Вы писали:
VD>Горазд ты трепаться. Вот смотри. Я тебе сейчас приведу сразу 8 критических к производительности задач написанных на дотнетных языках и яве: VD>1. ReSharper. VD>2. IDEA и ее языковые плагины. VD>3. NetBeans. VD>4. Eclipse. VD>5. Компилятор Шарпа. VD>6. Компилятор Явы. VD>7. CodeRush. VD>8. Nitra и наш движок IDE. VD>Назови хотя бы 3-4 аналога написанного на С++ и объясни почему движки IDE и компиляторы теперь пишут на управляемых языках? Может они не так уж и медленны?
Ну вообще то полно IDE и на C++ (тот же QtCreator, да и VS по сути тоже, хотя сейчас уже не целиком, но я пользовался как раз полностью нативной), причём они в отличие от указанных Java IDE не имеет слабого, но раздражающего торможения (говорю по личному опыту, так что могу гарантированно продемонстрировать). А уж если говорить про компиляторы, то вообще смешно — если предложить gcc работать с такими объёмами как в .net, то он будет компилировать молнией.
VD>Может как раз AST на GC строится быстрее?
Ты похоже иногда не понимаешь разницу между курицей и яйцом. В языке без обязательного GC всегда можно легко использовать полностью аналогичные GC (или даже более быстрые, типа обычного пула) механизмы. Хотя бы потому, что рантайм языков с GC пишется на языках без GC. ))) А вот обратное уже не верно — в языке с обязательным GC обычно нет возможности нормального использования других механизмов.
Здравствуйте, Klikujiskaaan, Вы писали:
_>>Хех, вот смотри, ты вроде как не занимаешься написанием игр. Т.е. не относишься к их индустрии. Но при этом считаешь, что лучше их знаешь как им решать свои задачи. Тебе не кажется это несколько самонадеянным? ) Не допускаешь мысли, что всё же не "все вокруг неправы, один я знаю истину", а возможно наоборот? ) K>https://unity3d.com/ru/unity
О, ещё один с подобной ссылкой. И почему куча народа начинает её совать во всех темах, даже не попытавшись разобраться самому? )
Отвечаю в очередной раз: Unity написан на C++!
И его единственно отличие от множества подобных ему движков в том, что кроме стандартных для этой индустрии скриптовых языков (JS, Python, Lua) они взяли ещё и настандартный (да и вообще статически типизированный) C#. Но можно было взять вместо него и какой-нибудь Кобол — производительности всё равно без проблем хватило бы, т.к. он работает здесь только как системный клей, задающий высокоуровневую логику.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, Klikujiskaaan, Вы писали:
_>>>Хех, вот смотри, ты вроде как не занимаешься написанием игр. Т.е. не относишься к их индустрии. Но при этом считаешь, что лучше их знаешь как им решать свои задачи. Тебе не кажется это несколько самонадеянным? ) Не допускаешь мысли, что всё же не "все вокруг неправы, один я знаю истину", а возможно наоборот? ) K>>https://unity3d.com/ru/unity
_>О, ещё один с подобной ссылкой. И почему куча народа начинает её совать во всех темах, даже не попытавшись разобраться самому? )
_>Отвечаю в очередной раз: Unity написан на C++!
Здравствуйте, WolfHound, Вы писали:
_>>2. Хотя при промахе в кэше действительно инициируется чтение из памяти всей линии (собственно иначе в кэше было бы меньше смысла), WH>Вот это важно. А всё остальное несущественные детали. WH>Соответственно если у нас структура 32 байта, а запрашиваем мы 4 то через шину памяти будут прокачаны 28 лишних байт. WH>И ничего ты с этим не сделаешь.
Так при правильной организации кода (когда подкачка будет осуществляться фоном к вычислениям) как бы пофиг.
_>>А вот если взять код вида "a[i].v=F(G(b[i].v), H(c[i].v);" (где F, G и H заняты вычислениями на регистрах), то ситуация может быть уже совсем другой — есть шансы, что работа предсказателя в сочетание с OOO может обеспечить вычисления без задержек при любом размере структуры. Хотя подобные вещи уже надо смотреть в конкретике и на практике с конкретными ЦПУ. ))) WH>Проблема в том, что очень часто вычисления недостаточно тяжёлые для того чтобы они были узким местом. WH>Например, разработчики factorio.com говорят, что их главная проблема с производительностью симуляции именно скорость работы памяти.
Тут дело даже не в том, чтобы вычисления были узким местом, а в том чтобы было правильно взаимное расположение вычислений и обращений к памяти — без возникновения ожиданий. Но да, при очень быстрых вычислениях (особенно таких как в SIMD) это становится не реальным и надо оптимизировать работу кэша (например решениями в стиле SOA).
_>>Я как бы только за. Более того, я можно сказать настоятельно запрашиваю себе такой инструмент) Вот пытался вас соблазнить на создание "C++ с крутыми макросами" (теоретически с их помощью такое можно было бы попробовать сделать), но вроде пока что-то не вышло. ))) WH>Нативный язык сделаем. С++ я точно делать не буду.
Нуу лично мне именно C++ не нужен. Мне нужен язык с тремя очевидными свойствами:
1. Набор возможностей не слабее C++. Причём как низкоуровневых, так и высокоуровневых (разве что кроме МП на шаблонах).
2. Возможность подключения C и хотя бы частично (конвертер заголовков как в D?) C++ библиотек.
3. Поддержка в мощной IDE (желательно кроссплатформенной).
Если бы подобное существовало и при этом на базе llvm (с его оптимизатором и набором платформ/архитектур), то можно было бы переходить хоть прямо сейчас. )
_>>Хех, вот смотри, ты вроде как не занимаешься написанием игр. Т.е. не относишься к их индустрии. Но при этом считаешь, что лучше их знаешь как им решать свои задачи. Тебе не кажется это несколько самонадеянным? ) Не допускаешь мысли, что всё же не "все вокруг неправы, один я знаю истину", а возможно наоборот? ) WH>Я знаю, как они работают. Нет там ничего сверхъестественного.
Да нигде нет ничего сверхъестественного. Просто в каждой области есть миллион мелких нюансов, которые определяют наиболее удобные пути решения. И не имея опыта в данной конкретной области несколько странно считать, что знаешь решение лучше тех, кто в ней работают.
Кстати, вот лично я тоже не занимаюсь созданием игр. Но мне как-то с сходу вспоминается, что почти во всех играх есть некая разновидность понятия локация/сцена/сессия. Переход между которыми прямо напрашивается на место для сброса накопленного пула памяти. )
Здравствуйте, Klikujiskaaan, Вы писали:
_>>>>Хех, вот смотри, ты вроде как не занимаешься написанием игр. Т.е. не относишься к их индустрии. Но при этом считаешь, что лучше их знаешь как им решать свои задачи. Тебе не кажется это несколько самонадеянным? ) Не допускаешь мысли, что всё же не "все вокруг неправы, один я знаю истину", а возможно наоборот? ) K>>>https://unity3d.com/ru/unity _>>О, ещё один с подобной ссылкой. И почему куча народа начинает её совать во всех темах, даже не попытавшись разобраться самому? ) _>>Отвечаю в очередной раз: Unity написан на C++! K>Нет, рантайм написан на С.
Да хоть на Фортране) Главное, в контексте нашей дискуссии, что на языке без GC. )
Здравствуйте, VladD2, Вы писали:
WH>>По крайней мере я до сих пор не слышал ни об оlном генераторе парсеров который создавался бы как инструмент для создания промышленных решений не уступающих рукописным парсерам. VD>http://www.antlr.org/about.html http://thesz.livejournal.com/1486500.html
Я тут бьюсь с ANTLR4, который отдельные файлы с VHDL кодом разбирает ужасающе долго (со скоростью в 8К байт в секунду и даже меньше). Это, натурально, беда.
В общем далеко ему ещё до промышленного качества. А то что его используют так это от того что остальное ещё хуже.
VD>Вот чтобы его написать я пару реализаций в помойку пустил. И первые как раз с парсером возились, а не с АСТ-мо. VD>Сейчас уже гляжу и подробностей не помню. Помню только, что от души потрахался.
Когда с парсером возились там вообще беда была. Любое изменение алгоритма восстановления и генерация ошибок ломалась.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
VD> public Value : T
VD> {
VD> get {
VD> match (this) {
VD> | Some (x) => x
VD> | None =>
VD> throw System.ArgumentException ("option.Value")
VD> }
VD> }
VD> }
VD>
VD>Как говорится, найдите 10 отличий.
Так тоже нельзя.
Вангую, что эта хрень нужна исключительно для интероперабельности с другими ООП-языками на дотнете.
Но в option хоть есть ср-ва правильного использования, бо это ж variant! ))
Даже взять извлечение дефолтного значения, вот это правильный код (option[T]):
public WithDefault (val : T) : T
{
match (this) {
| Some (v) => v
| None => val
}
}
А вот за это руки поотрывать и отлучать от программирования на веки вечные (ValueOption[T]):
public GetValueOrDefault() : T { _value }
Это ты писал?
Ты что творишь-то?
Это же одна из бед дотнета как такового, что некое "дефолтное значение" некоей прикладной структуры может являться не валидным значением, т.е. источником ошибок.
Тебе же специально дали иммутабельный по-умолчанию Nemerle, чтобы ты эффективно боролся именно с подобного рода проблемами — не создавал неинициализированных значений. И тут ты такой — а нате вам протаскивание проблемы прямо в вашу иммутабельность.
V>>В том-то и беда для ООП-языков, что тип вариант нормально не выразить VD>Очередной раз несешь околесицу. С точки зрения использования разницы нет.
Разница есть с т.з. безопасности.
Ты действительно НЕ понял вот этой фразы, относительно сценариев Optional для ООП-языков?
именно в случае Optional объявление предварительно "пустого" extractedValue убивает всю идею на корню
И ты действительно НЕ понял вот этого паттерна для ООП-языков:
Пока что мне очевидно, что происходящее в твоём собственном коде надо объяснять ТЕБЕ.
V>>Но ведь именно в случае Optional объявление предварительно "пустого" extractedValue убивает всю идею на корню. V>>Не видишь разве сам? VD>Вижу, что ты в очередной раз трепишься вместо того, чтобы просто спросить у знающих людей.
Началось...
Я читал знающих людей, исследующих системы типов и безопасность кода. Это засчитывается за "спросить"? ))
В твоём коде безопасностью и не пахнет.
VD>Та же фигня у тебя с GLR и всем остальным.
Продолжается...
Конкретно ты пока со своим простейшим OptionValue еще не разобрался — что и как ты наделал.
Какой там в опу GLR...