Re[9]: Использование tuple
От: Gaperton http://gaperton.livejournal.com
Дата: 11.10.09 14:08
Оценка:
Здравствуйте, Mirrorer, Вы писали:

G>>Я не говорю, что надо употреблять туплы в публично интерфейсе. Стилевое руководство Эрланга рекомендует применять в данном случае record-ы.


M>Имеется ввиду эта штука или что-то другое ?


Эта штука, пункт 6.1.

The record features of Erlang can be used to ensure cross module consistency of data structures and should therefore be used by interface functions when passing data structures between modules.
Re[10]: Использование tuple
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 11.10.09 17:12
Оценка:
Здравствуйте, Gaperton, Вы писали:

G>Это несколько меняет дело. Но только "несколько". Как это поможет мне отличить выходной параметр от параметра io_ — с побочным эффектом?


У которого с побочным эффектом — у него модификатор не out, а ref.
... << RSDN@Home 1.2.0 alpha 4 rev. 1245 on Windows 7 6.1.7600.0>>
AVK Blog
Re[5]: Использование tuple
От: Andrei N.Sobchuck Украина www.smalltalk.ru
Дата: 12.10.09 15:58
Оценка:
VD>Он имел в виду, что функцию ты описываешь один раз и если накосячил с именами параметров, то только в ней. А вызваешь ты ее 100 раз и накосячить с именами можешь неограниченно количество раз:

Именно это я и хотел сказать. Спасибо.
Я ненавижу Hibernate
Автор: Andrei N.Sobchuck
Дата: 08.01.08
!
Re[10]: Использование tuple
От: VladD2 Российская Империя www.nemerle.org
Дата: 12.10.09 16:39
Оценка:
Здравствуйте, Gaperton, Вы писали:

G>Это несколько меняет дело. Но только "несколько". Как это поможет мне отличить выходной параметр от параметра io_ — с побочным эффектом?


io — обозначаются ref и ref тоже нужно указывать при вызове.

G>Туплы решают именно эту проблему. Кроме того, с туплами это выглядит элементарно лучше и естественнее, чем префиксы. Разумеется, при поддержке паттерн-матчинга на них.


На самом деле ref и out попросту не удобны. Для них нужно объявлять отдельные переменные, да и вообще не красиво. Такие феньки как вложение вызовов функций уже не прокатывают. Плюс есть еще одна проблема. ref и out-параметры требуют, чтобы выражения были lvalue, так что даже если тебе возвращаемое значение не интересно ты не сможешь в ref-параметр передать значение фунции (без промежуточной переменной).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Использование tuple
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 12.10.09 22:24
Оценка: 56 (1) +1
Здравствуйте, Gaperton, Вы писали:

G>Ситуация, когда в тупле значения одного типа идут подряд, их много, и они не имеют натурального порядка — крайне редка. Я ни разу с такой багой не сталкивался. Натуральный порядок, например — обозначение временного диапазона — сначала ты пишешь startTime, потом endTime, и по названиям легко понять, что ты их перепутал.


Это зависит от предметной области. В MATLAB таких функций много. Я запускаю на выполнение математический расчет, который помимо основного результата может вывести еще кучу вспомогательных (побочных) результатов. Например,

  [b,bint,r,rint,stats] = regress(y,X,alpha)


В этом примере b — вектор точечных оценок коэффициентов линейного уравнения регрессии, bint — матрица интервальных оценок параметров линейной регрессии, r — вектор остатков, rint — матрицу доверительных интервалов остатков. Получаем, что все пять параметров это вектора/матрицы, которые вполне могут кодироваться одним типом

Похожие примеры из той же предметной области:

  [p,table,stats,terms] = anovan(...) 
  [h,atab,ctab,stats] = aoctool(...)
  [ndim,prob,chisquare] = barttest(x,alpha)
  [A,B,r,U,V,stats] = canoncorr(X,Y)
  [p,Cp,Cpk] = capable(data,specs)
  [class,err,posterior,logp] = classify(...)
Re[11]: Использование tuple
От: Gaperton http://gaperton.livejournal.com
Дата: 12.10.09 23:23
Оценка:
Здравствуйте, AndrewVK, Вы писали:

G>>Это несколько меняет дело. Но только "несколько". Как это поможет мне отличить выходной параметр от параметра io_ — с побочным эффектом?


AVK>У которого с побочным эффектом — у него модификатор не out, а ref.


А. Ну тогда действительно все в порядке. Запись с туплом и матчингом, однако, все равно удобнее — в данном случае достаточно одного модификатора ref. И все, никакого мусора. Синтаксический оверхэд (тм), сам понимаешь. Иметь возвращаемые значения слева от знака присваивания, где они и должны быть, более естественно, чем расставлять модификаторы-подсказки.

