Здравствуйте, vdimas, Вы писали:
V>MS SQL справлялся с такими джоинами еще с версии 6.5, т.е. с 96-го года, чем нехило меня удивил. V>Бо до этого я наблюдал в других диалектах SQL только джоины по строгому равенству inner, либо outer left/right. V>FULL не считается, бо он во многих диалектах получается "сам" и без join. V>Собсно, удивило то, что в MS SQL план запроса не зависел от того, использовалась ли для обсуждаемых неординарных соединений конструкция join, или простое where. V>Т.е. это уже было дело вкусовщины автора запроса.
Отож. Известный факт, во всех учебниках по проектированию RDBMS описан. V>Но факт остаётся фактом, такое соединение по групповой операции заметно тяжелее, чем обычный джоин. S>>Вот тут было бы уместно показать определение таблиц, запрос, и его план. V>Ничего военного там нет — обычный составной кластерный индекс {код_валюты, дата_изменения_курса}, бо ширина индекса практически равна ширине таблицы.
Ещё раз поясню природу вопроса: "тяжесть" запроса определяется исключительно операциями, из которых состоит план запроса.
Удивительно не то, что MS SQL порождает одинаковый план для where, для join, и для subselect.
А то, что при грамотном построении индексов, порождается одинаковый план что для =, что для max(...) where ...<=...
Не надо мне рассказывать про то, как вы боролись с замедлением таких запросов.
Лучше расскажите, откуда берётся замедление в пару секунд при джойне с историческими данными.
На примере плана запроса "если бы курсы были на каждый день" и плана запроса для "но курс на данную дату нужно вычислять по max(change_date) where change_date<=doc_date."
Ибо не должно там быть никаких тормозов, про которые вы рассказываете.
Оффтоп: поймите, когда я вижу вот такие вот фразы в форумных постах, то возникает ощущение катастрофической разницы во владении предметом между мной и автором поста.
И мне каждый раз интересно — вдруг это ятупой чего-то интересного не знаю о СУБД. Не разочаровывайте меня — покажите, откуда могут взяться тормоза в современном движке, который уж 15 лет как перестал выполнять full scan в таких подзапросах.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, Serginio1, Вы писали:
S>> Поиск на == и <= используют один и тот же алгоритм половинного деления по индексу.
V>Это при запросе одиночного значения. V>При джоинах на индексе этот алгоритм для каждой строчки выполняется не в "чистом" виде, а лишь по интервалу от предыдущего найденного значения (соединяются-то сортированные наборы), т.е. в случае простого join== почти всегда сводится к операции перехода на следующую строку, если текущая строка основного ключа не подошла строке внешнего ключа.
Да и это ерунда, так ка данные находятся в кэше. Это малая величина по сравнению с основным временем поиска.
S>>Сложность одинакова. Только при курсе на каждый день нужно заполнять все дни (выходные, праздники когда торги не проходят), что увеличивает размер таблицы и индекса
V>Ну как же одинаковая, если в случае нестрого равенства надо сравнивать даты у двух соседних строчек? )) V>Это совсем другой алгоритм перебора строк.
Еще раз я для чего тебе ссылку то давал. При поиске останавливается на месте которое меньше или равно.
Почитай алгоритм. Он кстати используется и для вставки если ключ не найден. V>Если первый алгоритм всегда присутствует в базе выполненым на "железячном" уровне, то второй выполняется по классике жанра — через честные параметрические сканы таблиц по индексам и запуски "скриптов" проверки условий (привет метаинформации и всему, что с ней связано), бо таких различных условий, отличных от базовых inner и outer join — их потенциально бесконечный комбинаторный набор.
Угу. Проблема может быть только при использовании Хэш таблиц. Но если ты посмотришь на результвты там разница в 4 раза максимум. Но хэш таблицы применимы только для небольших размеров данных
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
V>>При джоинах на индексе этот алгоритм для каждой строчки выполняется не в "чистом" виде, а лишь по интервалу от предыдущего найденного значения (соединяются-то сортированные наборы), т.е. в случае простого join== почти всегда сводится к операции перехода на следующую строку, если текущая строка основного ключа не подошла строке внешнего ключа. S>Да и это ерунда, так ка данные находятся в кэше. Это малая величина по сравнению с основным временем поиска.
Эти рассуждения устарели лет эдак на 15.
Мы тут уже разбирали, что для десктопов уже доступны относительно быстрые RAID-технологии поверх SSD.
Относительно скорости оперативной памяти, разумеется, бо они уже примерно одного порядка.
Поэтому уже относительно давно можно рассуждать о "чистой" стоимости алгоритмов, без разбиения их на "бесплатную" часть поверх RAM и "платную" часть доступа к диску.
Усё, обе части одинаково платные.
Отсюда и пляшем.
S> Еще раз я для чего тебе ссылку то давал. При поиске останавливается на месте которое меньше или равно.
Или на месте, которое больше или равно — это зависит от конкретного вида алгоритма бинарного поиска: "Upper Bound" vs "Lower Bound".
Или возвращает две позиции, между которыми должно находиться искомое значение — бо классический алгоритм "binary search" оперирует парой индексов.
Разновидности Upper/Lower Bound возвращают один индекс, т.е. отличаются лишь своей концовкой, когда принимается решение — какой из двух результирующих индексов выкинуть.
В любом случае, когда речь идёт о "слиянии" отсортированных последовательностей, то применять эти алгоритмы в чистом виде — нубство.
Например, есть разновидности слияния с двух концов таких последовательностей одновременно, бо в этом случае на каждой итерации область поиска сужается.
И, хотя с математической верхний предел сложности у такого алгоритма такой же, но нижний резко отличается, в том числе из-за чисто "механических" причин — более близкие индексы более близко расположены на диске и в памяти.
S>Почитай алгоритм. Он кстати используется и для вставки если ключ не найден.
Здравствуйте, Serginio1, Вы писали:
V>>Ну да, если сравнивать с Y-комбинатором, то yeld выглядит катастрофически предпочтительней, как грится. )) S>Основной идеей yeld было использование энумераторов для рекурсивного обхода. S>В свое время испльзоваль файберы. Вот это было катострофично!
Отнюдь.
Класс statefull-continuation прямо по-определению более мощный, чем stateless.
В тот момент, когда переключение контекста становится дешевле суммарных затрат на переключение цепочек ДК-автоматов stateless coroutines, их statefull-родственники начинают заруливать со страшной силой, отрываясь практически бесконечно по эффективности по мере усложнения самих алгоримов.
Например, при обходе обычного дерева, уже при глубине примерно от 3-х уровней statefull не уступает stateless.
И далее вчистую его обходит, по мере роста глубины иерархии.
Здравствуйте, Sinclair, Вы писали:
V>>Собсно, удивило то, что в MS SQL план запроса не зависел от того, использовалась ли для обсуждаемых неординарных соединений конструкция join, или простое where. V>>Т.е. это уже было дело вкусовщины автора запроса. S>Отож. Известный факт, во всех учебниках по проектированию RDBMS описан.
Это ты какие-то уже поздние учебники имеешь виду, походу. ))
Такой join Оракл научился только с 5-й версии, а до него умел только DB2.
Собсно, не смотря на свою объективную тормознутость относительно конкурентов и непростыми процедурами обслуживания, MS SQL 6.x натурально подкупал удобством/всеядностью своего диалекта. Жрал всё подряд.
"Снижал порог входа", как стали говорить чуть позже.
Хотя, из-за такого "сниженного порога", когда рынок труда именно MS SQL наводнили полуграмотные админы (т.е. ушедшие в ДБА по причине недостаточно успешной карьеры "честным программистом") и это реноме "недостаточно несерьёзной БД" висит над MS SQL до сих пор. ))
V>>Ничего военного там нет — обычный составной кластерный индекс {код_валюты, дата_изменения_курса}, бо ширина индекса практически равна ширине таблицы. S>Ещё раз поясню природу вопроса: "тяжесть" запроса определяется исключительно операциями, из которых состоит план запроса. S>Удивительно не то, что MS SQL порождает одинаковый план для where, для join, и для subselect. S>А то, что при грамотном построении индексов, порождается одинаковый план что для =, что для max(...) where ...<=...
Ясно ))
Ты опять тянешь нас в "медленные диски".
Не вырос еще из этих штанишек?
S>Не надо мне рассказывать про то, как вы боролись с замедлением таких запросов.
Я? ))
У меня вообще курсы в клиентском кеше лежали.
Я не мог позволить себе такие вычисления на серваке.
S>Лучше расскажите, откуда берётся замедление в пару секунд при джойне с историческими данными.
От рандтрипа на клиента, речь же была про 1С.
S>На примере плана запроса "если бы курсы были на каждый день" и плана запроса для "но курс на данную дату нужно вычислять по max(change_date) where change_date<=doc_date." S>Ибо не должно там быть никаких тормозов, про которые вы рассказываете.
Ибо, сорри, но это нупство уже какое-то.
В первом случае мы имеем обычный внешний ключ и "железную" прямо в базе реализацию join по внешнему ключу.
Кароч, твои рассуждения имеют право на жизнь, если ты рассуждаешь сугубо о дисках.
Но курсы всегда лежат в оперативке, бо набор их смешной.
Указанная операция слияния по диапазонам технически медленнее операции слияния по внешнему ключу.
S>Оффтоп: поймите, когда я вижу вот такие вот фразы в форумных постах, то возникает ощущение катастрофической разницы во владении предметом между мной и автором поста.
Разумеется, ты же ленишься проверять — просто из пальца себе что-то насосал и умничаешь.
Т.е. ты себе вообразил некую модель и согласно твоей модели — это одно и то же.
А то, что твоя модель не дотягивает и до 1% от фактической — всегда игноришь.
Я уже на второй подобный пост отвечаю.
Де-факто ты НИКОГДА не выжимал из базы максимальное быстродействие.
Твои примеры увеличения чего-то там в 50 раз всегда смешны.
Это как у меня по работе, например, ускорить операцию обработки из исходных в лоб 250 микросекунд до 20-ти микросекунд — это легко.
Но из 20-ти получить 12 — уже на порядок сложнее.
Из этих 12 получить затем 5-6 — в мире десятки людей всего, которые так могут.
Без ложной скромности.
Ты всегда хвастаешься первым сценарием, чем делаешь смешно.
Там даже не само это хвастовство смешно, а твоя попытка экстраполяции опыта из первого сценария на все остальные сценарии.
Это уже натуральная узколобость, сорри.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, gandjustas, Вы писали:
V>>>1С тормозит при активном использовании т.н. "временнЫх данных". V>>>Современные СУБД не заточены под этот специальный вид данных, они хранятся просто как последовательность значений (просто строки в таблицах), например, изменений курсов валют на определённые даты. V>>>Когда такие данные в логике не участвуют, то там быстродействие примерно как везде. G>>В 1С есть временная метка для записи, джоинить по ней с таблицей курсов не сильно большая проблема.
V>Это если бы курсы были на каждый день, но курс на данную дату нужно вычислять по max(change_date) where change_date<=doc_date. V>В этом месте начинаются тормоза.
Почему начинаются?
Предположим что там всетаки есть индекс по (change_date, rate), потому что если нет, то дальше обсуждать нечего.
Даже если план запроса такой, что для каждой строки документа честно бежит по всему индексу, то разве индекс полностью не умещается в память?
Я вот год назад примерно делал отчет по базе 1С, где делал этот самый джоин. Тормозов не заметил, отчет строился за 3 года (около 3м проводок) с пересчетом сумм. Но у меня в этой базе индексы нужные были.
G>>При этом 1С умудряется тормозить не на аналитических запросах, а на обычных проводках. V>Смотря на каких. Для "обычной проводки" надо рассчитать сначала все цифры, бо "обычная проводка документа" — это порой до десятка проводок по разным бух.счетам в рамках одной транзакции, вот на этом рассчёте может и тормозить, если сам рассчёт нетривиальный на клиентской стороне.
Вот и я про то же. Операций для БД немного, а при проведении документа 1С аж подвисает. Может и не в базе дело.
V>>>Но речь была именно о хинтах — что там выходит при прочих равных? V>>>Так вот, без хинтов блокировок в запросе всё тормозит еще больше. G>>Если запрос кривой, его можно попытаться ускорить хинтом, но факт кривости запроса не отменяет.
V>Ясно. )) V>Похоже, ты не в курсе, зачем хинты вообще нужны.
Я знаю для чего они НЕ нужны. Для всех приведенных тобой примеров — не нужны однозначно.
V>>>Ну так отзвучь кол-во транзакций в секунду и примерный объем данных? G>>2500 запросов в секунду (при тесте) на базе в 100 с чем-то гб. Общий объём таблиц в 30 млн строк с несколькими джоинами.
V>Общий объем таблиц не интересен, интересен по которым происходят одновременные/параллельные изменения и выборки. V>Бо если из миллионов строк ты постоянно запрашиваешь/изменяешь только десятки тыщ их последних, то не считается. V>Мало ли что там на диске хранится.
Это была СЭД, данные меняются по активным задачам, которых по оценке около 1% от такого объема было.
G>>Этого заказчику хватило с запасом в 10 раз.
V>Ну вот у меня были цифры в 97-м году на однопроцессорном Пентиум-3 500 МГц порядка 180 запросов в секунду для системы учёта предприятия.
А у меня 19 см...
Не показывая запросов и схемы цифры вообще ни о чем.
Странно что ты вообще за цифры цепляешься, если такой специалист.
Здравствуйте, gandjustas, Вы писали:
G>>>Если запрос кривой, его можно попытаться ускорить хинтом, но факт кривости запроса не отменяет. V>>Ясно. )) V>>Похоже, ты не в курсе, зачем хинты вообще нужны. G>Я знаю для чего они НЕ нужны. Для всех приведенных тобой примеров — не нужны однозначно.
Да не, своё понимание хинтов ты уже обрисовал (оставил в процитированном).
Т.е. ты в эту область еще ни разу не погружался и спорить с тобой смысла нет.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, gandjustas, Вы писали:
G>>>>Если запрос кривой, его можно попытаться ускорить хинтом, но факт кривости запроса не отменяет. V>>>Ясно. )) V>>>Похоже, ты не в курсе, зачем хинты вообще нужны. G>>Я знаю для чего они НЕ нужны. Для всех приведенных тобой примеров — не нужны однозначно.
V>Да не, своё понимание хинтов ты уже обрисовал (оставил в процитированном). V>Т.е. ты в эту область еще ни разу не погружался и спорить с тобой смысла нет.
Конечно нет, потому что у меня в других областях знания достаточные, чтобы хинты не применять. А у тебя — нет, поэтому ты считаешь, что без них — никак.
Это как пытаться оператору комбайна рассказывать о хитростях правильной заточки косы для уборки урожая.
Здравствуйте, gandjustas, Вы писали:
V>>Да не, своё понимание хинтов ты уже обрисовал (оставил в процитированном). V>>Т.е. ты в эту область еще ни разу не погружался и спорить с тобой смысла нет. G>Конечно нет, потому что у меня в других областях знания достаточные, чтобы хинты не применять.
Достаточные для 2500 запросов в секунду на современных мощностях? ))
У любого недоучившегося студента (или даже студента непрофильных специальностей) они ровно такие же "достаточные".
G>А у тебя — нет, поэтому ты считаешь, что без них — никак.
Не угадал.
Я считаю проще: решение зависит от задачи.
Твои решения показывают уровень решаемых тобой задач.
G>Это как пытаться оператору комбайна рассказывать о хитростях правильной заточки косы для уборки урожая.
Это как оператор комбайна будет рассказывать сказки разработчику комбайнов, почему он не асилил инструкцию и не смог отключить понижайку при движении по асфальтированной дороге, а пытался ехать как по привычной ему рыхлой.
Здравствуйте, vdimas, Вы писали:
V>>>что тебя смущает-то? I>>Меня смущает, что ты свою версию никак объяснить не можешь
V>Я объяснил — есть сценарий последовательности запросов вместо одного.
Непонятно, что это такое. Каким именно образом это чудо будет учитывать индексы ?
Здравствуйте, Sinclair, Вы писали:
S>Оффтоп: поймите, когда я вижу вот такие вот фразы в форумных постах, то возникает ощущение катастрофической разницы во владении предметом между мной и автором поста.
И чего же ты себя тут не забанил?
S>И мне каждый раз интересно — вдруг это ятупой чего-то интересного не знаю о СУБД.
Да не, просто опытный манипулятор, не может не наводить тень на плетень.
Правда, в итоге умудрился сам себя обмануть:
— тут утверждаешь, что для случаях "простого индекса" и для случая FK мы всё-равно имеем "один и тот же план запроса" (С), позволяя себе на основании этого лишь утверждения рассуждать о квалификации собеседника.
— а уже рядом ты до хрипоты утверждаешь, что наличие FK способно кардинально повлиять на производительность.
Но и там тоже "работаешь" ниже пояса, со своим свинством: "обычно так делают невежды".
И вроде бы не назвал коллегу невеждой прямо, но при этом дал понять окружающим, что с большой вероятностью коллега, таки, невежда. Бо "обычно".
Вот сие свинство.
Т.е. я не говорю, что ты сам свинья, просто намекаю на большую такую вероятность, подражая твоим попыткам завуалировать прямые наезды через якобы коссвенные.
В сухом остатке, сам с собой споришь в разных подветках одной темы и сам себя обозвал невеждой и "катастрофически не владеющим предметом".
===================
Отчего произошёл такой знатный залёт?
— потому что каждый раз тщательно уходишь от технических подробностей.
Что характерно, почему-то уходишь от разных их, да еще во втором случае как на аргумент умудрился сослаться на ту самую подробность, от которой тщательно дистанцировался в первом случае. Ну и кто ты после таких "трюков"? ))
А я знал! (С)
вот если бы ты не бегал, то нормальная последовательность рассуждений вырисовывается такая:
Ты (для случая диапазонов дат):
— считаю, что с точки зрения оценки сверху трудоёмкости алгоритма они оба имеют одинакову аппроксимацию, особенно с точки зрения числа обращений к диску.
(твои обычные рассуждения)
Я:
— тождественость оценки сверху не принципиальна с т.з. современных ресурсов, их характеристик и обычного объема истории курсов валют. Разница в подробностях реализации более катастрофическая, т.е. разница оценки снизу, и составляет не проценты, а разы, порой до порядка.
Причины-то известные.
Самая первая из них — если рассматривать "точный" FK vs "неточный" max(change_date) where change_date<=doc_date — первый сценарий слияния можно выполнять по отсортированным регионам независимо, т.е. единая отсортированная последовательность дат и док-тов не нужна.
В первом случае можно в каждый момент времени рассматривать лишь некий домен (область значений), т.е., можно как оптимизировать алгоритмы чтения областей данных с диска/памяти (пользоваться допустимостью перестановки последовательности чтения страниц и/или их регионов), так и вовсе распараллелить вычисления по ядрам процессора — ведь у нас на руках есть кое-какие важные гарантии относительно даных, не? Имея на руках гарантии целостности FK мы можем избавиться от лишних двух проверок на каждом цикле (через скриптовый движок, на секундочку), и в каждый момент времени можем рассматривать лишь одну запись (кардинально увеличивая параллелизм всей базы, на секундочку).
А второй сценарий всегда требует единой непрерывной отсортированной последовательности дат для целей слияния, хотя в выходном результате такая полная сортировка может быть не нужна вовсе, скажем, при надобности вычислений лишь агрегатов.
На фоне всего этого вижу спекуляцию (или просто делаешь вид, что не понимаешь), насчёт того, что inner join по FK — он в базе имеет "железную" наиболее эффективную реализацию для большинства популярных сценариев — как раз по числовым типам данных, к которым относятся так же даты.
На подобные вещи ты рядом тщательно намекал — про "семантическую оптимизацию".
Но здесь тщательно делал вид, что таких вещей в природе не существует.
Смишно.
S>Не разочаровывайте меня — покажите, откуда могут взяться тормоза в современном движке, который уж 15 лет как перестал выполнять full scan в таких подзапросах.
Да не, это ты разочаровываешь откровенным передёргиванием:
1. RANK, OVER, PARTITION появилась с версии MS SQL 2008, т.е. широко могла быть освоена индустрией не ранее 2009-2010-х годов (никто же не кинется срочно переписывать имеющийся код, верно?)
2. В любом случае, такие выражения эквивалентно выразимы через join или where и обсуждаемое изначально условие, т.е. это вопрос вкуса, а не "плана запросов"; т.е. мимо более чем полностью — обычный пук в лужу;
3. ответ по ссылке содержит ошибку.
Ты не обратил на п.3 внимание?
Или обратил, но понадеялся, что мы не обратим?
Оба варианта для тебя крайне плохи, после твоих-то разлагольствований о "катастрофической разнице" и "невежестве".
Тот самый случай, когда два раза подряд в лужу со всего разгона — плюх.
Здравствуйте, Sinclair, Вы писали:
S>Понятно, что программист-фанатик может написать версию этого кода и на C#, но это — скользкий путь: в коде всё меньше остаётся от исходной задачи, и всё больше добавляется подробностей реализации.
Когда средствами LINQ можно генерить новый LINQ, язык обязательно должен называться C##
Здравствуйте, Sinclair, Вы писали:
V>>Это тонкости реализации, тут x может быть даже ф-ией генерируемого типа-замыкания: V>>Enumerable<int> x(args) => some_predicate(args) ? 1 : x(some_decrement(args)); V>>К тому же такой код более эффективный, чем рекурсия через делегат. S>Я не понимаю этот код. Каким образом мы будем использовать такой x в последующих выражениях?
Через очередной from y in x select foo(y).
S>>>проблема сводится к невозможности автовывода типов для частично рекурсивных функций.
V>>Разве? V>>
V>>var y = (int x) => x < 1 ? 1 : x * y(x-1);
V>>
V>>Сигнатуру делегата int y(int x) тут вывести легко. S>Вам — да. Компилятору — нет. Тут же уравнение на типы. Y — это most common type из двух типов: int и тип выражения (int * Y).
И выражения тернарного оператора ? 1.
S>Покажите, почему Y не может быть double или long.
Может.
Дело за конкретным алгоритмом вывода типов.
Например, можно брать наиболее специализированный подходящий ковариантный тип и наименее специализированный контрвариантный.
В данном случае это одозначный int для входящего параметра, бо там выражение x-1, для double или long требуется более широкий класс, некий "Number".
Для возвращаемого значения, если бы не было тернарного оператора-подсказки, можно было бы взять наиболее общий тип, для которого определен оператор умножения на int.
Т.е. тут потребуются две доработки:
— Ввести иерархию Number <- IntNumber <- Int.
— запретить оператор умножения, скажем, м/у int и float, пусть программист явно приводит аргументы умножения к нужному типу, как в Хаскеле. ))
S>Дело не в этом. В данном месте компилятор ломается потому, что не может сделать выбор между Func<T, R> и Expression<Func<T,R>>.
Где он не может сделать такой выбор?
Если коллекция в выражении from приводима к IQueryable, то на выходе Expression, иначе коллекция должна быть приводима к IEnumerable, иначе ошибка компиляции.
V>>Здесь отсутствует проверка на выход из рекурсии, поэтому она бесконечна, но можно взять какой-нить валидный рекурсивный алгоритм, из показанных мною ранее. V>>(Тип переменной t в этом выражении достоверно известен) S>Так не получится. Если посмотреть цепочки методов, в которые превращается query, то там x в разных местах отношения друг к другу не имеет. S>Когда мы пишем S>
S>from a in A
S> let x = expr(a)
S>select a + x;
S>
S>то первый и второй x отношения друг к другу не имеют. В том методе, где a+x, нет возможности передать в х какие-либо параметры, которые у нас были в предыдущем вызове.
Что значит "нет возможности передать в x какие-либо параметры"?
Это зависит от типа x, он может быть в том числе делегатом.
Здравствуйте, vdimas, Вы писали: V>Через очередной from y in x select foo(y).
Ок, понятно. То есть вы предлагаете в transparent identifier закапывать не значение T, а промис для него — Func<T>.
Сходу не могу оценить последствия. V>>>Разве? V>>>
V>>>var y = (int x) => x < 1 ? 1 : x * y(x-1);
V>>>
V>>>Сигнатуру делегата int y(int x) тут вывести легко. S>>Вам — да. Компилятору — нет. Тут же уравнение на типы. Y — это most common type из двух типов: int и тип выражения (int * Y). V>И выражения тернарного оператора ? 1.
Я и говорю — most common type из int (это тип единички) и типа, который получается при умножении int (это тип x) на этот неизвестный тип.
V>Может. V>Дело за конкретным алгоритмом вывода типов.
Совершенно верно. V>Например, можно брать наиболее специализированный подходящий ковариантный тип и наименее специализированный контрвариантный.
Из чего и чего? Давайте для начала построим формальный алгоритм нахождения всех возможных кандидатов в Y.
V>В данном случае это одозначный int для входящего параметра, бо там выражение x-1, для double или long требуется более широкий класс, некий "Number".
Откуда тут взялся "однозначный int"? Да, x мы типизировали. x-1 — тоже типизировали. А дальше у нас тупик: сигнатура для y имеет вид Y y(int x), V>Для возвращаемого значения, если бы не было тернарного оператора-подсказки, можно было бы взять наиболее общий тип, для которого определен оператор умножения на int.
1. Тернарный оператор нам не подсказывает ничего, кроме того, что для типа результата должно быть определено неявное преобразование из инта.
2. Что такое "наиболее общий тип" из несвязанных между собой типов? Ок, для встроенных числовых типов мы можем попытаться построить иерархию вроде byte > short > int > long > float > double.
Тогда для решения уравнения мы сможем выбрать int (на самом деле, конечно же, выбираем "наиболее специализированный", а не наиболее общий — иначе у нас все уравнения с участием нумериков будут сваливаться в double)/
Но ведь никто не запрещает определять свои типы — и у нас запросто могут быть одновременно доступны Rational и Complex. Для обоих есть умножение на int, для обоих есть неявное приведение от инта.
Кто из них будет "наиболее специализированным"?
V>Т.е. тут потребуются две доработки: V>- Ввести иерархию Number <- IntNumber <- Int. V>- запретить оператор умножения, скажем, м/у int и float, пусть программист явно приводит аргументы умножения к нужному типу, как в Хаскеле. ))
Пока что выглядит как адское усложнение "простого" кода ради поддержки экзотических сценариев. В которых как раз не так то и трудно выполнить ручное приведение аргументов.
S>>Дело не в этом. В данном месте компилятор ломается потому, что не может сделать выбор между Func<T, R> и Expression<Func<T,R>>. V>Где он не может сделать такой выбор?
Вот прямо здесь:
var f = (int i)=>i+1;
V>Если коллекция в выражении from приводима к IQueryable, то на выходе Expression, иначе коллекция должна быть приводима к IEnumerable, иначе ошибка компиляции.
Совершенно верно. Там, где компилятор может увидеть, что ему требуется, он не требует руками выписывать "сигнатуру делегата".
Например, когда мы передаём нашу лямбду в метод Select:
from i in new[]{1, 2, 3} select i+1; // даже тип i указывать не надо, не говоря уже о выборе между Expression<Func<int, int>> и Func<int, int>
V>Это зависит от типа x, он может быть в том числе делегатом.
Тогда нам придётся писать x(), а не x. Либо переделать язык, разрешив опускать скобки при семантике вызовов, но тогда придётся заставить писать что-то типа &x во всех местах, где мы хотим передать сам делегат, а не результат его вызова.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
V>>Т.е. тут потребуются две доработки: V>>- Ввести иерархию Number <- IntNumber <- Int. V>>- запретить оператор умножения, скажем, м/у int и float, пусть программист явно приводит аргументы умножения к нужному типу, как в Хаскеле. )) S>Пока что выглядит как адское усложнение "простого" кода ради поддержки экзотических сценариев.
Ради поддержки автоматического вывода типов.
Иначе Хиндли-Милнер не сработает. Или его более продвинутая реализация, как в Немерле.
А что делать? Автоматический вывод типов — это всегда контракт на некие ограничения, т.е. однозначно читаемый договор м/у программистом и компилятором, чтобы программист в уме выводил типы так, как компилятор.
Все эти неявные упомянутые преобразования — они ведь принципиально работают лишь в ситуациях, когда компилятору известно из какого в какой тип надо приводить.
Собсно, поэтому и потребовалось допиливать Хиндли-Милнер для Немерле, бо в чистом виде он не работает при наличии всей этой автоконверсии.
Пришлость ввести дополнительные стратегии для сценария ковариантности и контрвариантности и еще там что-то (когда-то любопытсвовал, но уже очень давно).
V>В которых как раз не так то и трудно выполнить ручное приведение аргументов.
Дык, я же сразу предложил снабдить оператор let возможностью указывать тип при надобности.
(Хотя, это можно и сейчас через явное приведение типа выражения, просто не так консистентно, что ле...)
Думаю, рано или поздно такая возможность появится.
Просто еще не дошли какие-нить шаловливые ручки до linq, наподобие того, как дошли когда-то у Александреску до С++. ))
Там если под выражение let подпихивать функциональный тип (в том числе возвращающий списки или тоже функциональные типы), то появляется еще пара степеней свободы у этой комбинаторики.
V>>Если коллекция в выражении from приводима к IQueryable, то на выходе Expression, иначе коллекция должна быть приводима к IEnumerable, иначе ошибка компиляции. S>Совершенно верно.
Но это не из-за линка, а потому что C# так выбирает перегрузки сигнатур — он выбирает наиболее специализированную перегрузку.
Чтобы в этом месте не поломался весь линк, классы расширения Enumerable и Queryable должны жить в одном неймспейсе и в одной сборке, аки неразлучные близнецы-братья.
S>Там, где компилятор может увидеть, что ему требуется, он не требует руками выписывать "сигнатуру делегата".
Иногда эту сигнатуру и невозможно описать только лишь через "голое" описание функциональных типов, т.е. без именованных алиасов типов и рекурсивного в итоге определения.
Собсно, в этом и состоит моя претензия к типам-делегатам — их хотелось бы допилить до эдаких совместимых м/у собой "алиасов" функциональных сигнатур.
С другой стороны, в условиях JIT это резко замедлило бы проверку типов, т.е. такое "допиливание" хорошо лишь для полностью статической компиляции.
А так-то фокус дотнета в том, что свой бинарник мы могли скомпилировать с одним определением импортируемого типа, а в рантайме подсунуть другой.
V>>Это зависит от типа x, он может быть в том числе делегатом. S>Тогда нам придётся писать x(), а не x.
Это в том числе может помочь при выборе перегрузки метода-расширения при наличии конкурирующих альтернатив, как квадратные скобочки помогли при выборе твоих методов-расширений для алгоритма над изображением.
S>Либо переделать язык, разрешив опускать скобки при семантике вызовов, но тогда придётся заставить писать что-то типа &x во всех местах, где мы хотим передать сам делегат, а не результат его вызова.
Ну да. Это для иммутабельного ленивого окружения не важно что у нас там — ф-ия с арностью 0 или результат её вызова.
А тут важно всё, поэтому про опускание скобок — будем считать забракованным мысленным экспериментом.
Здравствуйте, vdimas, Вы писали:
V>Думаю, рано или поздно такая возможность появится.
Ну, возможность указывать тип x в let может и появится. А вот про поддержку recurrent lambdas — не факт. V>Там если под выражение let подпихивать функциональный тип (в том числе возвращающий списки или тоже функциональные типы), то появляется еще пара степеней свободы у этой комбинаторики.
Вот именно. Стоимость отладки — огромная, а выигрыш сомнителен. Не факт, что всё это нужно кому-то кроме пары гиков.
V>>>Если коллекция в выражении from приводима к IQueryable, то на выходе Expression, иначе коллекция должна быть приводима к IEnumerable, иначе ошибка компиляции. S>>Совершенно верно.
V>Но это не из-за линка, а потому что C# так выбирает перегрузки сигнатур — он выбирает наиболее специализированную перегрузку. V>Чтобы в этом месте не поломался весь линк, классы расширения Enumerable и Queryable должны жить в одном неймспейсе и в одной сборке, аки неразлучные близнецы-братья.
Ничего подобного. Никакой проблемы разнести Enumerable и IQueryable по разным неймспейсам или сборкам нету.
Единственное "но" — это риск получить неожиданный результат, когда ошибки компиляции нету, но все запросы выполняются на клиенте. (Enumerable в области видимости есть, а вот Queryable — нету).
И эту "проблему" было бы легко решить, включив все нужные сигнатуры не в Queryable, а в IQueryable — тогда бы у нас была гарантия того, что компилятор всегда выберет корректную реализацию, видя from p in DB.Products;
V>Иногда эту сигнатуру и невозможно описать только лишь через "голое" описание функциональных типов, т.е. без именованных алиасов типов и рекурсивного в итоге определения.
Это редкость.
V>Собсно, в этом и состоит моя претензия к типам-делегатам — их хотелось бы допилить до эдаких совместимых м/у собой "алиасов" функциональных сигнатур.
Они и так совместимы. Просто надо присваивать не так
Delegate1 x = ...;
Delegate2 y = x
А вот так:
Delegate2 y = x.Invoke;
V>С другой стороны, в условиях JIT это резко замедлило бы проверку типов, т.е. такое "допиливание" хорошо лишь для полностью статической компиляции.
Вообще бы не повлияло. Достаточно сделать так, чтобы компилятор C# видя y=x порождал MSIL для y=x.Invoke. И у нас волшебным образом начинает работать вся ко- и контра-вариантность, и прочие штуки — то есть мы не только бесплатно получаем совместимость делегатов в рамках одной сигнатуры, но и в рамках допустимой вариантности сигнатур.
V>А так-то фокус дотнета в том, что свой бинарник мы могли скомпилировать с одним определением импортируемого типа, а в рантайме подсунуть другой.
Это не работает чуть более, чем нигде. Способов заменить определение импортируемого типа без перекомпиляции импортирующего, не сломав всё нахрен, очень немного.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.