Здравствуйте, artelk, Вы писали:
A>Здравствуйте, nikov, Вы писали:
N>>
N>>a = b / c;
N>>
A>Подумаю...
Ошибка компиляции, при неполном матче, решает не проблему "ой, забыл добавить вхождение" при написании самого матча, а проблему "добавили позже новый элемент варианта, все скомпилировалось без ошибок, но упало в рантантайме".
Надеюсь, понятно выразился.
Здравствуйте, artelk, Вы писали:
A>Ошибка компиляции, при неполном матче, решает не проблему "ой, забыл добавить вхождение" при написании самого матча, а проблему "добавили позже новый элемент варианта, все скомпилировалось без ошибок, но упало в рантантайме". A>Надеюсь, понятно выразился.
Обычно варнинга хватает, и при компиляции будет выявлено несоответствие. Но то что компилятор не умеет определить что матч упадет 100% это да, хотя информация о связывании типов есть.
Здравствуйте, hi_octane, Вы писали:
_NN>>Может все же не так плохо если в match требовать явно вариант "не подошло" в определенных случаях ?
_>Сейчас есть warning времени компиляции и возможная ошибка в рантайме. А в случае | _ => всё тоже самое, только без warning'a
Ну если программист сам пишет код вида try { .. } catch { /* ничего не делай */ } , то тут ничем не поможешь.
Другое дело, что это надо писать явно.
_NN>Ну если программист сам пишет код вида try { .. } catch { /* ничего не делай */ } , то тут ничем не поможешь. _NN>Другое дело, что это надо писать явно.
Представь насколько сложнее станет поиск ошибок, если у каждого try потребовать обязательный catch { }. Тоже самое и с требованием | _ => в match.
Здравствуйте, hi_octane, Вы писали:
_NN>>Ну если программист сам пишет код вида try { .. } catch { /* ничего не делай */ } , то тут ничем не поможешь. _NN>>Другое дело, что это надо писать явно.
_>Представь насколько сложнее станет поиск ошибок, если у каждого try потребовать обязательный catch { }. Тоже самое и с требованием | _ => в match.
Между прочим в C++ всегда требуется catch, а в Java/C# либо catch, либо finally.
И ничего, все инструменты работают хорошо и находят ошибки.
Здравствуйте, hi_octane, Вы писали:
_NN>>Может все же не так плохо если в match требовать явно вариант "не подошло" в определенных случаях ?
_>Сейчас есть warning времени компиляции и возможная ошибка в рантайме. А в случае | _ => всё тоже самое, только без warning'a
Ок, давай рассмотрим минусы и... минусы.
Минусы в случае, если будет ошибка компиляции:
1. Если будут ошибки компиляции, то разработчику придется делать матчи полными. Иногда он будет бездумно вставлять | _ => throw, чтобы тупо подавить ошибку компиляции.
2. Иногда компилятору будет недостаточно мозгов, чтобы определить, что матч полный. Например, из за хитрых when или случаев типа такого:
variant Num { |Zero | One | Two | Three }
Sum(n1 : Num, n2 : Num) : Num
{
match(n1)
{
| Zero => n2
| _ =>
match(n2)
{
| Zero => n1
| _ =>
match(n1, n2) // warning : matching is not exhaustive, example unmatched value: (Num.One, Zero)
{
| (One, One) => Num.Two()
| (One, Two)
| (Two, One) => Num.Three()
| (Two, Two)
| (_, Three)
| (Three, _) => throw OverflowException()
}
}
}
}
И люди будут вставлять | _ => throw со всеми вытекающими.
Минус в случае, если будет только предупреждение:
1. В проекте множество матчей. На часть из них компилятор ругается, показывая warning. На часть из этих предупреждений мы отреагировали, сделав матчи полными. Часть из них проигнорировали, т.к. уверены, что в рантайме ошибки не выскочит.
Итого получилось, например, 10 предупреждений, которых мы проигнорировали. Добывили в какой-то вариант новый элемент, появилось 11-е предупреждение. Никто, естественно, не заметил. Ошибка в рантайме.
Что-то мне подсказывает, что минус второго случая (с учетом вероятности возникновения) заметно превышает минусы первого (с учетом вероятности возникновения).
PS
Конечно, если делать так:
variant FooBar { |Foo | Bar }
match(fooBar)
{
| Foo => WriteLine("Foo")
| _ => //я имею ввиду Bar, но мне лень писать
WriteLine("Bar")
}
, то при добавлении в FooBar компилятор ничем не поможет в обоих случаях.
A>1. Если будут ошибки компиляции, то разработчику придется делать матчи полными. Иногда он будет бездумно вставлять | _ => throw, чтобы тупо подавить ошибку компиляции.
Ага. И впоследствии, как только появится ещё одно значение типа variant или enum, это будет создавать ему проблему. И думаешь разработчик себя будет винить, или компилятор которые сначала заставил его этот throw написать, а потом никаких средств для поиска этих мест до запуска не дал?
A>2. Иногда компилятору будет недостаточно мозгов, чтобы определить, что матч полный. Например, из за хитрых when или случаев типа такого: A>И люди будут вставлять | _ => throw со всеми вытекающими.
В реальном проекте тривиальных матчей, считанные единицы. Сейчас throw пишет компилятор, и вдобавок пишет предупреждение. Ты предлагаешь переложить половину этой работы на человека, а вторую половину выкинуть. А задача компилятора вообще-то человека максимально разгрузить
A>Минус в случае, если будет только предупреждение:
A>1. В проекте множество матчей. На часть из них компилятор ругается, показывая warning. На часть из этих предупреждений мы отреагировали, сделав матчи полными. Часть из них проигнорировали, т.к. уверены, что в рантайме ошибки не выскочит. A>Итого получилось, например, 10 предупреждений, которых мы проигнорировали. Добывили в какой-то вариант новый элемент, появилось 11-е предупреждение. Никто, естественно, не заметил. Ошибка в рантайме.
warning as error на продакшн спасут разработчика от позора. А #pragma disable N#### от ложных срабатываний в тех местах где проверено мин нет.
Но когда предупреждение всё-таки выскочит — место ошибки можно будет найти до того как она вылезет в рантайм. В случае же с обязательным | _ => это вообще никак невозможно сделать до запуска.
A>Что-то мне подсказывает, что минус второго случая (с учетом вероятности возникновения) заметно превышает минусы первого (с учетом вероятности возникновения).
Я почему такой злой был — у меня велосипеда небыло. Я тут междусобойчик готовил про ADT и pattern matching (плюс ненавязчивая реклама Nemerle ).
Хотел сказать такую фразу: "в отличие от switch/case по типам, в матче компилятор следит, чтобы был полный набор вхождений — т.е. как в паттерне Визитор".
Вобщем, согласен, что всегда и везде требовать полный матч не очень хорошо. Однако иногда хочется сказать компилятору: "вот тут должен быть полный матч, следи за этим".
Предлагаю разрешить эту проблему на уровне синтаксиса — сделать макрос matchex (от слова exhaustive) с таким поведением:
1. конвертация предупреждения в ошибку
2. запрет "_ => ..."
Здравствуйте, artelk, Вы писали:
A>Я почему такой злой был — у меня велосипеда небыло. Я тут междусобойчик готовил про ADT и pattern matching (плюс ненавязчивая реклама Nemerle ). A>Хотел сказать такую фразу: "в отличие от switch/case по типам, в матче компилятор следит, чтобы был полный набор вхождений — т.е. как в паттерне Визитор".
A>Вобщем, согласен, что всегда и везде требовать полный матч не очень хорошо. Однако иногда хочется сказать компилятору: "вот тут должен быть полный матч, следи за этим". A>Предлагаю разрешить эту проблему на уровне синтаксиса — сделать макрос matchex (от слова exhaustive) с таким поведением: A>1. конвертация предупреждения в ошибку A>2. запрет "_ => ..."
A>На сколько сложно это сделать?
Звучит хорошим компромисом.
Синтаксис то сделать несложно, см. regex match
Да и поведение тоже, сканнируем все сопоставления и делаем что хотим.
Здравствуйте, artelk, Вы писали:
A>Предлагаю разрешить эту проблему на уровне синтаксиса — сделать макрос matchex (от слова exhaustive) с таким поведением: A>1. конвертация предупреждения в ошибку
Это и так делается. Запрещаешь ворнинги и разрешаешь те что хочешь оставить.
A>2. запрет "_ => ..."
В рамках боле-менее большого проекта это будет неработоспособным решением, на мой взгляд.
A>На сколько сложно это сделать?
Сделать элементарно. Но незачем.
A>PS Доклад прошел на отлично, всем понравилось!
Надо было записывать и выкладывать куда-нить.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
A>Я почему такой злой был — у меня велосипеда небыло. Я тут междусобойчик готовил про ADT и pattern matching (плюс ненавязчивая реклама Nemerle ). A>Хотел сказать такую фразу: "в отличие от switch/case по типам, в матче компилятор следит, чтобы был полный набор вхождений — т.е. как в паттерне Визитор".
Я полагаю, что главное отличие сопоставления с образцом — это структурная декомпозиция и возможность связывать идентификаторы. В любом случае, у вас было полное право сказать эту фразу, поскольку компилятор действительно следит и информирует.
A>Вобщем, согласен, что всегда и везде требовать полный матч не очень хорошо. Однако иногда хочется сказать компилятору: "вот тут должен быть полный матч, следи за этим".
Он и так следит, просто не надо его игнорировать. Лично для ошибка компиляции при неполном матче не стала бы проблемой. Но, если посмотреть на ближайший родственный язык — F#. В нем подход абсолютно аналогичный. Например
type Test= Foo | Bar
let test = Foo
let x = match test with
| Bar -> true
приводит к
предупреждение FS0025: Незавершенный шаблон соответствует данному выражению. К примеру, значение "Foo" может указывать на случай, не покрытый шаблоном(ами).
Я полагаю, что в OСaml тоже самое. Видимо, практика применения показывает, что предупреждения в данной ситуации удобней.
A>Предлагаю разрешить эту проблему на уровне синтаксиса — сделать макрос matchex (от слова exhaustive) с таким поведением: A>1. конвертация предупреждения в ошибку A>2. запрет "_ => ..."
Я считаю, это организационный вопрос. Как уже писали выше, надо лишь трактовать warning'и как ошибку. Например по рецепту hardcase'a
. Плодить на ровном месте (непонятно кем поддерживаемый) дубликат одного из ключевых элементов языка — явный оверкилл, вне зависимости от сложности реализации. Тут же не лисперы собрались, чтобы ради косметических отличий пилить все с нуля в своей песочнице.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, artelk, Вы писали:
A>>Предлагаю разрешить эту проблему на уровне синтаксиса — сделать макрос matchex (от слова exhaustive) с таким поведением: A>>1. конвертация предупреждения в ошибку
VD>Это и так делается. Запрещаешь ворнинги и разрешаешь те что хочешь оставить.
Кажется ты не понял. Макрос matchex — это не макроаттрибут уровня сборки, который во всем проекте заставляет делать матчи полными и при этом запрещает _=>...
Это вид матча, применяемый в конкретном месте — там, где нам нужно.
Вот делаешь ты, например, транслятор одной структуры данных в другую (или тупо специальный сериализатор какого-то варианта, например). И тебе нужно, чтобы, если в будущем автор этого варианта что-то еще добавит, транслятор не компилировался бы до тех пор, пока ты его не обновишь.
A>>2. запрет "_ => ..."
VD>В рамках боле-менее большого проекта это будет неработоспособным решением, на мой взгляд.
Здравствуйте, artelk, Вы писали:
A>Здравствуйте, VladD2, Вы писали:
VD>>Здравствуйте, artelk, Вы писали:
A>>>Предлагаю разрешить эту проблему на уровне синтаксиса — сделать макрос matchex (от слова exhaustive) с таким поведением:
Тогда уж лучше сделать
variant hhh
{
[RequestMatch]|Foot
|Bar
}
def t=hhh.Bar
match()
{
| Bar => ....
| _ =>
}
// error Foot request match
Здравствуйте, artelk, Вы писали:
A>Кажется ты не понял. Макрос matchex — это не макроаттрибут уровня сборки, который во всем проекте заставляет делать матчи полными и при этом запрещает _=>... A>Это вид матча, применяемый в конкретном месте — там, где нам нужно.
Ну, такой ты конечно можешь сделать, но не уверен, что он будет востребован. Можно положить в какое-нибудь отдельное пространство имен в сниппеты. Кому захочется может воспользоваться.
A>Вот делаешь ты, например, транслятор одной структуры данных в другую (или тупо специальный сериализатор какого-то варианта, например). И тебе нужно, чтобы, если в будущем автор этого варианта что-то еще добавит, транслятор не компилировался бы до тех пор, пока ты его не обновишь.
Я обычно думаю нужно ли делать "| _ =>" в маче и не делаю его, если можно просто перечислить оставшиеся элементы. Я "| _ =>" только там, где есть осмысленная обработка. В крайнем случае я ставлю assert3(false) в "| _ =>".
Ну, и на ворнинги всегда обращаю внимание и не иду дальше если они есть.
Вместе эти два принципе дают вполне достаточный контроль.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.