Впрочем, этим польза от туплов не ограничивается. Тупл также позволяет группировать аргументы функции, что сильно помогает в читабельности как вызовов, так и (особенно) тела функции, если применять с умом. Тупл может с той же целью применяться в теле функции.
Re[9]: Использование tuple
От: Gaperton http://gaperton.livejournal.com
Дата: 12.10.09 23:32
Оценка:
Здравствуйте, Mystic, Вы писали:

M>Это зависит от предметной области. В MATLAB таких функций много. Я запускаю на выполнение математический расчет, который помимо основного результата может вывести еще кучу вспомогательных (побочных) результатов. Например,


M>
M>  [b,bint,r,rint,stats] = regress(y,X,alpha)
M>


M>В этом примере b — вектор точечных оценок коэффициентов линейного уравнения регрессии, bint — матрица интервальных оценок параметров линейной регрессии, r — вектор остатков, rint — матрицу доверительных интервалов остатков. Получаем, что все пять параметров это вектора/матрицы, которые вполне могут кодироваться одним типом


Ну вот я пишу сигнатуру типа подобного вызова — синтаксис Эрланга.

-spec regress( y::vector(), X::vector(), alpha::float() )
    -> { b::vector(), bint::matrix(), r::vector(), rint::matrix(), stats::any() }


Все, тайпчекер даст по рукам при ошибке. И в чем именно проблема с таким туплом, можно поинтересоваться? Вот так — было бы сильно легче, да?

-spec regress( y::vector(), X::vector(), alpha::float(), bint::matrix(), r::vector(), rint::matrix(), stats::any() )
    -> b::vector()


Или все-таки вот так — легче, если иметь возможность сгруппировать три последних параметра в еще один тупл, и разбирать его в вызывающем коде там, где понадобиться, а не в точке вызова?

-spec regress( y::vector(), X::vector(), alpha::float() )
    -> { b::vector(), bint::matrix(), { r::vector(), rint::matrix(), stats::any() } }
Re[9]: Использование tuple
От: Gaperton http://gaperton.livejournal.com
Дата: 12.10.09 23:54
Оценка: +1
Здравствуйте, VladD2, Вы писали:

G>>Ситуация, когда в тупле значения одного типа идут подряд, их много, и они не имеют натурального порядка — крайне редка. Я ни разу с такой багой не сталкивался. Натуральный порядок, например — обозначение временного диапазона — сначала ты пишешь startTime, потом endTime, и по названиям легко понять, что ты их перепутал.


VD>Это все соглашения (практики) которых можно придерживаться, а можно не предерживаться. Пока над проектом работает мало народа квалификация которых высока все будет ОК. Когда же язык выходит на уровень мэйнстрима, то проблем не избежать. Так что лично я за решения которые полностью устраняют проблему или дают удобное и простое решение которое люди будут выбирать, что называется, из лени.


Удивительно, и как люди только на мэйнстримовом С программят — одни ведь соглашения, которых надо придерживаться. Про ассемблер вообще молчу. Ты против туплов, что-ли? Ты ясно скажи, с чем ты не согласен, а то я перестал понимать. Тон, вроде, дискуссионный, споришь с чем-то.

VD>Вот такие решения (в дизайне языка) мне нравятся. Это уже не соглашение, а правило которое нельзя обойти. Жаль, что оно сделано для весьма неудобной фичи.


Да пожалуйста. А мне нравятся удобные, простые и красивые решения — вроде туплов с матчингом.

VD>>>Вот это я и называю "дурацкие ретуальные действия и соблюдать кодекс чести". Проблем и я не имел, но тратил на это время которое мог бы потратить более интересно.


G>>Я не тратил на это много времени.


VD>А вот это, извини за резкость, сказки. Иначе скорость программирования на любом языке была бы одинакова, так как ко всему можно привыкнуть и "не иметь проблем".


Не извиню за резкость, ибо не вижу ни одной причины, почему бы тебе не вести себя прилично.

Я говорю не про "любой язык", и не про абстрактную скорость, а про то, что конкретно я не тратил много времени на управление памятью в С++. Мне виднее, сколько времени я тратил, и твои соображения про "любой язык" ничего не меняют.

G>>Один шаблонный класс MemberPtr для агрегации по указателю, который копирует контент при присваиваниях и конструкторе копирования, и удаляет в деструкторе — и все, никаких трат времени. Заодно — глядя на объявление класса, легко видно, где просто ссылка, а где агрегация. И никакого геморроя и потраченного времени.


VD>Не, батенька. Время ты тратишь при проектировании. Ты вынужден создавать более детальные решения. Вынужден продумывать иделогию владения и т.п.


Это да, я должен при проектировании делать невероятно сложную вещь — уметь отличать просто ссылку от агрегации (натуральное отношение "является частью"). В настоящую проблему, когда это действительно начинает отнимать время, это превращается не так часто, как тебе хотелось бы. Ну, я про себя говорю, естественно. У тебя, возможно, было не так — ну так мы же с тобой разные. Опыт разный, навыки. Все такое.

VD> Плюс ты вынужден заниматься ритуальными приседаниями.


А вот здесь ты ошибся — я не религиозен.
Re[10]: Использование tuple
От: VladD2 Российская Империя www.nemerle.org
Дата: 13.10.09 00:22
Оценка:
Здравствуйте, Gaperton, Вы писали:

G>Удивительно, и как люди только на мэйнстримовом С программят — одни ведь соглашения, которых надо придерживаться.


Херовенько. Иногда вместо решения основной задачи часами (а то и днями) баги ищут. Некоторые баги живут по 10 лет. Про ассемблер вообще молчу.

G> Ты против туплов, что-ли?


Я "за".

G>Ты ясно скажи, с чем ты не согласен, а то я перестал понимать. Тон, вроде, дискуссионный, споришь с чем-то.


Не согласен с тем, что нет проблем. Проблемы есть, но они не значительны. Рядом в теме я описал свои идеи на этот счет.

Ну, и собственно проблемы есть в основном в донтете, так как в нем нет структурной идетничности, и стало быть нельзя как следует реализовать записи.

VD>>Вот такие решения (в дизайне языка) мне нравятся. Это уже не соглашение, а правило которое нельзя обойти. Жаль, что оно сделано для весьма неудобной фичи.


G>Да пожалуйста. А мне нравятся удобные, простые и красивые решения — вроде туплов с матчингом.


Повторяться не буду. Мне тоже нравится. Но я идеалист, в некотором роде.

VD>>А вот это, извини за резкость, сказки. Иначе скорость программирования на любом языке была бы одинакова, так как ко всему можно привыкнуть и "не иметь проблем".


G>Не извиню за резкость, ибо не вижу ни одной причины, почему бы тебе не вести себя прилично.


А что неприличного в моих словах?

G>Я говорю не про "любой язык", и не про абстрактную скорость, а про то, что конкретно я не тратил много времени на управление памятью в С++. Мне виднее, сколько времени я тратил, и твои соображения про "любой язык" ничего не меняют.


Это все так говорят. Но на поверку все оказывается иначе. Скорость программирования даже при переходе на Яву (у которой с выразительностью огромные проблемы) через некоторое время сильно вырастает.

G>Это да, я должен при проектировании делать невероятно сложную вещь — уметь отличать просто ссылку от агрегации (натуральное отношение "является частью"). В настоящую проблему, когда это действительно начинает отнимать время, это превращается не так часто, как тебе хотелось бы. Ну, я про себя говорю, естественно. У тебя, возможно, было не так — ну так мы же с тобой разные. Опыт разный, навыки. Все такое.


Бестолковая работа, есть бестолковая работа. Опыт может ускорить ее, но не устранить.

VD>> Плюс ты вынужден заниматься ритуальными приседаниями.


G>А вот здесь ты ошибся — я не религиозен.


А ритуальные приседания — это не религия. Это — шаманство.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Использование tuple
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.10.09 06:24
Оценка: +1
Здравствуйте, Gaperton, Вы писали:
G>Зато всегда есть имена принимающих переменных, которые названы так, что все понятно. Этого мало?
Конечно мало. Возвращаясь к примеру с вызовом обычного метода — у тебя что, всегда имена фактических аргументов совпадают с именами параметров?
Если убрать имена параметров из стандартной библиотеки, оставив только _1, _2, _3, то как-то трудновато будет разобраться с тем, что происходит, не так ли?

G> Кроме того, это имеет вообще значение только в случае, если возвращаемые значения тупла одного типа. Такое бывает редко, и обычно однозначно трактуется, скажем:

G>Прямоугольник, или линия — ( x1, y1, x2, y2 ).
Отлично. Осталось понять, кто из них горизонтальный, а кто вертикальный. И x2 там в третьем члене или width.
G>Диапазон времени — ( startTime, endTime ).
Или всё-таки startTime, timeSpan?

G>Имена принимающих переменных в строке вызова — забыл, нет? На них смотреть религия не позволяет?

А если их перепутали?
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[7]: Использование tuple
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.10.09 06:24
Оценка: 10 (1)
Здравствуйте, VladD2, Вы писали:
VD>Не такая это и глупость. Особенно если тип можно было бы описать по месту.
VD>Что ты имеешь против такого синтаксиса:
VD>
VD>GetPersonInfo(id : int) : (Name:string * Addres:string * Age:int)
VD>

VD>Ну, а дальше хочешь к полям обращайся через точку:

VD>
VD>def p = GetPersonInfo(123);
VD>... p.Age;
VD>


VD>или декомпозицию в переменные делай, но чтобы при этом провлялись имена без учета регистра и наличия подчеркиваний в именах:

VD>
VD>def (name, _, age) = GetPersonInfo(123); // OK
VD>def (_, name, age) = GetPersonInfo(123); // Error
VD>

VD>Ну, и естественно типы описанные таким образом в разных библиотеках (в том числе и с динамической подгрузкой) считались идентичными.
Я бы тут постарался развернуть вызов задом наперёд. Так, чтобы code completion мне помогал. Что-то типа
GetPersonInfo(123) => (name, _, age);
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[11]: Использование tuple
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.10.09 06:24
Оценка: +2
Здравствуйте, VladD2, Вы писали:
VD>На самом деле ref и out попросту не удобны. Для них нужно объявлять отдельные переменные, да и вообще не красиво. Такие феньки как вложение вызовов функций уже не прокатывают. Плюс есть еще одна проблема. ref и out-параметры требуют, чтобы выражения были lvalue, так что даже если тебе возвращаемое значение не интересно ты не сможешь в ref-параметр передать значение фунции (без промежуточной переменной).
Кроме того, ref и out не поддерживают вариантность (из-за многократного присваивания).
Поэтому ты не можешь, к примеру, использовать object в качестве out-переменной для параметра, который тебе не нужен.
Т.е. вот такой синтаксис работает:
Mammal m = tigers["Sam"]; // var tigers = new Dictionary<string, Tiger>();

а вот такой — нет:
Mammal m;
if(tigers.TryGetValue("Sam", out m))
  return m;
else 
  return null;
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: Использование tuple
От: maxkar  
Дата: 13.10.09 10:19
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, maxkar, Вы писали:


M>>Да нет, с учебниками проблем не было. Особенно с учебниками математики. Я по ней олимпиадник был и пару раз участвовал в отборах на международный этап


VD>Вот и тут не будет. У тебя же не бывает проблем с чтение кода вроде "f(a, b)"? Ну, и с "def (a, b) = f();" не будет.


Тут в соседней ветке Sinclar привел хороший пример с прямоугольниками, так что могу утверждать: даже при чтении (проверке!) вызова функции в виде f(a, b) могут быть проблемы. Касается как раз тех самых прямоугольников. Например, g.drawRect(x1, y1, x2, y2) является местом потенциальной ошибки (опять же, нужно знать библиотеку или читать документацию). По сравнению с ним вызов g.drawRect(rect) гораздо читабельнее. Да, там нужно проверять, что rect инициализирован правильно, но он то инициализируется через rect.setWidth(width) или rect.setX2(x2), что читается и проверяется легче (глаз цепляется за rest.setWidth(x2) или rect.setX2(width)).

Где-то в ветке уже вывигалась идея, что такая проблема не существует, если элементы кортежа разных типов. С этим я согласен, при разных типах результата (с разной семантикой) путаницы не возникает (так же, как и при передаче параметров).

Теперь перейдем к математике. Стоит заметить, что выражения вида (a, b) = f() встречаются редко. Обычно идет a = f_a(), b = f_b(). Что-то не помню, чтобы из математических функций возвращалиь "пары и более объектов". Вектора — да, но это "объект вектор". Возврат прямоугольника скорее всего выглядел бы как "Пусть прямоугольник R — это .... Функция f находит прямоугольник r1, такой, что ...". Т.е. в математических текстах практически везде мы будем иметь "класс" (или запись с именованными полями). Вполне вероятно, что "кортежи" можно будет встретить только в областях, непосредственно соприкасающихся с IT. Это области, изучающие алгоритмы и их сложность. В этом случае возврат кортежа вместо класса или вызова нескольких функций может давать преимущества (например, по времени выполнения). В примере Partition, например, в математическом тексте, скорее всего было бы x1 = {x in X : p}, x2 = {x in X : (not p} (или x2 = X\x1). Если не брать в расчет сложность, я не вижу, где бы эта запись проигрывала записи с partition, возвращающей пару. Данная запись чуть длиннее, но явно говорит, что и где создается.

Второе замечание про математику — сравнивать типичную программу с учебником не совсем корректно. В них очень сильно различается акцент на правильности понимания читателем текста. Так, типичный учебник помимо определения содержит еще примеры и задачи. И цель этих примеров и задач — определить, что ученик понял определение/термин/абстракцию неправильно и откорректировать понимание. Причем такой "вспомогательный текст" составляет большую часть учебника. Сами формальные определения составляют меньшую часть. В типичной программе — наоборот. Большая часть — формальные определения (через детали реализации), меньшая — документация (если вообще есть). Это ближе к справочнику математических функций, который для большинства функций просто приводит их определение через другие функции. Ну да, понять, что же обозначает функция (ее физический смысл) можно, но гораздо тяжелее. Так и с программами — комментариев мало или они низкого качества, основная часть — определения функций. Встречаются исключения, но очень редко.

M>>Почему разумеется?


VD>Потому что это интуитивно. Везде в ФЯ ты будешь встречать конструкции вроде "head :: tail". Так что о том, что идет вначале ты даже задумываться не будешь. А если вдруг задумаешся, то есть описание метода доступное как в коментариях, так и в интеллисенсе (в IDE).

Конструкция вида head :: tail вполне нормальна. Она применяется к списку, который имеет порядок элементов. И "разрезать" список на части с получением частей (не только по одному элементу, а вообще в фиксированной позиции) как раз просто — у этих частей есть неявное отношение "порядка", определяемое "порядком" в исходном списке. В связи с этим интересный вопрос — какую сигнатуру должен иметь кортеж, который возвращает функция, откусывающая последний элемент списка. Должно ли это быть (list a, a) (из отношения порядка) или (a, list a) ("скаляр" и не "скаляр")? Но это мелочь на самом деле, вопрос на "консистентность" API, ни одно из решений не ухудшает читаемость (типы разные).

M>>Вот есть у нас список [1,2,3,4,5,6]. И есть какой-то предикат p. Как разделять будем? Да очень просто — читаем список слева направо и все, на что p дает true, вычеркиваем и записываем в другой спискок.


VD>Это какая-то извращенная императивная логика. Функциональная логика будет такая:

VD>1. Для пустого списаа [] вернем ([], []).
VD>2. Для не пустого списка x :: xs присоединим x к первому списку если x удовлетворяет предикату и ко второму если нет.
VD>3. Повторим рекурсивно операцию для остатка списка (xs).
А на втором шаге — та же самая "извращенная" императивная логика. И ведь совершенно нет объяснения тому, почему на этом самом шаге к первому списку х присоединяется тогда, когда предикат выполняется, а не наоборот. А ведь если изменить это правило, то получится мой вариант

Теперь вспомним про "отношение порядка элементов списка" и то, что кортеж является упорядоченной парой (в данном случае). Для большинства функций, работающих со списком, порядок на результате (в возвращаемых кортежах) логично выводится (индуцируется) из отношения порядка на исходном списке (аргументе). Ну тот же partition по позиции в списке, например. Попытка распространить "порядок" исходного списка на partition по предикату дает еще два интересных упорядочивания результата.
Первый — первый элемент кортежа содержит первый элемент исходного списка.
Второй — последний элемент кортежа содержит последний элемент списка.
Такое упорядочивание (допустим для определенности первый вариант) очень хорошо. Оно, например, очень хорошо обобщается с pred : T -> bool на pred : T -> T1. Семантика "обобщенного partition" — разбить список элементов на классы эквивалентности согласно предиката pred. Очевидно, что в общем случае среди этих классов эквивалентности никакого отношения порядка нет. И в этом случае если мы хотим какой-то порядок результатов, мы должны создавать его исходя из исходного состояния списка. Вполне логичен (с "императивной" точки зрения) первый вариант (порядок классов эквивалентности совпадает с порядком первых элементов этих классов в исходном списке). В ФП с рекурсией и т.п. это может быть второй вариант (определяется последним элементом в классе).
Ладно, вернемся к случаю T -> bool (T1 = bool). Допустим, на T1 есть "отношение порядка" (не знаю, есть ли оно на самом деле в C# и nemerle). В большинстве языков False < True (проверил haskell и ocaml, знаю что в C/C++, если не ошибаюсь, в paskall так же). Тогда логично предположить, что что порядок списков в результате совпадает с порядком на T1 (= bool), а сам "класс эквивалентности" определяется предикатом. Ну в "императивной логике" мы перечисляем элементы T1 и для них выбираем значения из T. Это, кстати, даст вариант, когда первыми идут значения, не соответствующие предикату.
Только вот мы видим, что и последний случай — не наш (ну либо в c#/nemerle отношение порядка на bool определено сильно нестандартным образом). Т.е. мы получили "специальное решение" для какого-то очень частного случая, который не выводится из общих случаев. Все таки мне кажется, что в существующей реализации такой порядок "получился случайно", а не следует из каких-либо других соображений.

После всего того, что написал, мне кажется наиболее логичным вариант упорядочивания по первому элементу в списке. Хотя да, я предполагал, что наиболее вероятным будет ответ "сначала то, что соответствует предикату, затем остаток". Но делал это не исходя из каких-либо соображений о списках и т.п., а из "ситуационной логики" автора функции, которому нужно было получить "список, соответствующий предикату и все остальное". Только вот функцию он назвал partition, а не по тому, что она на самом деле делает. И, кстати, сделал эту функцию недостаточно обобщенной (см. выше, partition в общем случае вполне работает на pred : T -> T1). Да и плохо то, что такая "телепатия" может не сработать в каком-нибудь случае и будет обидно. А учить все "сложившиеся случайности" не очень хочется.
VD>А вот как эта реализация записана на немерле:
VD>
VD>    /**
VD>     * Partitions a list into two sublists according to a predicate.
VD>     */
VD>    public Partition[T] (l : list [T], pred : T -> bool) : list [T] * list [T]
VD>    {
VD>

Ой! У меня на ревью такая конструкция во всех случаях получает "штамп" "не reusable, провоцирует создание велосипедов". Причина проста — не описан порядок возвращаемых значений. Дополнительное условие — partition реализуется элементарно своими силами. Я уже выше привел кучу рассуждений о том, какие же порядки в результате бывают и что нет никаких аргументов за один из них. В документации порядок значений в паре не описан, поэтому intellisence и ide мне ничем помочь не смогут. Использовать телепатию рискованно — можно ошибиться. Читать чужой код особого смысла нет. Я напишу свой вариант, при этом потеряю немного, но у меня таки будет уже "велосипед", который можно использовать в IDE (он дает подсказку, например). Ну и свой вариант я какое-то время буду помнить. А когда забуду — напишу еще один, они же быстро пишутся.

Есть и еще одна причина не использовать подобный код другого автора. Если уж мы дошли до применения "телепатии" и "ситуационной логики", чтобы понять, что же именно делает данная функция, то велик риск изменения дальнейшего поведения данной функции. Это не относится к стандартным и внешним библиотекам, но полностью относится к "общим библиотекам организации". Я такую наблюдал. Там код появлялся по принципу "Я решил задачу и написал такую крутую вещь. Я хочу, чтобы это стало общим достоянием". Только вот потом выясняется, что в исходной задаче нужно что-нибдуь подправить и контракт функции слегка меняется. Обычно это касается всяких граничных случаев, специальных значений параметра и т.п. И то, что было нормально у автора, начинает доставлять проблемы мне, потому что я тоже наблюдаю и обрабатываю граничные случаи.

M>>Вот если я использую Where, то рассуждаю просто — collection.where(predicate). Очевидно, это "коллекция", "где" выполняется "предикат". А с фильтром я не рассуждаю,


VD>А, ну, ясно решил докопаться до радио. Продолжай в том же духе.

А радио то бывает разное. Радиоприемники, например, чувствительностью отличаются. А читаемость при поиске бага или добавлении фичи — важный для меня момент. Хорошая читаемость позволяет не просматривать лишние методы, открывать или вспоминать документацию и т.п. Меньше скачков внимания, меньше вероятности ошибиться. Кстати, я уже выше "порассуждал с фильтром", получил еще 3 варианта, один из которых кажется более логичным и более "обобщаемый", чем вариант в библиотеке. Так что практика показывает, что рассуждать на "типичном" коде опасно.
VD>Методом Select не пользуйся, так как до него тоже можно докапываться. Он же занимается отображением или проекциец, а не как не выбором. SQL тоже в плохиши запиши...
Ну так а разве "выбор" не является проекцией? Да и термин понятнее большинству. Ну чем отличается "выбор по условию" и "проекция по условию"? Первое поймет больше народу. А второе бывает полезно в определенных контекстах для устранения неоднозначностей, но там, где однозначностей не возникает — не обязательно.
Да и если мне не изменяет память, select как раз таки не является проекцией в смысле реляционной алгебры. Реляционная алгебра работает в терминах множеств (неупорядоченной структуры) кортежей, а select позволяет еще и сортировки указывать (т.е. возвращает не множество а список) А то ведь мне хочется иметь возможность применять order by к результату join'а, например, без лишних select'ов.
Re[15]: Использование tuple
От: dr.Chaos Россия Украшения HandMade
Дата: 13.10.09 10:47
Оценка:
Здравствуйте, Andrei N.Sobchuck, Вы писали:

ANS>10% кода могут породить 90% багов И вообще, странно слышать аргумент про "требуют чуть более внимательного отношения" от человека предпочитающего статическую типизацию перед динамической из-за возможности получить ошибку приведения типов во время выполнения.


В Хаскеле есть newtype. Если его применить совместно с туплами перепутать будет невозможно.
Побеждающий других — силен,
Побеждающий себя — Могущественен.
Лао Цзы
Re[8]: Использование tuple
От: maxkar  
Дата: 13.10.09 10:48
Оценка: +1
Здравствуйте, VladD2, Вы писали:

VD>На самом деле программист обязан знать, что делает функция или метод, так как они являются абстракцией. Вопрос в том, что какие-то функции могут получить интуитивные имена, а какие-то нет (не будем же мы давать функциям имена состоящие из 40 слов?). И это не зависит от того используются ли тюплы или нет. Ты точно так же поцдешь смотреть, что делает функция если она возвратит одно значение. Да что там одно? Даже если она вообще ничего не возвращает, ты можешь "пойти". Ну, а вопрос интуитивности это совершенно отдельный вопрос. Для меня Filter и Partition совершенно интуитивно понятные названия определяющие как параметры, так и вовращаемые значения. Если тебе они не ясны, ну, что что или запомнишь, или будешь каждый раз читать документацию.


Не может программист знать, что функция делает. Для библиотек еще можно такого требовать, но для кода организации — нет. Иначе ценность программиста в компании сильно определяется тем, что же из ее кода он знает. А это плохо и для организации, и для самого программиста. Для организации плохо тем, что менять программистов тяжело. Для программиста тем, что полученная информация совершенно не конвертируемая (в другой организации придется учить другой набор велосипедов). Чем меньше нужно учить — тем лучше. Да и при том обычно получается так, что функции и методы являются плохой абстракцией. Ну не заботятся типичные разработчики о том, чтобы выделить и логично описать эту абстракцию. Так получаются, например, функции, которые являются "шаблоном с параметрами". Т.е. это алгоритм, в который подставляются отдельные фрагменты. Сам по себе алгоритм (метод) никакой законченной абстракцией не является и хоть что-то начинает проявляться только тогда, когда появляются его аргументы (т.е. соответствующие куски кода).
Я согласен с тем, что хорошо бы было, если бы имена были "интуитивные" и что это не всегда получается. Также хорошо бы было, чтобы все приложение было организовано "логичным образом" и т.п. А у тюплов (кортежей?) здесь примерно та же проблема, что и у out-параметров. Они позволяют "усложнить" ответственность функции, сделать неинтуитивные вещи. Причем в них даже не надо писать out при использовании. Кроме того, тюплы позволяют использовать себя там, где по-хорошему стоило бы использовать "запись" с именованными полями. Т.е. их наличие в языке открывает дорогу к misuse при том, что облегчает не так много ситуаций.

Интуитивность — да, отдельный вопрос. Но здесь следует уметь отличать два варианта — "интуитивно очевидно, что" и "как несложно показать, из введенных нами концепций следует, что...". Первое подлежит обязательному документированию, второе — нет. В противном случае можно получить проблемы, если у кого-то интуиция работает по-другому (например, он немного в другой области работал) и без документированного момента он тоже решит, что "все очевидно".
Re[9]: Использование tuple
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.10.09 11:54
Оценка: 10 (1) +1
Здравствуйте, maxkar, Вы писали:

M>Тут в соседней ветке Sinclar привел хороший пример с прямоугольниками, так что могу утверждать: даже при чтении (проверке!) вызова функции в виде f(a, b) могут быть проблемы. Касается как раз тех самых прямоугольников. Например, g.drawRect(x1, y1, x2, y2) является местом потенциальной ошибки (опять же, нужно знать библиотеку или читать документацию). По сравнению с ним вызов g.drawRect(rect) гораздо читабельнее. Да, там нужно проверять, что rect инициализирован правильно, но он то инициализируется через rect.setWidth(width) или rect.setX2(x2), что читается и проверяется легче (глаз цепляется за rest.setWidth(x2) или rect.setX2(width)).

Не-не-не-не-не, Дэвид Блейн. Две ошибки.
1. В записи g.drawRect(x1, y1, x2, y2) мы достаточно хорошо защищены от ошибки мудрой IDE, которая по ctrl-space покажет нам имена соучастников рисования прямоугольника.
2. Вызов g.drawRect(rect) не лучше. Я вижу два варианта:

g.drawRect(Rect(x1, y1, x2, y2));

rect = new Rect();
rect.X1 = x1;
rect.X2 = x2;
rect.Width = x2-y2; //+1
rect.Height = y2-y1; //+1
g.drawRect(rect);

Первый вариант 100% изоморфен тому, от которого уходим — т.е. мы только откладываем проблемы с читаемостью. Второй вариант — визуальный мусор. У нас тут уже появился именованный временный объект, строчек стало в шесть раз больше — а это означает, что наш прозрачный и понятный класс перестал влазить в один экран, со всеми вытекающими последствиями.

Если есть опасения, что код будут править люди без доступа к IDE, то мы можем (на C#) подчеркнуть суть происходящего значительно менее болезненным способом:
g.drawRect(x1=x1, y1=y1, x2=x2, y2=y2); // именованные параметры в действии

Это аналогично вот такому варианту твоего предложения:
g.drawRect(new Rect{X1=x1, Y1=y1, X2=x2, Y2=y2}; // object initializers спешат на помощь

Заметь, что мы по прежнему опираемся на именование параметров. Даже если мы перепутаем порядок, компилятор восстановит смысл за нас.

Именно вот такие вещи лично меня и вдохновляют на желание иметь (опциональную) возможность именовать элементы возвращаемого, гм, кортежа.

M>Теперь перейдем к математике. Стоит заметить, что выражения вида (a, b) = f() встречаются редко. Обычно идет a = f_a(), b = f_b(). Что-то не помню, чтобы из математических функций возвращалиь "пары и более объектов".

Из математических функций сплошь и рядом возвращаются пары и более объектов. В школе — да, используются в основном скаляры и вектора фиксированного размера.

M>Вектора — да, но это "объект вектор". Возврат прямоугольника скорее всего выглядел бы как "Пусть прямоугольник R — это .... Функция f находит прямоугольник r1, такой, что ...". Т.е. в математических текстах практически везде мы будем иметь "класс" (или запись с именованными полями).


M> Вполне вероятно, что "кортежи" можно будет встретить только в областях, непосредственно соприкасающихся с IT. Это области, изучающие алгоритмы и их сложность. В этом случае возврат кортежа вместо класса или вызова нескольких функций может давать преимущества (например, по времени выполнения).

Я так думаю, не обязательно изучать сложность алгоритмов, чтобы хотеть получать преимущества во времени выполнения.
Например, деление по модулю для длинных целых имеет смысл делать один раз для получения и остатка, и частного.

M>После всего того, что написал, мне кажется наиболее логичным вариант упорядочивания по первому элементу в списке.

Мне кажется наиболее логичным дать в языке средства явного выражения намерений автора.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[16]: Использование tuple
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 13.10.09 14:22
Оценка:
Здравствуйте, dr.Chaos, Вы писали:

DC>В Хаскеле есть newtype. Если его применить совместно с туплами перепутать будет невозможно.


Да, но использовать неудобнее — в паттерне надо будет явно выставлять конструктор.
Re[9]: Использование tuple
От: FR  
Дата: 13.10.09 16:40
Оценка: +1 :)
Здравствуйте, maxkar, Вы писали:

VD>>На самом деле программист обязан знать, что делает функция или метод, так как они являются абстракцией. Вопрос в том, что какие-то функции могут получить интуитивные имена, а какие-то нет (не будем же мы давать функциям имена состоящие из 40 слов?). И это не зависит от того используются ли тюплы или нет. Ты точно так же поцдешь смотреть, что делает функция если она возвратит одно значение. Да что там одно? Даже если она вообще ничего не возвращает, ты можешь "пойти". Ну, а вопрос интуитивности это совершенно отдельный вопрос. Для меня Filter и Partition совершенно интуитивно понятные названия определяющие как параметры, так и вовращаемые значения. Если тебе они не ясны, ну, что что или запомнишь, или будешь каждый раз читать документацию.


M>Не может программист знать, что функция делает.


Те же Filter и Partition это практически базовые стандартные ФВП. Их незнание для императивного программиста равносильно например незнанию цикла while.

M>Для библиотек еще можно такого требовать, но для кода организации — нет.


Это даже более базово чем библиотеки, по фундаментальности это что-то среднее между циклами и скажем STL в С++.
Плюс свои функции обычно строятся по образу и подобию базовых ФВП, поэтому их понятность на высоком уровне.
Re[12]: Использование tuple
От: VladD2 Российская Империя www.nemerle.org
Дата: 13.10.09 16:58
Оценка:
Здравствуйте, Sinclair, Вы писали:

VD>>На самом деле ref и out попросту не удобны. Для них нужно объявлять отдельные переменные, да и вообще не красиво. Такие феньки как вложение вызовов функций уже не прокатывают. Плюс есть еще одна проблема. ref и out-параметры требуют, чтобы выражения были lvalue, так что даже если тебе возвращаемое значение не интересно ты не сможешь в ref-параметр передать значение фунции (без промежуточной переменной).


Ага. Это особенно достает, так как приходится скатываться на конкретику там где в этом нет нужды.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Использование tuple
От: VladD2 Российская Империя www.nemerle.org
Дата: 13.10.09 17:04
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Я бы тут постарался развернуть вызов задом наперёд. Так, чтобы code completion мне помогал. Что-то типа

S>
S>GetPersonInfo(123) => (name, _, age);
S>


Это у тебя предрссудок. Видимо он вызван роскознями тех кто придумал from в LINQ спереди ставить.

На само деле интелисенсу глубоко фиолетово какой конкретно синтаксис у выражения. Так что геерацию имен параметров или пдсказки можно сделать и так. Было бы с чем сравнивать (т.е. была бы метаинформация).

Например, в интеграции немерла ты можешь подвести курсор к имени переменной в паттерне декомпозиции кортежа и увидить ее тип. Были бы имена, то увидил бы и их. Можно даже было бы сделать аналог метод-хинта. Ну, и конечно можно было бы просто генерировать такие переменные по некоторому паттерну. Например, вбиваешь:
def () = GetPersonInfo(123)

вызваешь комлейшон внутри скобок или жмешь некоторую комбинацию кнопок и получаешь заполненный список переменных.

Ну, и естественно при этом можно было бы и через точку работать. Но об этом я уже говорил.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.